Java NIO SocketChannel的使用

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中移除

### 回答1: NioSocketChannel类本身并没有实现Serializable接口,也就是说它本身不能被序列化。如果你想将NioSocketChannel对象序列化,你需要自己定义一个类来实现Serializable接口,并将NioSocketChannel对象作为该类的一个成员变量。然后你就可以将这个自定义类的对象序列化了。 具体实现方式可以参考如下代码: ``` import java.io.Serializable; public class MyClass implements Serializable { private NioSocketChannel socketChannel; public MyClass(NioSocketChannel socketChannel) { this.socketChannel = socketChannel; } public NioSocketChannel getSocketChannel() { return socketChannel; } public void setSocketChannel(NioSocketChannel socketChannel) { this.socketChannel = socketChannel; } } ``` 这样你就可以通过如下代码将MyClass对象序列化: ``` MyClass myObject = new MyClass(socketChannel); // 将myObject对象序列化到文件中 FileOutputStream fos = new FileOutputStream("myObject.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(myObject); oos.close(); ``` ### 回答2: Netty的NioSocketChannel对象是一个网络套接字通道,用于在Java NIO(非阻塞I/O)中进行网络通信。NioSocketChannel类本身并没有直接实现Serializable接口,因为它不是一个可序列化的对象。 Serializable接口是一个标记接口,用于指示一个类可以被序列化,即可以将其转换为字节流以便在网络中传输或持久化到存储设备中。可序列化对象需要实现Serializable接口,并提供相应的序列化和反序列化方法。 然而,NioSocketChannel是Netty的抽象类实现之一,它的子类AbstractChannel已经实现了Serializable接口。可以通过自定义一个类继承NioSocketChannel,并实现Serializable接口来实现将NioSocketChannel对象序列化。 以下是一个可能的示例代码: ```java import io.netty.channel.socket.nio.NioSocketChannel; import java.io.Serializable; public class SerializableNioSocketChannel extends NioSocketChannel implements Serializable { // 添加一个默认的构造函数 public SerializableNioSocketChannel() { super(); } } ``` 在上述示例中,我们创建了一个新的类SerializableNioSocketChannel,该类继承了NioSocketChannel并实现了Serializable接口。通过将NioSocketChannel对象转换为SerializableNioSocketChannel对象,就可以实现对NioSocketChannel对象的序列化。 需要注意的是,由于NioSocketChannel作为网络套接字通道,它包含了底层网络连接状态等非序列化的信息,因此在实际使用中,可能需要根据具体需求选择序列化对象中需要保留的属性。 ### 回答3: Netty的NioSocketChannel对象不能直接实现Serializable接口,因为NioSocketChannel类并没有实现Serializable接口。Serializable接口是Java提供的一种机制,用于将对象转换为字节流,以便能够在网络中传输或持久化到磁盘。 然而,我们可以通过一些其他方式来实现将NioSocketChannel对象序列化和反序列化。比如,可以通过将NioSocketChannel的一些关键信息提取出来,例如远程地址、本地地址、管道选项等,并将这些信息进行序列化和反序列化。 具体实现方式如下: 1. 创建一个包含NioSocketChannel关键信息的可序列化类,例如NioSocketChannelWrapper。 2. 在NioSocketChannelWrapper中定义所需的关键信息字段,例如remoteAddress、localAddress、channelOptions等。 3. 实现NioSocketChannelWrapper类的序列化接口Serializable,并添加序列化和反序列化方法。 4. 在需要将NioSocketChannel对象序列化时,通过构建NioSocketChannelWrapper对象,并将关键信息赋值给NioSocketChannelWrapper的相应字段。 5. 将NioSocketChannelWrapper对象进行序列化处理,并传输到目标。 6. 在需要反序列化NioSocketChannel对象时,接收到NioSocketChannelWrapper对象后,进行反序列化操作,将关键信息提取出来。 7. 通过Netty的Bootstrap或其他相关类,使用提取出的关键信息重新构建NioSocketChannel对象。 需要注意的是,反序列化后的NioSocketChannel对象并不是原始NioSocketChannel对象的完全克隆,而是通过关键信息重新构建的新对象。一些与网络连接、管道状态等相关的信息可能无法被序列化和恢复。 总之,Netty的NioSocketChannel对象不能直接实现Serializable接口,但通过提取关键信息并自定义可序列化类,我们可以实现将NioSocketChannel对象进行序列化和反序列化操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值