小白也可以学会的Java NIO的Write事件

 更新时间:2021年06月01日 14:17:11   作者:JavaEdge.  
刚开始对NIO的写操作理解的不深,不知道为什么要注册写事件,何时注册写事件,为什么写完之后要取消注册写事件,今天特地整理了本篇文章,需要的朋友可以参考下

一、NIO Server端

1.1 多路复用开发一般步骤

//打开选择器
Selector selector = Selector.open();
//打开通到
ServerSocketChannel socketChannel = ServerSocketChannel.open();
//配置非阻塞模型
socketChannel.configureBlocking(false);
//绑定端口
socketChannel.bind(new InetSocketAddress(8080));
//注册事件,OP_ACCEPT只适用于ServerSocketChannel 
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
    selector.select();
    Set<SelectionKey> selectionKeys = selector.selectedKeys();
    Iterator<SelectionKey> iter = selectionKeys.iterator();
    while(iter.hasNext()) {
        SelectionKey key = iter.next();
        if(key.isAcceptable()) {
            SocketChannel channel = ((ServerSocketChannel)key.channel()).accept();
            channel.configureBlocking(false);
            channel.register(selector,SelectionKey.OP_READ);
        }
        
        if(key.isWritable()) {
        }
        
        if(key.isReadable()) {
            SocketChannel channel = (SocketChannel) key.channel();
            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
            channel.read(readBuffer);
            readBuffer.flip();
            // handler Buffer
            // 一般是响应客户端的数据
            // 直接是write写不就完事了嘛,为啥需要write事件?
            // channel.write(...)
        }
        iter.remove();
    }
}

1.2 解惑写事件

对NIO的写操作:

  • 为什么要注册写事件
  • 何时注册写事件
  • 为什么写完之后要取消注册写事件

如果有channel在Selector上注册了SelectionKey.OP_WRITE,在调用selector.select();时,系统会检查内核写缓冲区是否可写:

  • 如果可写,selector.select();立即返回,进入key.isWritable()
  • 何时不可写?比如缓冲区已满,channel调用了shutdownOutPut等

当然除了注册写事件,你也可以在channel直接调用write(…),也可以将数据发出去,但这样不够灵活,而且可能浪费CPU。

比如服务端需要发送一个200M的Buffer,看看是否使用OP_WRITE事件的区别。

二、不使用事件

程序运行到这会等到200M文件发送完成后才继续往下执行,不符合异步事件模型的思想。若缓冲区一直处不可写状态,则该过程一直在这里死循环,浪费CPU。

// 200M的Buffer
ByteBuffer buffer = .... 

while(buffer.hasRemaining()) {
    // 该方法只会写入小于socket's output buffer空闲区域的任何字节数
    // 并返回写入的字节数,可能是0字节
    channel.write(buffer);
}

三、使用事件

if(key.isReadable()) {
	// 200M Buffer
    ByteBuffer buffer = .... 
    // 注册写事件
    key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
    // 绑定Buffer
    key.attach(buffer);
}
// 可写分支
if(key.isWritable()) {
    ByteBuffer buffer = (ByteBuffer) key.attachment();
    SocketChannel channel = (SocketChannel) key.channel();
    if (buffer.hasRemaining()) {
        channel.write(buffer)
    } else {
        // 发送完了就取消写事件,否则下次还会进入写事件分支(因为只要还可写,就会进入)
        key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
    }
}

要触发写事件,需要先向 selector 注册该通道的写事件,跟注册读事件一样,当底层写缓冲区有空闲就会触发写事件了,而一般来说底层的写缓冲区大部分都是空闲的。所以一般只要注册了写事件,就会立马触发了,为了避免 cpu 空转,在写操作完成后需要把写事件取消掉,然后下次再有写操作时重新注册写事件。

四、NIO Client端

开发的一般步骤

