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

一、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事件内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 一文学习Java NIO的ByteBuffer工作原理

    网络数据的基本单位永远是 byte(字节).Java NIO 提供 ByteBuffer 作为字节的容器,但该类过于复杂,有点难用. ByteBuf是Netty当中的最重要的工具类,它与JDK的ByteBuffer原理基本上相同,也分为堆内与堆外俩种类型,但是ByteBuf做了极大的优化,具有更简单的API,更多的工具方法和优秀的内存池设计. 1 API Netty 的数据处理 API 通过两个组件暴露--抽象类ByteBuf 和 接口 ByteBufHolder. ByteBuf API 的优

  • Java事件机制要素及实例详解

    java事件机制中包含下述三要素: 1.事件,发生了什么事,比如用户在界面上的一个操作(手势滑动屏幕),当一个事件发生的时候,该事件用一个事件对象表示,每一个事件对象都有其对应的事件类. Java中事件一般继承自java.util.EventObject类,封装了事件源对象,以及事件的相关信息. 每一类事件有一个相应的事件监听器接口,该接口定义了接收和处理事件的抽象方法.实现该接口的类,就是监听器类.其对象可作为监听器对象向相应的组件注册.事件的类名通常为:XxxEvent ,比如下面实例中的C

  • java实现点击按钮事件弹出子窗口

    本文实例为大家分享了java实现点击按钮事件弹出子窗口的具体代码,供大家参考,具体内容如下 要求: 1.在父窗口中添加一个按钮 2.点击按钮弹出子窗口 注意:这是JDK1.7版本 在JDK1.7之前,JFrame是不能直接添加子窗口的,要先将JInternalFrame添加到JDesktopPane中,再将JDesktopPane添加到父窗口内,完成这个操作. (一)建立父类JFrame package com.java.view; import java.awt.BorderLayout; i

  • 详解JAVA Spring 中的事件机制

    说到事件机制,可能脑海中最先浮现的就是日常使用的各种 listener,listener去监听事件源,如果被监听的事件有变化就会通知listener,从而针对变化做相应的动作.这些listener是怎么实现的呢?说listener之前,我们先从设计模式开始讲起. 观察者模式 观察者模式一般包含以下几个对象: Subject:被观察的对象.它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify().目标类可以是接口,也可以是抽象类或具体类. ConcreteSubject:具体的

  • Java事件处理步骤讲解

    什么是事件? 用户对组件的一个操作,称之为一个事件. 事件源:能够产生事件的GUI组件对象. 事件处理方法:能够接受,解析和处理事件类对象,实现与用户交互功能的方法. 事件监听器:可以处理事件的一个类. 处理事件步骤: 假设事件为XXXX 1.向事件源注册某种事件的事件监听器对象 addXXXXListener(...); 2.设计好可以处理这种事件的事件监听器 class 类名 implements XXXXListener{ 重写XXXXListener接口中的方法 } 说明: 要想设计出能

  • 基于Java写minio客户端实现上传下载文件

    前言: 确保已经安装了minio的服务端 代码: pom.xml <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>7.0.2</version> </dependency> application.yml server: port:90 minio: url: http://10.69.94.140

  • Java NIO无法绑定指定IP和端口解决方案

    在使用SNMP4J时,我想指定创建的客户端使用的本地IP和端口,因为在Socket时这是可以的,但是发现无法实现 因为SNMP4J底层的通信是使用NIO实现的,而NIO编程时貌似就不能显示的指定 例如在SNMP4J的DefaultTcpTransportMapping类里面,当作为客户端需要发送消息时,程序首先判断是否创建了这个客户端,如果没有在创建时看到这样的代码: SocketChannel sc = null; try { sc = SocketChannel.open(); sc.con

  • 详解java NIO之Channel(通道)

    通道(Channel)是java.nio的第二个主要创新.它们既不是一个扩展也不是一项增强,而是全新.极好的Java I/O示例,提供与I/O服务的直接连接.Channel用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据. channel介绍 通道是访问I/O服务的导管.I/O可以分为广义的两大类别:File I/O和Stream I/O.那么相应地有两种类型的通道也就不足为怪了,它们是文件(file)通道和套接字(socket)通道.我们看到在api里有一个F

  • Java NIO实战之聊天室功能详解

    本文实例讲述了Java NIO实战之聊天室功能.分享给大家供大家参考,具体如下: 在工作之余花了两个星期看完了<Java NIO>,总体来说这本书把NIO写的很详细,没有过多的废话,讲的都是重点,只是翻译的中文版看的确实吃力,英文水平太低也没办法,总算也坚持看完了.<Java NIO>这本书的重点在于第四章讲解的"选择器",要理解透还是要反复琢磨推敲:愚钝的我花了大概3天的时间才将NIO的选择器机制理解透并能较熟练的运用,于是便写了这个聊天室程序. 下面直接上代

  • 处理java异步事件的阻塞和非阻塞方法分析

    前言 由于多核系统普遍存在,并发性编程的应用无疑比以往任何时候都要广泛.但并发性很难正确实现,用户需要借助新工具来使用它.很多基于 JVM 的语言都属于这类开发工具,Scala 在这一领域尤为活跃.本系列文章将介绍一些针对 Java 和 Scala 语言的较新的并发性编程方法. 在任何并发性应用程序中,异步事件处理都至关重要.事件来源可能是不同的计算任务.I/O 操作或与外部系统的交互.无论来源是什么,应用程序代码都必须跟踪事件,协调为响应事件而采取的操作. Java 应用程序可采用两种基本的异

随机推荐