2021-05-28

本文详细解读了NIO中的Selector选择器和SelectionKey在非阻塞客户端/服务端通信中的关键作用,特别关注了remove()操作对于避免重复处理的重要性。通过实例演示和理论剖析,解释了为何在轮询后移除SelectionKey以及其背后的逻辑。
摘要由CSDN通过智能技术生成

学习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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值