// 打开选择器
Selector selector = Selector.open();
// 打开通道
SocketChannel socketChannel = SocketChannel.open();
// 配置非阻塞模型
socketChannel.configureBlocking(false);
// 连接Server
socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));
// 注册事件
socketChannel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ);
// 循环处理
while (true) {
    selector.select();
    Set<SelectionKey> keys = selector.selectedKeys();
    Iterator<SelectionKey> iter = keys.iterator();
    while(iter.hasNext()) {
        SelectionKey key = iter.next();
        if(key.isConnectable()) {
            // 连接建立或者连接建立不成功
            SocketChannel channel = (SocketChannel) key.channel();
            // 完成连接建立
            if(channel.finishConnect()) {
                
            }
        }
        
        if(key.isReadable()) {
            SocketChannel channel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(500 * 1024 * 1024);
            buffer.clear();
            channel.read(buffer);
            // buffer Handler
        }
        iter.remove();
    }
}

起初对OP_CONNECT事件还有finishConnect不理解,OP_CONNECT事件何时触发,特别是为什么要在key.isConnectable()分支里调用finishConnect方法后才能进行读写操作。

首先,在non-blocking模式下调用socketChannel.connect(new InetSocketAddress(“127.0.0.1”,8080));连接远程主机,如果连接能立即建立就像本地连接一样,该方法会立即返回true,否则该方法会立即返回false,然后系统底层进行三次握手建立连接。连接有两种结果,一种是成功连接,第二种是异常,但是connect方法已经返回,无法通过该方法的返回值或者是异常来通知用户程序建立连接的情况,所以由OP_CONNECT事件和finishConnect方法来通知用户程序。不管系统底层三次连接是否成功,selector都会被唤醒继而触发OP_CONNECT事件,如果握手成功,并且该连接未被其他线程关闭,finishConnect会返回true,然后就可以顺利的进行channle读写。如果网络故障,或者远程主机故障,握手不成功,用户程序可以通过finishConnect方法获得底层的异常通知,进而处理异常。

到此这篇关于小白也可以学会的Java NIO的Write事件的文章就介绍到这了,更多相关Java NIO的Write事件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • springboot实现获取当前服务器IP及当前项目使用的端口号Port

    springboot实现获取当前服务器IP及当前项目使用的端口号Port

    这篇文章主要介绍了springboot实现获取当前服务器IP及当前项目使用的端口号Port方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • 深入理解hibernate的三种状态

    深入理解hibernate的三种状态

    本篇文章主要介绍了深入理解hibernate的三种状态 ,主要包括了transient(瞬时状态),persistent(持久化状态)以及detached(离线状态),有兴趣的同学可以了解一下
    2017-05-05
  • SpringBoot项目如何访问jsp页面的示例代码

    SpringBoot项目如何访问jsp页面的示例代码

    本篇文章主要介绍了SpringBoot项目如何访问jsp页面的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • 浅谈Maven的安装及修改为阿里云下载依赖

    浅谈Maven的安装及修改为阿里云下载依赖

    下面小编就为大家带来一篇浅谈Maven的安装及修改为阿里云下载依赖。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • ibatis迁移到mybatis3的注意事项

    ibatis迁移到mybatis3的注意事项

    这篇文章主要介绍了ibatis迁移到mybatis3的注意事项的相关资料,需要的朋友可以参考下
    2017-10-10
  • 浅析JAVA常用JDBC连接数据库的方法总结

    浅析JAVA常用JDBC连接数据库的方法总结

    本篇文章是对在JAVA中常用JDBC连接数据库的方法进行了详细的总结分析,需要的朋友参考下
    2013-07-07
  • Java中值类型和引用类型详解

    Java中值类型和引用类型详解

    大家好,本篇文章主要讲的是Java中值类型和引用类型详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2022-01-01
  • JAVA将中文转换为拼音简单实现方法

    JAVA将中文转换为拼音简单实现方法

    拼音转换是中文处理的常见需求,TinyPinyin、HanLP、pinyin4j是常用的本地拼音转换库,各有特点,开发者可根据具体需求选择合适的拼音转换工具,需要的朋友可以参考下
    2024-10-10
  • Spring Boot 开发环境热部署详细教程

    Spring Boot 开发环境热部署详细教程

    这篇文章主要介绍了Spring Boot 开发环境热部署,本文给大家介绍了Spring Boot 开发环境热部署的原理及快速配置方法,通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • SpringSecurity导致SpringBoot跨域失效的问题解决

    SpringSecurity导致SpringBoot跨域失效的问题解决

    本文主要介绍了SpringSecurity导致SpringBoot跨域失效的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01

最新评论