remaining
limit 减去 position的值
2.2 Selector(选择器)
Java NIO引入了选择器的概念,选择器用于监听多个通道的事件 。单个的线程可以监听多个数据通道 。要使用Selector,得向Selector注册Channel,然后调用它的select方法 。这个方法会一直阻塞到某个注册的通道有事件就绪 。一旦这个方法返回,线程就可以处理这些事件 。
文章插图
线程使用一个selector处理多个channel
代码3:
channel.configureBlocking( false); SelectionKey key = channel. register(selector,Selectionkey.OP_READ);注意register方法的第二个参数,这是一个监听的集合,即在通过Selector监听Channel时关注什么事件集合 。
SelectionKey包含:
1) interest集合:selectionKey.interestOps 可以监听四种不同类型的事件:OP_ACCEPT、OP_CONNECT、OP_WRITE、OP_READ
2) ready集合:selectionKey.readyOps; ready 集合是通道已经准备就绪的操作的集合,提供4个方便的方法:
- selectionKey.isAcceptable;
- selectionKey.isConnectable;
- selectionKey.isReadable;
- selectionKey.isWritable;
4) Selector:selectionKey.selector;
5) 可选的附加对象:
提示:
OP_ACCEPT和OP_CONNECT的区别:简单来说,客户端建立连接是connect,服务器准备接收连接是accept 。一个典型的客户端服务器网络交互流程如下图
文章插图
selectedKeys
一旦调用了select方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys方法,访问已选择键集(selected key set)中的就绪通道 。
wakeUp
某个线程调用select方法后阻塞了,即使没有通道已经就绪,也有办法让其从select方法返回 。只要让其它线程在阻塞线程调用select方法的对象上调用Selector.wakeup方法即可 。阻塞在select方法上的线程会立马返回 。如果有其它线程调用了wakeup方法,但当前没有线程阻塞在select方法上,下个调用select方法的线程会立即wake up 。
close
用完Selector后调用其close方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效 。通道本身并不会关闭 。
通过Selector选择通道:
- int select 阻塞直到至少有一个通道在你注册的事件上就绪了
- int select(long timeout) 增加最长阻塞毫秒数
- int selectNow 不会阻塞,不管什么通道就绪都立刻返回
了解完 Java NIO的原理,我们来看看Tars是如何使用NIO进行网络编程的 。
文章插图
Tars的网络模型是多reactor多线程模型 。有一点特殊的是tars的reactor线程组里随机选一个线程处理网络事件,并且该线程同时也能处理读写 。
核心类之间的关系如下:
文章插图
3.1 一个典型的Java NIO服务端开发流程
- 创建ServerSocketChannel,设置为非阻塞,并绑定端口
- 创建Selector对象
- 给ServerSocketChannel注册SelectionKey.OP_ACCEPT事件
- 启动一个线程循环,调用Selector的select方法来检查IO就绪事件,一旦有IO就绪事件,就通知用户线程去处理IO事件
- 如果有Accept事件,就创建一个SocketChannel,并注册SelectionKey.OP_READ
- 如果有读事件,判断一下是否全包,如果全包,就交给后端线程处理
- 写事件比较特殊 。isWriteable表示的是本机的写缓冲区是否可写 。这个在绝大多少情况下都是为真的 。在Netty中只有写半包的时候才需要注册写事件,如果一次写就完全把数据写入了缓冲区就不需要注册写事件 。
- Communicator.stringToProxy 根据servantName等配置信息创建通信器 。
- ServantProxyFactory.getServantProxy 调用工厂方法创建servant代理 。
- ObjectProxyFactory.getObjectProxy 调用工厂方法创建obj代理 。
- TarsProtocolInvoker.create 创建协议调用者 。
- ServantProtocolInvoker.initClient(Url url) 根据servantProxyConfig中的配置信息找到servant的ip端口等进行初始化ServantClient 。
- ClientPoolManager.getSelectorManager 如果第一次调用selectorManager是空的就会去初始化selectorManager 。
- reactorSet = new Reactor[selectorPoolSize]; SelectorManager初始化构造类中的会根据selectorPoolSize(默认是2)的配置创建Reactor线程数组 。线程名称的前缀是servant-proxy-加上CommunicatorId,CommunicatorId生成规则是由locator的地址生成的UUID 。
推荐阅读
- 长线牛股指标源码 长线牛股
- MySQL 驱动中虚引用 GC 耗时优化与源码分析
- 带你读 MySQL 源码:Where 条件怎么过滤记录?
- 谷歌开源 Rust Crate 审查结果:便于 Rust 开发者验证源码安全
- Spring/SpringBoot中的声明式事务和编程式事务源码、区别、优缺点、适用场景、实战
- 从Java源码来看Native命令执行方法
- 带你读 MySQL 源码:Select *
- 深扒RocketMQ源码之后,我找出了RocketMQ消息重复消费的7种原因
- 一文看懂Java中的ThreadLocal源码和注意事项
- 一文看懂Redisson分布式锁的Watchdog机制源码实现