概述
Java NIO采用IO多路复用的IO模型,基本的IO模型有四种
- 阻塞IO。一直等待直到读到数据,一个线程只能处理一个IO通道
- 非阻塞IO。可以查询IO上是否有数据,查询方法会立即返回,一个线程能够通过轮询的方式处理多个IO通道,缺点也比较明显:轮询频率高浪费CPU,轮询频率低则响应时长增加
- IO多路复用。可以阻塞(或者非阻塞)询问哪些IO通道上有数据,一个线程可以处理多个IO通道,且不会浪费CPU
- 异步IO。当有数据可读时,由系统发起数据的处理,需要事先注册handler
IO多路复用模型下,主要有Channel、Buffer、Selector三个组件。
Channel的注册
使用NIO首先要将channel注册到selector上,只有SelectableChannel才能注册到selector上,主要会使用到的就是SocketChannel跟ServerSocketChannel
channel通过SelectableChannel#register方法注册到seletor上。简单翻一下注释说的重点
- register可以反复调用,如果channel注册过,那么会返回相同的SelectionKey,只是修改ops跟att,否则返回一个新的SelectionKey
- **这个方法会同步锁住selector的key set,意味着如果一个线程正在阻塞在selector#select方法上,那么register也会阻塞 **
/**
* Registers this channel with the given selector, returning a selection
* key.
*
* <p> If this channel is currently registered with the given selector then
* the selection key representing that registration is returned. The key's
* interest set will have been changed to <tt>ops</tt>, as if by invoking
* the {@link SelectionKey#interestOps(int) interestOps(int)} method. If
* the <tt>att</tt> argument is not <tt>null</tt> then the key's attachment
* will have been set to that value. A {@link CancelledKeyException} will
* be thrown if the key has already been cancelled.
*
* <p> Otherwise this channel has not yet been registered with the given
* selector, so it is registered and the resulting new key is returned.
* The key's initial interest set will be <tt>ops</tt> and its attachment
* will be <tt>att</tt>.
*
* <p> This method may be invoked at any time. If this method is invoked
* while another invocation of this method or of the {@link
* #configureBlocking(boolean) configureBlocking} method is in progress
* then it will first block until the other operation is complete. This
* method will then synchronize on the selector's key set and therefore may
* block if invoked concurrently with another registration or selection
* operation involving the same selector. </p>
*/
public abstract SelectionKey register(Selector sel, int ops, Object att)
throws ClosedChannelException;
注册之前需要将这个channel配置为非阻塞模式,也就是通过configureBlocking方法设置为false
public abstract SelectableChannel configureBlocking(boolean block)
register方法的ops表示感兴趣的事件,有四种
- OP_READ,表示读就绪,可以从socket里读取数据了
- OP_WRITE,表示写就绪,可以向soket里写数据了
- OP_CONNECT,表示连接就绪,也就是连接成功了
- OP_ACCECPT,仅对ServerSocket有效,表示有客户端请求连接
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
这里面OP_READ、OP_ACCEPT都好理解,因为都是由对方发起的,我方被动接收,所以需要注册感兴趣的事件,当事件就绪时调用对应的方法处理,也就是read或者accept。为什么需要注册OP_WRITE、OP_CONNECT事件呢?这不都是我方发起的吗?
OP_CONNECT
- 在阻塞模式下,SocketChannel#connect方法会等待IO的连接,直到连接成功(返回true)或者IO错误(抛出异常)
- 在非阻塞模式下,SocketChannel#connect初始化了一个连接操作,如果这个操作能够立即完成,那么会返回true,否则直接返回false。这时候需要将这个socket注册OP_CONNECT事件,通过selector得到OP_CONNECT就绪事件,通过finishConnect方法才能知道是连接成功(返回true)还是IO错误(抛出异常)
/**
* Connects this channel's socket.
*
* <p> If this channel is in non-blocking mode then an invocation of this
* method initiates a non-blocking connection operation. If the connection
* is established immediately, as can happen with a local connection, then
* this method returns <tt>true</tt>. Otherwise this method returns
* <tt>false</tt> and the connection operation must later be completed by
* invoking the {@link #finishConnect finishConnect} method.
*
* <p> If this channel is in blocking mode then an invocation of this
* method will block until the connection is established or an I/O error
* occurs.
*/
public abstract boolean connect(SocketAddress remote) throws IOException;
OP_WRITE
CPU的写速度必然比网络发送速度要快,为了平衡这种速度差,每个socket都有发送缓冲区,CPU将数据写到缓冲区再由网络发送出去。当发送的内容非常多以至于发送缓冲区容纳不下时,再往里写则数据会丢失。这种情况下就需要注册OP_WRITE事件,当写就绪(缓冲区可写)时,再往里写数据
监听Channel就绪事件
监听就绪的Channel主要就是两步
1、select、selectNow、带timeout的select获取就绪Channel个数
2、当就绪Channel个数不为0的时候,通过selectedKeys拿到Set
selectNow
这个方法会立即返回
select/带timeout的select,并不是只有等到channel就绪才返回,而是有几个条件满足一个就会返回
1、带timeout的select超时时间到达
2、有channel就绪
3、另一个线程调用selector#wakeup(selector#close等同于调用了selector#wakeup)
4、这个线程被中断了(方法会返回0,而不是抛出中断异常)
拿到selectedKeys集合,在处理每个channel时需要注意
1、把正在处理的SelectionKey从集合中移除
2、把正在处理的就绪事件也从interestOps中移除
最后
以上就是结实铅笔为你收集整理的Java NIO SocketChannel的使用Channel的注册的全部内容,希望文章能够帮你解决Java NIO SocketChannel的使用Channel的注册所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复