NIO的写操作分析

很多人对NIO的写操作理解的不深,不知道为什么要注册写事件,何时注册写事件,为什么写完之后要取消注册写事件。

Selector.isWriteable()方法监控的是内核的写缓冲器是否可写,所以除非内核的写缓冲区满了,否则一旦一个SocketChannel注册了写事件,每次selector都会选中这个SocketChannel,进入到isWriteable()分支中去。


实际上服务器端写消息时完全可以不注册写事件,直接调用SocketChannel.write(ByteBuffer),也能把数据写到缓冲区并发送出去。但是这种情况下selector不会选择到isWriteable()分支。在写缓冲区满的情况下处理也不灵活,需要while(ByteBuffer.hasRemain())来检查写的状态,会导致CPU空转while.

更好的做法是注册写事件。注册写事件也有两种做法,一种是SocketChannel.register(selector, SelectionKey.OP_WRITE),直接注册到SocketChannel。第二种是用SelectionKey.interestOps(SelectionKey.interestOps() | SelectionKey.OP_WRITE),用SelectionKey来注册。每个SelectionKey只会关联一个SocketChannel,相当于注册到了SocketChannel上。

所以NIO写方法的一般做法是:
1. 注册写事件
2. 写数据
3. 取消注册写事件

注意第3步,每次写完之后要取消注册写事件,SelectionKey.interestOps(SelectionKey.interestOps() & ~SelectionKey.OP_WRITE),否则只要内核写缓冲区可写,这个SocketChannel就会一直被selector选中。

由于有两种注册写事件的方式,所以导致写数据也有两种做法,如果用SocketChannel来注册写事件,那么直接用SocketChannel来写ByteBuffer。
更好的一种做法是第二种,将业务处理和底层Socket操作隔离,采用SelectionKey的Attachment,把业务数据用Attachment的方式和SocketChannel绑定。在SocketChannel注册事件时可以添加Attachment,比如SocketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));

业务方法只需要操作Attachment,往Attachment里面写,然后在Selector.isWriteable()分支里面取出写好的Attachment,然后调用SocketChannel.Write(ByteBuffer) 方法来把数据写到Channel。

大部分的框架都采用第二种方式,比如Thrift的TNonblockingServer处理写事件时,采用一个业务类FrameBuffer来封装业务数据的读写,把FrameBuffer作为Attachment绑定到SocketChannel,并用FrameBuffer记录读写的状态。这样业务功能只需要处理FrameBuffer,
TNonblockingServer来处理SocketChannel实际的写操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值