《网络概述》与 《网络IO模型》 二文中介绍了,NIO与Select模型的概念。接下来看看open jdk中select模型怎么实现的。
JAVA NIO 例子
上图是现在最常见的NIO写法
- ServerSocketChannel.open() 打开一个连接
- serverChannel.configureBlocking(false); 把连接设置为阻塞
- bind(new InetSocketAddress(host, port)); 把连接绑定到端口
- Selector.open(); 选择器select创建
- serverChannel.register(selector,SelectionKey.OP_ACCEPT); 把连接的接收事件注册进选择器,如果连接非阻塞,则注册失败。
- selector.select(); 选择器阻塞直到事件发生
- selector.selectedKeys() 取出所有发生的事件
- key.isAcceptable() 可接收事件发生 ,从ServerSocketChannel取出新连接,然后把连接设置为非阻塞并注册监听可读事伯’
- key.isReadable()可读事件发生
步骤解析
1-3步骤为一个很普通的打开连接,设置为非阻塞,并且绑定端口
4步骤打开选择器,实际实现类为WindowsSelectorImpl
- Pipe.open()打开一个管道
- 拿到wakeupSourceFd和wakeupSinkFd两个文件描述符;
- 把唤醒端的文件描述符(wakeupSourceFd)放到pollWrapper,
PollArrayWrapper作用即存放选择key和选择key关注的事件,
5步骤,主要干两件事情
1.把自己注册进Selector
最终信息加入到pollWrapper的末尾
2.自己本地保留关注事件的引用。
(PS:这连接可以把关注信息注册到不同的Select)
6步骤,主要干两件事情
1.用线程池subSelect从pollWrapper拉取所关注的事件
2.processSelectedKeys方法主要是更新关注读写事件的选择key的相关通道的已经就绪的操作事件集
然后通过7步骤,拉取已到关注状态的事件。
通过8,9加入新连接的关注状及读写
前文总结分析有2个问题
1.Pipe.open()打开一个管道,把唤醒端的文件描述符(wakeupSourceFd)放到pollWrapper,有什么用
2.doSelect是poll时,如果一直没有新连接过来,难道要一直等到timeout吗,如果在timeout结束前要主动发送消息怎么办。
(poll等待时间过长将会让消息不能即时被处理,而过短又会占用过多cpu费电)
巧的是问题一的wakeupSourceFd,刚好是用来解决2的。
大家把管道的读取端fd放入selector,那么在wait的时候这个读取端管道fd也会一起参与wait,那么用户线程往队列里塞完任务后,马上往管道的写端写入一个字节,这样poll就有新连接过来,就可以把网络线程唤醒了.
这个方法是用来解决多个reactor之间互相唤醒的问题的,利用该技巧可以让网络线程即时处理网络事件的同时也能即时处理来自非网络(比如内部消息队列)的其它消息。
总关系图如图所示:
另注,《网络概述》 一文中管道技术是要操作系统支持,而window是不支持的,那OpenJdk中windows 下 Pipe.open怎么实现
1.新创建一ServerSockectChannel
2.创建一SocketChannel(sc1)去请求ServerSockectChannel
3.从ServerSockectChannel拿出SocketChannel (sc2)连接着(sc1)
4.从sc1发送一数据过去
5.从sc2接收数据并认证,成功链路建立好了。
6.关闭ServerSockectChannel
7.sc1到sc2的管道建好了可以实现通信了
这正是TCP打洞技术的真实案例