1、阻塞I/O模型
阻塞I/O模型是常见的I/O模型,在读写数据时客户端会发生阻塞。阻塞I/O模式的工作流程为:当用户发出I/O请求之后,内核会检查数据是否就绪,此时用户线程会一直阻塞等待内存数据就绪,在内存数据就绪后,内核将数据复制到用户线程中,并返回I/O执行结果到用户线程,此时用户线程将解除阻塞状态并开始处理数据。
典型的阻塞I/O模型的如下:
data=socket.read()
2、非阻塞I/O模型
非阻塞I/O模型是指用户在发起一个I/O操作后,无需阻塞便马上可以得到内核返回的结果。如果内核返回的结果为false,则表示内核数据还没准备好,需要稍后再发起I/O操作。一旦内核中数据准备好了,并且再次收到用户的请求,内核就会立刻将数据复制到用户线程中并将结果通知用户线程。
典型的非阻塞I/O模型如下:
1 while (true){ 2 data = socket.read(); 3 if(data == true){ 4 //获取并处理内核数据,通知用户线程 5 break; 6 }else { 7 //数据没有准备好,此时处理用户线程其他实际 8 } 9 }
3、多路复用I/O模式
多路复用模型是多线程并发编程用得比较多的模型,java NIO就是基于多路复用I/O模型实现的。在多路复用I/O模型中会有一个被称为selector的线程不断轮询多个socket的状态,只有在socket有读写事件时,才会通知用户线程进行I/O读写操作。
因为在多路复用I/O模型中只需要一个selector线程就可以管理多个socket,并且只有在真正有socket读写操作的时候才会使用操作系统的I/O资源,大大节约了系统资源。
java NIO在用户的每个线程中都通过selector.select()查询当前通道是否有事件到达,如果没有,用户线程会一直阻塞。而多路复用I/O模型通过一个线程管理多个socket通道,在socket有读写事件触发的时候才会通知用户线程进行I/O读写操作。
非阻塞I/O模型在每个用户线程中都进行socket状态检查,而多路复用I/O模型中是在系统内核中进行socket状态检查,这也是多路复用I/O模型比非阻塞I/O模型效率高的原因。
多路复用I/O模型通过在一个selector线程上以轮询的方式检测在多个socket上是否有事件到达,并啄个进行事件处理和响应。因为对于多路复用I/O模型来说,在事件响应体很大时,selector线程就会成为性能瓶颈,导致后续的事件迟迟得不到处理,影响下一轮的事件轮询。在事件应用中,在多路复用方法体内一般不建议做复杂逻辑运算,只做数据的接收和转发。
4、信号驱动I/O模型
在信号驱动I/O模型中,在用户发起一个I/O请求操作时,系统会为该请求对应的socket注册一个信号函数,然后用户线程可以继续执行其他业务逻辑,在内核数据就绪时,系统会发送一个信号到用户线程,用户线程收到信号时,会在信号函数中调用对应的I/O读写操作完成实际的I/O请求操作。
5、异步I/O模型
在异步I/O模型中,用户线程会发起一个asynchronous read操作到内核,内核在接收到请求后会立马返回一个状态,来说明请求是否成功发起,在此过程中用户线程不会发生任何阻塞。接着,内核会等待数据准备完成并将数据复制到用户线程中,在复制完成后会发个信号给用户线程,通知用户线程asynchronous读操作已完成。在异步I/O模型中,用户线程不需要关心整个I/O操作是如何进行的,只需要发一个请求,在接收到内核返回的成功或者失败时,说明I/O请求已经完成,直接使用数据即可。
6、java I/O
具体使用参考JDK api
7、Java NIO
Java NIO 的主要实现涉及三大核心内容:Selector、Channel、Buffer。Selector用于监听多个Channeld的事件,比如链接打开或数据到达,因此,一个线程可以实现对多个Channel的管理。传统的I/O是基于流的读写操作,而java NIO基于Channel和Buffer进行I/O读写操作,并且数据总是被从Channel读取到Buffer中,或者从Buffer读取到Channel中。
java NIO跟传统I/O最大的区别如下:
1、I/O是面向流的,NIO是面向缓冲区的,在面向流的操作中,数据只能在一个流中连续进行读写,数据没有缓冲,因此字节流无法前后移动。而在NIO中每次都是将数据从一个Channel读取到一个Buffer中,再从Buffer读取到Channel中,因此可以方便的在缓冲区进行数据的前后移动等操作。该功能在应用层主要用于数据的粘包,拆包等操作,在网络不可靠的环境下尤为重要。
2、传统I/O的流操作是阻塞的,NIO流的操作是非阻塞的。在传统的I/O下,用户线程在调用read()或write()时,用户线程将一直被阻塞,直到数据被完全读取或写入。NIO通过selector监听Channel上的事件变化,在Channel上有数据变化时,通知该线程进行数据的读写操作。对于读取操作而言,在Channel上有数据时,线程将进行读Buffer操作,在没有数据时,线程可以执行其他业务逻辑。对于写操作而言,在使用线程执行写操作将一些数据写入某channel时,只需将channel上的数据异步写入Buffer即可,Buffer上的数据会被异步写入目标Channel上,用户不需要等待整个数据被完全写入目标Channel就可以继续执行其他业务逻辑。
1、Channel介绍:
Channel和I/O中的Stream(流)类似,只不过Stream是单向的,而Channel是双向的,既可以操作读操作,也可以操作写操作。
2、Buffer
Buffer实际上是一个容器,其内部通过一个连续的字节数组存储I/O上的数据,在NIO中,Channel文件、网络上对数据的读取或写入都必须经过Buffer。
3、Selector
Selector用于检测在多个注册的Channel上是否有I/O事件发生,并检测到的I/O事件进行相应的响应和处理。因此通过一个Selector线程就可以实现对多个Channel的管理,不必为每个链接都创立一个线程,避免线程资源的浪费和多线程上下文切换导致的开销。同时,Selector只有在Channel上有读写事件发生时,才会调用I/O函数进行读写操作,可极大的减少开支,提高系统并发量。
4、java NIO的使用
MyServer类:
package cn.com.yitong.ares.A20200818; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class MyServer { private int size = 1024; private ServerSocketChannel serverSocketChannel; private ByteBuffer byteBuffer; private Selector selector; private int remoteClientNum = 0; public MyServer(int port){ try { initChannel(port); } catch (Exception e) { e.printStackTrace(); System.exit(-1); } } //channel初始化 private void initChannel(int port) throws Exception{ //打开channel serverSocketChannel = ServerSocketChannel.open(); //设置为非阻塞模式 serverSocketChannel.configureBlocking(false); //绑定端口 serverSocketChannel.bind(new InetSocketAddress(port)); System.out.println("port is :" + port); //选择器的创建 selector = Selector.open(); //向选择器注入通道 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //分配缓冲区的大小 byteBuffer = ByteBuffer.allocate(size); } //监听器,用于监听channel上的数据变化 private void listrner() throws Exception{ while(true){ //返回的int值表示有多个channel处于就绪状态 int n = selector.select(); if(n == 0){ continue; } //每个selector 对应多个selectionKey,每个selectionKey对应一个channel Iterator<SelectionKey> iterable = selector.selectedKeys().iterator(); while (iterable.hasNext()){ SelectionKey key = iterable.next(); //如果selectionKey处于就绪状态,则开始接收客户端的链接 if(key.isAcceptable()){ //获取channel ServerSocketChannel server = (ServerSocketChannel) key.channel(); //channel接收链接 SocketChannel channel = server.accept(); //channel注册 registerChannel(selector,channel,SelectionKey.OP_READ); //远程客户端的链接数 remoteClientNum++; System.out.println("online client num = " + remoteClientNum); write(channel,"hello world".getBytes()); } //如果通道已经处于就绪状态 if(key.isReadable()){ read(key); } iterable.remove(); } } } private void read(SelectionKey key) throws Exception{ SocketChannel socketChannel = (SocketChannel) key.channel(); int count; byteBuffer.clear(); //从通道中读取数据到缓冲区 while ((count = socketChannel.read(byteBuffer)) >0){ //byteBuffer写模式变为读模式 byteBuffer.flip(); StringBuffer stringBuffer = new StringBuffer(); boolean firstLine = true; while(byteBuffer.hasRemaining()){ if(firstLine){ firstLine = false; }else { // stringBuffer.append("\n"); } stringBuffer.append((char)byteBuffer.get()); System.out.println("+++++++" + stringBuffer.toString()); }; System.out.println("+++++++" + stringBuffer.toString()); byteBuffer.clear(); } if(count < 0 ){ socketChannel.close(); } } private void write(SocketChannel channel, byte[] bytes) throws Exception{ byteBuffer.clear(); byteBuffer.put(bytes); byteBuffer.flip(); //将缓冲区的数据写入通道中 channel.write(byteBuffer); } private void registerChannel(Selector selector, SocketChannel channel, int opRead) throws Exception{ if(channel == null){ return; } channel.configureBlocking(false); channel.register(selector,opRead); } public static void main(String[] args){ MyServer server = new MyServer(9000); try { server.listrner(); } catch (Exception e) { e.printStackTrace(); } } }
Myclient类:
package cn.com.yitong.ares.A20200818; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class Myclient { private int size = 1024; private ByteBuffer byteBuffer; private SocketChannel socketChannel; public void connectServer() throws IOException { socketChannel = socketChannel.open(); socketChannel.connect(new InetSocketAddress("10.2.189.172",9000)); socketChannel.configureBlocking(false); byteBuffer = ByteBuffer.allocate(size); receive(); } private void receive() throws IOException { while (true){ byteBuffer.clear(); int count; //如果没有数据,则read方法一直阻塞,直到读取到新的数据 while ((count = socketChannel.read(byteBuffer)) > 0){ byteBuffer.flip(); while (byteBuffer.hasRemaining()){ System.out.println((char)byteBuffer.get()); } send2Server("<head></head>".getBytes()); byteBuffer.clear(); } } } private void send2Server(byte[] bytes) throws IOException { byteBuffer.clear(); byteBuffer.put(bytes); byteBuffer.flip(); socketChannel.write(byteBuffer); } public static void main(String[] args){ try { new Myclient().connectServer(); } catch (IOException e) { e.printStackTrace(); } } }
此文本非原创!!
原文:https://www.cnblogs.com/xueyan4040/p/13531432.html