javaNIO原理(含代码)及与 同步阻塞IO 、伪异步IO比较

一.同步阻塞IO

BIO就是阻塞式的IO,网络通信中对于多客户端的连入,服务器端总是与客户端数量一致的线程去处理每个客户端任务,即,客户端与线程数1:1,并且进行读写操作室阻塞的,当有你成千上完的客户端进行连接,就导致服务器不断的建立新的线程,最后导致低通资源不足,后面的客户端不能连接服务器,并且连接入的客户端并不是总是在于服务器进行交互,很可能就只是占用着资源而已。

二.伪异步IO

伪异步IO对同步IO进行了优化,后端通过一个线程池和任务队列去处理所有客户端的请求,当用完后在归还给线程池,线程池的原理和数据库连接池的原理很像,他减少了线程创建和销毁的时间,无论多少客户端的连接都是这些固定的线程数量去处理,这在大量客户端与服务器信息交互量很少的情况下,是一种很好的处理方式,但是想一种情况,当有一个客户端的读取信息非常慢时,服务器对其的写操作时会很慢,甚至会阻塞很长时间,因为线程池中的线程是有限的,当有客户端需要分配线程时,就会导致新任务在队列中一直等待阻塞的客户端释放线程。当任务队列已经满时,就会有大量的用户发生连接超时。其实,伪异步IO也是同步阻塞IO。

三.javaNIO原理

为了解决上面的两种阻塞IO的缺陷,java在1.4版本开始加入NIO也称为new IO,异步IO模型。网络通信中,NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道来实现,可以设置阻塞余非阻塞两种模式,为了实现高负载高并发都采取非阻塞的模式。NIO采用缓冲区BUFFER,实现对数据的读写操作,缓冲区是固定大小,并由内部状态记录有多少数据被放入或者取出。与阻塞IO不同,阻塞IO采用阻塞式流(Stream)的方式进行读写,流是单向的只能向一个方向读数据或者写数据,而通道是双向的,可以同时在通道上发送和读取数据,而且是非阻塞的,在没有数据可读可写时可以去做别的事情。

NIO改进了上面的一对一或者M:N的模型。服务器仅采用一个一个线程去处理所有客户端线程,这就需要创建一个selector,并将其注册到想要监控的信道上(注意是通过channel的方法来实现),并返回一个selectorKey实例(包含通道和select以及感兴趣的操作),selector就好像是一个观察者,通过不断轮询所注册的一组通道上有没有等待的操作发生,当等待事件发生的时候可以做其他事情,当有信道出现感兴趣的操作,则该信道就进入就绪状态。

Slector的select方法阻塞等待又没有就绪的通道,当出现就绪的信道或者等待超时返回,就绪信道的个数,若等待超时则返回-1,selectedKeys方法返回就绪的信道。

下面附代码

这是处理感兴趣信道的接口,因为他可以放在多个服务器上所以把他做成了接口。

package Nio;

import java.io.IOException;
import java.nio.channels.SelectionKey;

public interface TCPProtocol {
	void handleAccept(SelectionKey key) throws IOException;
	void handleRead(SelectionKey key) throws IOException;
	void handleWrite(SelectionKey key) throws IOException;
}
<span style="font-family:FangSong_GB2312;">这是对上面接口的具体实现</span>
package Nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class SelectorProtocol implements TCPProtocol {
	private int bufSize ;
	public SelectorProtocol(int buffsize){
		this.bufSize = buffsize;
	}

	 //服务端信道已经准备好了接收新的客户端连接
    public void handleAccept(SelectionKey key) throws IOException {
        SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();
        clntChan.configureBlocking(false);
        //将选择器注册到连接到的客户端信道,并指定该信道key值的属性为OP_READ,同时为该信道指定关联的附件
        clntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufSize));
    }  

    //客户端信道已经准备好了从信道中读取数据到缓冲区
    public void handleRead(SelectionKey key) throws IOException{
        SocketChannel clntChan = (SocketChannel) key.channel();
        //获取该信道所关联的附件,这里为缓冲区
        ByteBuffer buf = (ByteBuffer) key.attachment();
        long bytesRead = clntChan.read(buf);
        //如果read()方法返回-1,说明客户端关闭了连接,那么客户端已经接收到了与自己发送字节数相等的数据,可以安全地关闭
        if (bytesRead == -1){
            clntChan.close();
        }else if(bytesRead > 0){
        //如果缓冲区总读入了数据,则将该信道感兴趣的操作设置为为可读可写
        key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        }
    }  

    //客户端信道已经准备好了将数据从缓冲区写入信道
    public void handleWrite(SelectionKey key) throws IOException {
    //获取与该信道关联的缓冲区,里面有之前读取到的数据
    ByteBuffer buf = (ByteBuffer) key.attachment();
    //重置缓冲区,准备将数据写入信道
    buf.flip();
    SocketChannel clntChan = (SocketChannel) key.channel();
    //将数据写入到信道中
    clntChan.write(buf);
    if (!buf.hasRemaining()){
    //如果缓冲区中的数据已经全部写入了信道,则将该信道感兴趣的操作设置为可读
      key.interestOps(SelectionKey.OP_READ);
    }
    //为读入更多的数据腾出空间
    buf.compact();
  }
}

