欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java学习历程:NIO为什么SelectionKey在被轮询后需要remove()

程序员文章站 2022-03-02 15:27:30
...

学习NIO的过程中,对selector选择器的知识产生了兴趣,尤其是关于SelectionKey的轮询后remove()的问题,博主尝试简单地解释一下NIO如何实现非阻塞的。

首先是客户端的代码:

public void testNonBlockingNIOClient() throws IOException{
		//客户端
		//1.获取通道
		SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9899));
		
		//2.切换成非阻塞模式
		sChannel.configureBlocking(false);//key
		
		//3.缓冲区
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		//4.读文件发送至服务端
		buf.put(new Date().toString().getBytes());
		buf.flip();
		sChannel.write(buf);
		buf.clear();
		
		sChannel.close();
		
	}
	

再者是服务端代码:

public void testNonBlockingNIOServer() throws IOException{
		//服务端
		//1.获取通道
		ServerSocketChannel ssChannel = ServerSocketChannel.open();
		
		//2.切换成非阻塞模式
		ssChannel.configureBlocking(false);
		
		//3.绑定连接
		ssChannel.bind(new InetSocketAddress(9899));
		
		//4.获取选择器selector
		Selector selector = Selector.open();
		
		//5.将通道注册到选择器上
		ssChannel.register(selector, SelectionKey.OP_ACCEPT);//ops选择键,监控通道什么状态
		//return SelectionKey
		                                  //1 读        OP_READ
		                                  //4 写        OP_WRITE
		                                  //8 连接    OP_CONNECT
		                                  //16接受  OP_ACCEPT
		                                  //|连接符监控多个
		//6.轮询式地获取选择器上准备就绪的事件
		while(selector.select()>0){
			//7.获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
			Iterator<SelectionKey> it = selector.selectedKeys().iterator();
			while(it.hasNext()){
				//8.获取准备就绪的事件
				SelectionKey sk = it.next();
				
				//9.判断具体是什么事件准备就绪
				if(sk.isAcceptable()){
					//若“接收就绪”
					SocketChannel SChannel = ssChannel.accept();
					//切换非阻塞模式
					SChannel.configureBlocking(false);
					
					SChannel.register(selector, SelectionKey.OP_READ);
				}else if(sk.isReadable()){
					//若读就绪
					SocketChannel SChannel = (SocketChannel) sk.channel();
					
					ByteBuffer buf = ByteBuffer.allocate(1024);
					
					int len = 0;
					while((len = SChannel.read(buf))>0){
						buf.flip();
						System.out.println(new String(buf.array(),0,len));
						buf.clear();
					}
				}
				
				//10.重点,取消选择键
				it.remove();
			}
		}
	}
		

可以发现服务端的最后进行了remove()操作,将SelectionKey从迭代器中删除了,博主一开始总觉得很纳闷,SelectionKey中可是记录了相关的channel信息,如果将SelectionKey删除了,那不就代表着将通道信息也抹除了吗,那后续还怎么继续获取通道,说来惭愧,这问题问的确实缺乏水准。

后来博主理了理selector的思路,要知道,一码事归一码事,channel是注册在selector中的,在后面的轮询中,是先将已准备好的channel挑选出来,即selector.select(),再通过selectedKeys()生成的一个SelectionKey迭代器进行轮询的,一次轮询会将这个迭代器中的每个SelectionKey都遍历一遍,每次访问后都remove()相应的SelectionKey,但是移除了selectedKeys中的SelectionKey不代表移除了selector中的channel信息(这点很重要),注册过的channel信息会以SelectionKey的形式存储在selector.keys()中,也就是说每次select()后的selectedKeys迭代器中是不能还有成员的,但keys()中的成员是不会被删除的(以此来记录channel信息)。

那么为什么要删除呢,要知道,迭代器如果只需要访问的话,直接访问就好了,完全没必要remove()其中的元素啊,查询了相关资料,一致的回答是为了防止重复处理(大雾),后来又有信息说明:每次循环调用remove()是因为selector不会自己从已选择集合中移除selectionKey实例,必须在处理完通道时自己移除,这样,在下次select时,会将这个就绪通道添加到已选择通道集合中,其实到这里就已经可以理解了,selector不会自己删除selectedKeys()集合中的selectionKey,那么如果不人工remove(),将导致下次select()的时候selectedKeys()中仍有上次轮询留下来的信息,这样必然会出现错误,假设这次轮询时该通道并没有准备好,却又由于上次轮询未被remove()的原因被认为已经准备好了,这样能不出错吗?

即selector.select()会将准备好的channel以SelectionKey的形式放置于selector的selectedKeys()*使用者迭代,使用的过程中需将selectedKeys清空,这样下次selector.select()时就不会出现错误了。

附上一个非常好的连接:https://blog.csdn.net/qq_32331073/article/details/81132937

相关标签: NIO