java nio Socket网络编程-Selector 作者:马育民 • 2023-02-02 09:17 • 阅读:10036 # 介绍 [![](/upload/0/0/1IX4tFTOe0R2.png)](/upload/0/0/1IX4tFTOe0R2.png) nio 网络编程,需要用 `Selector`,相当于 监听器,可以 **监听 `channel` 通道 的 某个事件** **提示:** 在 nio 中,客户端与服务器端的连接,称之为:`channel` 通道 ## 作用 [![](/upload/0/0/1IX4uhvXvhpV.png)](/upload/0/0/1IX4uhvXvhpV.png) 多个 `channel` 可以 **注册** 到同一个 `Selector`,让 `Selector` 监听指定的 **事件**,当某个通道有事件发生时,可获取事件并做相应处理。 如:当某个通道有新数据时,才会进行读写 这样就可以用 **一个 `Selector` **,**管理多个** `channel`,即:处理多个客户端连接 ### 非阻塞通道 上图中的 `channel` 通道是 **必须是 非阻塞的**,不会像 bio 那样,一直阻塞,而是有数据时才进行读写,没有数据时,线程可以进行其他任务(因为不会阻塞)。 **理解:**相当于渣男渣女,当正牌对象在外地时,不会一直等待(没有数据,不会阻塞),而是跟别人处对象(处理其他任务)。一个人可以跟多人处对象,效率高 ## 优点 只需更少线程就可以处理多个 `channel`(jdk的源码中,每注册 `1023` 个通道,`selector` 会创建新的线程) # 创建对象 ``` Selector selector = Selector.open() ``` # 注册 Channel 为了将 `channel` 和 `Selector` 搭配使用,必须将 `channel` 注册到 `selector` ``` serverChannel.register(selector, SelectionKey.OP_ACCEPT); ``` `SelectionKey.OP_ACCEPT` 表示事件,封装在 [SelectionKey](https://www.malaoshi.top/show_1IX4tF97HfrK.html "SelectionKey") 类中 **注意:** `channel` 必须是非阻塞模式,通过下面代码设置: ``` channel.configureBlocking(false); ``` # 选取通道 一旦向 `Selector` 注册了一或多通道,就可调用 `select()` 方法,返回 某事件(如:连接、接受、读或写)已经准备就绪的那些通道 ## select() 阻塞,直到至少有一个channel在符合注册的事件 ``` selector.select(); ``` **返回值:**返回值不是已就绪的channel的总数,而是从上一个`select()` 调用之后,进入就绪态的channel的数量。 之前的调用中就绪的,并在本次调用中仍就绪的channel不会被计入。 在前一次调用中已就绪,但已不再处于就绪态的通道也不会计入。这些channel可能仍然在SelectionKey集合中,但不会被计入返回值中 所以返回值可能是 `0` ## select(long timeout) 和 `select()` 一样,只是规定了最长会阻塞 `timeout` 毫秒(参数) ## selectNow() 不会阻塞,立刻返回。若自从上一次选择操作后,没有channel可选择,则此方法直接返回 `0` # selectedKeys() 一旦调用 `select()` 系列方法,并且返回值 **大于0**,表明有一个或更多个通道就绪了,然后可以通过下面代码,获取就绪通道: ``` Set selectionKeys = this.selector.selectedKeys(); ``` 通常通过下面方式进行遍历: ``` Set selectionKeys = this.selector.selectedKeys(); Iterator ite = selectionKeys.iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next();' // 通过强转成 ServerSocketChannel 通道,可能报错,具体与操作相关 // ServerSocketChannel server = (ServerSocketChannel) key.channel(); // 通过强转成 SocketChannel 通道,可能报错,具体与操作相关 // SocketChannel channel = (SocketChannel) key.channel(); } ``` # 关键代码例子 ``` // 轮询访问selector while (true) { //当注册的事件到达时,方法返回;否则,该方法会一直阻塞 int select = selector.select(2000); if(select == 0){ continue; } // 获得selector中选中的项的迭代器,选中的项为注册的事件 Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 删除已选的key,以防重复处理 ite.remove(); if (key.isAcceptable()) { // 接受客户端连接就绪 ServerSocketChannel server = (ServerSocketChannel) key.channel(); } else if (key.isConnectable()) { // 连接准备就绪 } else if (key.isReadable()) { // 读就绪 SocketChannel channel = (SocketChannel) key.channel(); } else if (key.isWritable()) { // 写就绪 SocketChannel channel = (SocketChannel) key.channel(); } } } ``` 参考: https://zhuanlan.zhihu.com/p/376074271 https://blog.csdn.net/weixin_42762133/article/details/100040141 原文出处:http://malaoshi.top/show_1IX4tFP1gIOb.html