我是靠谱客的博主 结实铅笔,最近开发中收集的这篇文章主要介绍Java NIO SocketChannel的使用Channel的注册,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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的注册所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(71)

评论列表共有 0 条评论

立即
投稿
返回
顶部