客户端

package Nio;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class TCPEchoClientNonblocking {
	public static void main(String args[]) throws Exception{
		//第一个参数作为要连接的服务端的主机名或IP
		String server = "localhost";
		//第二个参数为要发送到服务端的字符串
		byte[] argument = "nihaopengyou".getBytes();
		//如果有第三个参数,则作为端口号,如果没有,则端口号设为7
		int servPort = 2002;
		//创建一个信道,并设为非阻塞模式
		SocketChannel clntChan = SocketChannel.open();
		clntChan.configureBlocking(false);
		//向服务端发起连接
		if (!clntChan.connect(new InetSocketAddress(server, servPort))){
			//不断地轮询连接状态,直到完成连接
			while (!clntChan.finishConnect()){
				//在等待连接的时间里,可以执行其他任务,以充分发挥非阻塞IO的异步特性
				//这里为了演示该方法的使用,只是一直打印"."
				System.out.print(".");
			}
		}
		//为了与后面打印的"."区别开来,这里输出换行符
		System.out.print("\n");
		//分别实例化用来读写的缓冲区
		ByteBuffer writeBuf = ByteBuffer.wrap(argument);
		ByteBuffer readBuf = ByteBuffer.allocate(argument.length);
		//接收到的总的字节数
		int totalBytesRcvd = 0;
		//每一次调用read()方法接收到的字节数
		int bytesRcvd;
		//循环执行,直到接收到的字节数与发送的字符串的字节数相等
		while (totalBytesRcvd < argument.length){
			//如果用来向通道中写数据的缓冲区中还有剩余的字节,则继续将数据写入信道
			if (writeBuf.hasRemaining()){
				clntChan.write(writeBuf);
			}
			//如果read()接收到-1,表明服务端关闭,抛出异常
			if ((bytesRcvd = clntChan.read(readBuf)) == -1){
				throw new SocketException("Connection closed prematurely");
			}
			//计算接收到的总字节数
			totalBytesRcvd += bytesRcvd;
			//在等待通信完成的过程中,程序可以执行其他任务,以体现非阻塞IO的异步特性
			//这里为了演示该方法的使用,同样只是一直打印"."
			System.out.print(".");
		}
		//打印出接收到的数据
		System.out.println("Received: " +  new String(readBuf.array(), 0, totalBytesRcvd));
		//关闭信道
		clntChan.close();
	}
}

服务器

package Nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;

public class TCPServerSelector{
	//缓冲区的长度
	private static final int BUFSIZE = 256;
	//select方法等待信道准备好的最长时间
	private static final int TIMEOUT = 3000;
	public static void main(String[] args) throws IOException {
		if (args.length < 1){
			throw new IllegalArgumentException("Parameter(s): <Port> ...");
		}
		//创建一个选择器
		Selector selector = Selector.open();
		for (String arg : args){
			//实例化一个信道
			ServerSocketChannel listnChannel = ServerSocketChannel.open();
			//将该信道绑定到指定端口
			listnChannel.socket().bind(new InetSocketAddress(Integer.parseInt(arg)));
			System.out.println("启动服务器"+Integer.parseInt(arg));
			//配置信道为非阻塞模式
			listnChannel.configureBlocking(false);
			//将选择器注册到各个信道
			listnChannel.register(selector, SelectionKey.OP_ACCEPT);
		}
		//创建一个实现了协议接口的对象
		TCPProtocol protocol = new SelectorProtocol(BUFSIZE);
		//不断轮询select方法,获取准备好的信道所关联的Key集
		while (true){
			//一直等待,直至有信道准备好了I/O操作
			if (selector.select(TIMEOUT) == 0){
				//在等待信道准备的同时,也可以异步地执行其他任务,
				//这里只是简单地打印"."
				System.out.print(".");
				continue;
			}
			//获取准备好的信道所关联的Key集合的iterator实例
			Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
			//循环取得集合中的每个键值
			while (keyIter.hasNext()){
				SelectionKey key = keyIter.next();
				//如果服务端信道感兴趣的I/O操作为accept
				if (key.isAcceptable()){
					protocol.handleAccept(key);
				}
				//如果客户端信道感兴趣的I/O操作为read
				if (key.isReadable()){
					protocol.handleRead(key);
				}
				//如果该键值有效,并且其对应的客户端信道感兴趣的I/O操作为write
				if (key.isValid() && key.isWritable()) {
					protocol.handleWrite(key);
				}
				//这里需要手动从键集中移除当前的key
				keyIter.remove();
			}
		}
	}
}

