• java NIO(四)----阻塞IO与非阻塞IO


    阻塞IO

      之间在java NIO(一)-----NIO基本概念中提到传统的IO是阻塞式的,而NIO是非阻塞式的(相对于网络通信而言)。通过下面图中可以了解IO阻塞的过程。

    1.   客户端向服务端发起一个读写请求,但是服务端不确定数据是否有效,此时该线程就会进入阻塞状态,也就是说此线程在此期间无法做其他任何事情。

    在这里插入图片描述

    1.   针对于上面的情况,后面有了一个治标不治本的方法-------使用多线程

    使用多线程技术之后,1号线程阻塞了还有2号线程可用来做其他事,2号堵塞了还有3号线程可以用,相对于上面单线程来说确实能够解决一部分问题,但是线程池的线程数量总是有限制了,不可能无限使用。而且线程占用的都是实实在在的内存,开销非常大。所以使用多线程解决IO阻塞治标不治本。
    在这里插入图片描述

    非阻塞IO

      从前文中知道了阻塞式IO的弊端,再来了解一下非阻塞式IO的原理:
    非阻塞式IO中有三个核心:
    1.Channel 通道
    2.Buffer 缓冲区
    3.Selector 选择器

      Selector选择器在java API文档中被称作是多路复用器,用于注册一个或多个Channel通道,Selector会不断地检测通道状态是否是可读的或者可写的。它实现了集中管理监控通道状态,避免了因数据的不确定性导致的IO阻塞等待。(Selector的作用让我想到了Zookeeper注册中心)

    在这里插入图片描述
    Selector API文档介绍
      当我们的客户端向服务端请求一个数据的时候,Channel通道因为注册到了Selector选择器,所以Selector会等到服务端的数据完全准备就绪之后,再将此请求发送到服务器端的一个或者多个线程上去执行,因为数据此时是已经准备好了的,所以线程不会进入阻塞状态。

    下面代码演示了客户端向服务端发送消息的实现过程;

    	@Test
    	void Client() throws IOException {
    		//1.获取通道
    		SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8083));
    		//2.切换成非阻塞模式
    		sChannel.configureBlocking(false);
    		//3.分配指定大小的缓冲区
    		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    		//4.发送数据给服务器端
    		byteBuffer.put("客户端发送的数据".getBytes());
    		//5.切换为读模式
    		byteBuffer.flip();
    
    		sChannel.write(byteBuffer);
    		byteBuffer.clear();
    		//6.关闭通道
    		sChannel.close();
    
    	}
    
    	@Test
    	void Server() throws IOException {
    		//1.获取通道
    		ServerSocketChannel serverChannel = ServerSocketChannel.open();
    		//2.切换为非阻塞模式
    		serverChannel.configureBlocking(false);
    		//3.绑定连接
    		serverChannel.bind(new InetSocketAddress(8083));
    		//4.获取选择器
    		Selector selector = Selector.open();
    		//5.注册通道到选择器(注册的是监听事件)
    		serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    		//6.通过选择器轮询式的获取选择器上已经准备就绪的事件
    		while (selector.select() > 0) {
    			//当选择器上准备就绪的时间大于0(至少有一个准备好了)
    			//7.获取当前选择器中所有注册的选择键(已就绪的监听事件)
    			Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
    			while (iterator.hasNext()) {
    				//8.获取准备就绪的事件
    				SelectionKey next = iterator.next();
    				//9.判断该事件是什么时间(读、写、连接、接收)
    				if (next.isAcceptable()) {
    					//10.若接收就绪,获取客户端连接
    					SocketChannel channel = serverChannel.accept();
    					//11.切换为非阻塞模式
    					channel.configureBlocking(false);
    					//12.将该通道注册到选择器上
    					channel.register(selector, SelectionKey.OP_READ);
    				} else if (next.isReadable()) {
    					//13.获取当前选择器上读就绪状态的通道
    					SocketChannel channel = (SocketChannel) next.channel();
    					//14.获取数据
    					ByteBuffer bBuffer = ByteBuffer.allocate(1024);
    					int len = 0;
    					while ((len = channel.read(bBuffer)) > 0) {
    						bBuffer.flip();
    						System.out.println(new String(bBuffer.array(),0,len));
    						bBuffer.clear();
    					}
    				}
    				//15.取消选择器
    				iterator.remove();
    			}
    		}
    	}
    

      上面代码中的第5步中的register方法是用于将Channel通道注册到Selector选择器上,其中的参数有:

    1.Selector 选择器对象
    2.SelectionKey 选择注册的事件

    看一下register方法的源码:

    在这里插入图片描述
    其中第二个参数ops源码中注明了来源于SelectionKey类。该类中有以下4个常量:

    1.表示读事件

    public static final int OP_READ = 1 << 0;
    

    2.表示写事件

    public static final int OP_WRITE = 1 << 2;
    

    3.表示连接事件

    public static final int OP_CONNECT = 1 << 3;
    

    4.表示接收事件

    public static final int OP_ACCEPT = 1 << 4;
    

    也就是说,register(Selector sel, int ops)方法中的第二个参数ops应该是来自于SelectionKey类中的4个常量中的其中一个,用来表示该通道在选择器上注册的是什么事件。

  • 相关阅读:
    Delphi XE2 update4 很快就要来了
    树型菜单表的合并。
    VS宏 之 选中解决方案中的文件
    Mvc,接收复杂对象。
    SQL 2008 CLR开发自定义聚合函数
    数据库主键按业务规则生成的解决方案。
    一些独特的语言思考
    vs环境设置
    SqlServer 2005+ 开发问题
    记录 VS 中的生成时间
  • 原文地址:https://www.cnblogs.com/wgty/p/12810463.html
Copyright © 2020-2023  润新知