当前位置: 首页 > 工具软件 > open-selector > 使用案例 >

netty-selector

丁勇
2023-12-01

管理逻辑

所有的channel通过regiest交给selector进行管理。

但是所有的selector并不是直接对channel进行管理,而是通过创建selectionKeychannel进行管理。

也就是这样

regiest
manage
manage
selector
channel
selectionKey

同时,注册时还需要声明类型,也就是说生成的selectionKey中还有类型的划分,也就是如此

SelectionKey
selectionType
channel
selector

关注状态

我们regiest注册之后,创建的selectionKey可以分作三种状态

listen:注册监听

trigger:监听触发

cancel:取消监听

也就是说,selector下管理的selectionKey分作了三个集合,然后之下是具体的selectionKey

SelectionKey
SelectionKeySet
eventType
channel
listen
trigger
cancel
selector

其中三种集合的状态关系如下

trigger
cancel
cancel
listen
trigger
cancel

listenregiest自动添加,cancel操作后移除,添加至cancel队列。

trigger:当事件触发之后,系统自动从listen添加到trigger,不可手动添加。

cancelcancel取消关注之后从trigger移动到cancel

自动变换

listentrigger的变换,是系统自动通知变换的。

这种事件通知是底层自动的,我们只需要处理通知的元素即可。

手动变换

  1. 当事件不需要关注的时候,需要手动去cancal
  2. trigger事件处理完毕之后,需要进行remove,否则事件会重复触发,甚至报错。

关注类型

typepointdescription
OP_READserver
client
读取事件
OP_WRITEserver
client
写入事件
OP_CONNECTclient连接事件
客户端独有
OP_ACCEPTserver接收事件
服务端独有

阻塞操作

select:一直阻塞,直到触发

selectNow:不阻塞,直接执行

select(timeout):阻塞timeout

一般常用select

代码示例

public class NioMain {
    public static void main(String[] args) throws IOException {

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress("localhost", 8989));
        Selector selector = Selector.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while(true){
            selector.select();
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            selectedKeys.forEach(selectedKey->{
               try{
                  if(selectedKey.isAcceptable()){
                      ServerSocketChannel server = (ServerSocketChannel) selectedKey.channel();
                      SocketChannel socketChannel = server.accept();
                      socketChannel.configureBlocking(false);
                      socketChannel.register(selector, SelectionKey.OP_READ);
                  }else if(selectedKey.isReadable()){
                      SocketChannel client = (SocketChannel) selectedKey.channel();
                      ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                      client.configureBlocking(false);
                      client.read(byteBuffer);
                      byteBuffer.flip();
                      client.write(byteBuffer);
                  }
               }catch (Exception e){
                   e.printStackTrace();
               }
            });
            selectedKeys.clear();
        }
    }
}

异步配置

serverSocketChannel.configureBlocking(false);

使用selector需要异步模式,模式不匹配会报错。

通道注册

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

通道主动注册,传入seletoreventType

阻塞

selector.select();

直到eventType触发,然后会自动把触发的selectionKey添加到trigger集合。

通过处理trigger集合完成目的。

集合获取

Set<SelectionKey> selectedKeys = selector.selectedKeys();

代码中使用的是clear对事件后续进行清理。

如果使用iterator进行处理,记得remove

trigger事件队列不会自动清理,处理完毕的事件需要手动移除,否则会重复触发,甚至报错。

类型判断

selectedKey.isAcceptable()
functiontype
isAcceptableOP_ACCEPT
isReadableOP_READ
isConnectableOP_CONNECT
isWritableOP_WRITE

通过事件选择性处理。

通道获取

SocketChannel client = (SocketChannel) selectedKey.channel();

通过selectedKey进行通道获取,然后进行读取或写入操作。

再次监听

socketChannel.register(selector, SelectionKey.OP_READ);

一般情况下,我们很少不再关注cancel,但是很多时候会新增关注regiest

while中,我们可以动态的增加关注的channel,后续增加的关注通道,仍然采取同样的管理办法。

不过值得注意的是。获取channel时需要判断类型,否则会强转失败。

结构对比

public class BioMain {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(8989));
        while(true){
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();
            int readLength = -1;
            byte[] bs = new byte[1024];
            while((readLength = inputStream.read(bs))!=-1){
                outputStream.write(bs, 0 , readLength);
            }
            inputStream.close();
            outputStream.close();
        }
    }
}

上面的是BIO的代码结构,看起来好像更简便。

NIO多个相比较,好像BIO比较单一来着,这算是直观感受和细节屏蔽给我们的错误视觉吧。

BIO
NIO
thread
request1
request2
requestn
requestQueue.foreach
request1
request2
requestn
request1
request2
requestn
client1
client2
clientn
server
thread1
thread2
threadn
client1
Client2
clientn
server
step1
step2
step3

最直白的,就是连接的个数了。

BIO:看似采用同一套逻辑,但是瞒着我们开了好多线程

NIO:看起来一大堆东西,过于繁杂,但是除了事件通知,仅此而已。

我们在NiO中挨个数channel,这不是更能说明多个连接使用的是同一个thread么。

BIO的理解

每个连接一条线程,过于浪费资源。

各种事件之间纯粹阻塞,不够及时,浪费资源,响应也慢。

但是各自占用一条线程,处理能力强。

总结

除去底层事件通知的短板,在高并发链接上弱势,在多内容传输上有一定优势。

适合少链接,大量处理场合。

NIO的理解

多链接共用单线程,节省线程开销,但是限制处理能力。

事件通知提高性能,快速感知状态变化。

总结

适合高并发链接,能够处理多变状态,快速反应和处理。

但是单线程限制处理能力,当处理复杂内容时,单个耗时在串行处理过程中阻塞明显,降低效率。

小结

Buffer对数据进行包装

Channel对数据进行传输

selector就是对channel进行管理

通过对channel状态切换的管理,连接上处理操作。

IO操作不再是一昧的死等,让每一步都更有意义

code here

 类似资料: