Java 非阻塞I/O使用方法
作者:swt369 时间:2022-01-01 22:54:57
绝大部分知识与实例来自O'REILLY的《Java网络编程》(Java Network Programming,Fourth Edition,by Elliotte Rusty Harold(O'REILLY))。
非阻塞I/O简介
非阻塞I/O(NIO)是处理高并发的一种手段。在高并发的情况下,创建和回收线程以及在线程间切换的开销变得不容忽视,此时就可以使用非阻塞I/O技术。这种技术的核心思想是每次选取一个准备好的连接,尽快地填充这个连接所能管理的尽可能多的数据,然后转向下一个准备好的连接。
利用非阻塞I/O实现的客户端
一般情况下,客户端不会需要处理很高数量的并发连接。事实上,非阻塞I/O主要是为服务器设计的,但它也可以用在客户端上。由于客户端的设计相比服务器容易,因此下面先用客户端来进行简单演示。
首先介绍通道(channel)和缓冲区。非阻塞I/O中使用SocketChannel类创建连接。要获取SocketChannel对象,需要将一个SocketAddress对象(通常会使用它的子类InetSocketAddress)传入它的静态工厂方法open()中。下面为一个示例:
SocketAddress address = new InetSocketAddress("127.0.0.1", 19);
SocketChannel client = SocketChannel.open(address);
open()方法是阻塞的,因此这之后的代码在连接建立之前不会执行。如果连接无法建立,会抛出一个IOException异常。
连接建立之后就需要获取输入和输出。不同于传统的getInputStream()与getOutputStream(),利用通道,你可以直接写入通道本身。不是写入字节数组,而是要写入一个ByteBuffer对象。ByteBuffer对象通过ByteBuffer.allocate(int capacity)获取,capacity为缓冲区大小,单位为字节:
ByteBuffer buffer = ByteBuffer.allocate(74);
获得ByteBuffer对象后,将其传递给SocketChannel对象的read()方法,SocketChannel对象会用从Socket读取的数据填充这个缓冲区。read()方法返回成功读取并储存在缓冲区中的字节数。默认情况下,它会至少读取一个字节,或者返回-1指示数据结束,没有字节可用时阻塞。这与InputStream的行为大致相同。但如果设置成非阻塞模式,没有字节可用时它会立即返回0,不会阻塞。
现在假定缓冲区内已经有了一些数据,之后就需要将它们提取出来。可以使用传统的方式,先将数据写入一个字节数组,之后再写入一个输出流中。这里介绍一种完全基于通道的方法:利用Channels工具类将输出流封装到一个通道中:
WritableByteChannel out = Channels.newChannel(System.out);
上面的代码将System.out封装入一个通道中。这之后就可以进行输出了。ByteBuffer对象在每次输出之前,需要调用一下它的flip()方法,使得通道从开头开始读。在读写完毕后,还需要调用它的clear()方法,重置缓冲区的状态。下面是进行一次数据输出的代码:
buffer.flip();
out.write(buffer);
buffer.clear();
实例1:利用非阻塞I/O实现的CharGenerator(字符生成器)客户端
服务器代码:
public static void createCharGeneratorServer(){
try(ServerSocket server = new ServerSocket(19)){
while(true){
try(Socket connection = server.accept()){
OutputStream out = connection.getOutputStream();
int firstPrintableCharacter = 33;
int numberOfPrintableCharacter = 94;
int numberOfCharactersPerLine = 72;
int start = firstPrintableCharacter;
while(true){
for(int i = start ;
i < start + numberOfCharactersPerLine ; i++){
out.write
(firstPrintableCharacter + (i - firstPrintableCharacter) % numberOfPrintableCharacter);
}
out.write('\r');
out.write('\n');
start = firstPrintableCharacter + (start + 1 - firstPrintableCharacter) % numberOfPrintableCharacter;
}
}catch (IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
客户端代码:
try {
SocketAddress address = new InetSocketAddress("127.0.0.1", 19);
SocketChannel client = SocketChannel.open(address);
ByteBuffer buffer = ByteBuffer.allocate(74);
WritableByteChannel out = Channels.newChannel(System.out);
while(client.read(buffer) != -1){
buffer.flip();
out.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
输出(无限循环):
]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEF
^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFG
_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGH
`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHI
abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJ
bcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK
启用非阻塞模式
上面的程序和使用输入/输出流的传统方式并没有太大差别。不过,可以调用ServerSocket的configureBlocking(false)方法将其设置为非阻塞模式。这个模式下,如果没有可用的数据,read()方法会立即返回,这让客户端可以去做其他事情。不过,由于read()方法在读不到数据时会返回0,读取数据的循环需要做一些改动:
while(true){
//这里可以写每次循环都要做的事,无论有没有读到数据
int n = client.read(buffer);
if(n > 0){
buffer.flip();
out.write(buffer);
buffer.clear();
}else if (n == -1) {
//除非服务器故障,否则不会发生
break;
}
}
来源:https://www.2cto.com/kf/201709/681503.html
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
Maven 错误找不到符号的解决方法
![](https://img.aspxhome.com/file/2023/7/62387_0s.png)
如何利用Java输出链表中倒数第k个结点
![](https://img.aspxhome.com/file/2023/4/61524_0s.png)
Java Set集合的遍历及实现类的比较
Android游戏开发学习之引擎用法实例详解
Opencv光流运动物体追踪详解
![](https://img.aspxhome.com/file/2023/4/91994_0s.jpg)
详解Spring Boot Security工作流程
![](https://img.aspxhome.com/file/2023/8/62668_0s.jpg)
C++容器适配与栈的实现及dequeque和优先级详解
![](https://img.aspxhome.com/file/2023/9/105179_0s.png)
Java Web项目部署在Tomcat运行出错与解决方法示例
![](https://img.aspxhome.com/file/2023/2/57622_0s.png)
SpringBoot下如何实现支付宝接口的使用
![](https://img.aspxhome.com/file/2023/7/58747_0s.png)
Java多线程之ThreadLocal浅析
![](https://img.aspxhome.com/file/2023/0/57500_0s.webp)
详解SpringMVC @RequestBody接收Json对象字符串
springboot 2.0 mybatis mapper-locations扫描多个路径的实现
IDEA部署JavaWeb项目到Tomcat服务器的方法
浅谈基于SpringBoot实现一个简单的权限控制注解
Java使用通配符实现增强泛型详解
![](https://img.aspxhome.com/file/2023/2/60282_0s.png)
Java实现答答租车系统
![](https://img.aspxhome.com/file/2023/9/61579_0s.jpg)
如何用java程序(JSch)运行远程linux主机上的shell脚本
支持SpEL表达式的自定义日志注解@SysLog介绍
SpringBoot参数校验Validator框架详解
Java实现分页的前台页面和后台代码
![](https://img.aspxhome.com/file/2023/1/61381_0s.jpg)