运行结果:

时间: 2024-10-10 01:42:13

javaNIO原理(含代码)及与 同步阻塞IO 、伪异步IO比较的相关文章

Windows内核原理-同步IO与异步IO

目录 Windows内核原理-同步IO与异步IO 背景 目的 I/O 同步I/O 异步I/O I/O完成通知 总结 参考文档 Windows内核原理-同步IO与异步IO 背景 在前段时间检查异常连接导致的内存泄漏排查的过程中,主要涉及到了windows异步I/O相关的知识,看了许多包括重叠I/O.完成端口.IRP.设备驱动程序等Windows下I/O相关的知识,虽然学习到了很多东西,但是仍然需要自顶而下的将所有知识进行梳理. 目的 本片文章主要讲解同步I/O与异步I/O相关知识,希望通过编写本篇

同步IO,异步IO,阻塞IO,非阻塞IO的联系与区别

转载 POSIX 同步IO.异步IO.阻塞IO.非阻塞IO,这几个词常见于各种各样的与网络相关的文章之中,往往不同上下文中它们的意思是不一样的,以致于我在很长一段时间对此感到困惑,所以想写一篇文章整理一下. POSIX(可移植操作系统接口)把同步IO操作定义为导致进程阻塞直到IO完成的操作,反之则是异步IO 按POSIX的描述似乎把同步和阻塞划等号,异步和非阻塞划等号,但是为什么有的人说同步IO不等于阻塞IO呢?先来说说几种常见的IO模型吧. IO模型 这里统一使用Linux下的系统调用recv

Python_Day11_同步IO和异步IO

同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的.所以先限定一下本文的上下文. 本文讨论的背景是Linux环境下的network IO. 一 概念说明 在进行解释之前,首先要说明几个概念:- 用户空间和内核空间- 进程切换- 进程的阻塞- 文件描述符- 缓存 I/O 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方).操作系统的核心是内核,独立于普通的应用程

Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO

Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO Java 非阻塞 IO 和异步 IO 转自https://www.javadoop.com/post/nio-and-aio 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO https://blog.csdn.net/column/details/21963.html 部分代码会放在我的的Github:https://github.com/h2p

{python之IO多路复用} IO模型介绍 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) 多路复用IO(IO multiplexing) 异步IO(Asynchronous I/O) IO模型比较分析 selectors模块

阅读目录 一 IO模型介绍 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路复用IO(IO multiplexing) 五 异步IO(Asynchronous I/O) 六 IO模型比较分析 七 selectors模块 一 IO模型介绍 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问题其实不同的人给出的答案都可能

同步IO和异步IO

链接: 同步IO和异步IO socket阻塞与非阻塞,同步与异步.I/O模型 Linux的IO系统常用系统调用及分析 linux异步IO的两种方式

同步IO与异步IO的区别

同步IO与异步IO的区别 首先要明确一点:不同IO模型之间的差别本质上是CPU的参与方式 这里重点说一下各自的应用场景 如何选择同步还是异步呢? 主要有这么几个指标供参考 1. 并发数量 2. 接收字节数 3. 处理请求所需CPU时间 我们一个一个来考察 并发数 并发低的时候同步IO与异步IO差别不大 并发高时差别会比较明显,这要表现在 1. 开启线程数:如并发1000时,同步IO要开启1000个线程,1000个线程要占用很多内存,这是其一,其二1000个线程间切换的时间也是很可观的:异步IO则

阻塞IO、非阻塞IO、IO多路复用、同步IO、异步IO 的理论

同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问题其实不同的人给出的答案都可能不同,比如wiki,就认为asynchronous IO和non-blocking IO是一个东西.这其实是因为不同的人的知识背景不同,并且在讨论这个问题的时候上下文(context)也不相同.所以,为了更好的回答这个问题,我先限定一下本文的上下文.本文讨论的背景是Linux环境下的ne

IO模型--阻塞IO,非阻塞IO,IO多路复用,异步IO

IO模型介绍: * blocking IO 阻塞IO * nonblocking IO 非阻塞IO * IO multiplexing IO多路复用 * signal driven IO 信号驱动IO () * asynchronous IO 异步IO IO模型介绍: 为了更好地了解IO模型,我们需要事先回顾下:同步.异步.阻塞.非阻塞 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,