BIO、NIO和AIO是Java编程语言中用于处理输入输出(IO)操作的三种不同的机制,它们分别代表同步阻塞I/O,同步非阻塞I/O和异步非阻塞I/O。
BIO(Blocking IO) 是最传统的IO模型,也称为同步阻塞IO。它实现的是同步阻塞模型,即服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理。如果这个连接不做任何事情会造成不必要的线程开销,并且线程在进行IO操作期间是被阻塞的,无法进行其他任务。在高并发环境下,BIO的性能较差,因为它需要为每个连接创建一个线程,而且线程切换开销较大,不过可以通过线程池机制改善。BIO适合一些简单的、低频的、短连接的通信场景,例如HTTP请求。
BIO模型
优点:
缺点:
服务端代码:
import java.io.*;
import java.net.*;
public class BIOServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
Socket clientSocket = null;
try {
//创建服务端
serverSocket = new ServerSocket(8888);
System.out.println("服务端已启动,等待客户端连接...");
while (true){
// 监听客户端请求,接收不到请求会一直等待
clientSocket = serverSocket.accept();
int port = clientSocket.getPort();
InetAddress inetAddress = clientSocket.getInetAddress();
System.out.println("客户端 "+inetAddress+":"+port+" 连接成功!");
//处理客户端消息
new Thread(new ServerThread(clientSocket)).start();
}
} catch (IOException e) {
System.out.println("客户端连接失败:" + e.getMessage());
} finally {
try {
if (clientSocket != null) {
clientSocket.close();
}
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e) {
System.out.println("关闭资源失败:" + e.getMessage());
}
}
}
}
/**
* 服务端线程处理类
*/
class ServerThread implements Runnable{
private Socket clientSocket;
public ServerThread(Socket clientSocket) {
this.clientSocket = clientSocket;
}
@Override
public void run() {
//获取客户端输入流以便接收客户端数据
try {
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
//获取客户端输出流以便向客户端发送数据
PrintWriter out = new PrintWriter(clientSocket.getOutputStream());
int port = clientSocket.getPort();
InetAddress inetAddress = clientSocket.getInetAddress();
String address = inetAddress+":"+port;
String inputLine;
while ((inputLine = in.readLine()) != null) {
//接收客户端消息
System.out.println("客户端"+address+"发来消息:" + inputLine);
//给客户端发送消息
out.println("服务端已接收到消息并回复:"+inputLine);
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码:
public class BIOClient {
public static void main(String[] args) throws IOException {
Socket clientSocket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
//绑定服务端ip和端口号
clientSocket = new Socket("localhost", 8888);
System.out.println("连接服务端成功!");
//获取输入流,接收服务端消息
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
//获取输出流,给服务端发送消息
out = new PrintWriter(clientSocket.getOutputStream(), true);
Scanner scanner = new Scanner(System.in);
while (true){
System.out.print("给服务端发送消息:");
String msg = scanner.nextLine();
out.println(msg);
String response;
if ((response = in.readLine()) != null) {
//接收服务端响应
System.out.println("服务端响应:" + response);
}
}
} catch (IOException e) {
System.out.println("连接服务端失败:" + e.getMessage());
} finally {
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (clientSocket != null) {
clientSocket.close();
}
} catch (IOException e) {
System.out.println("关闭资源失败:" + e.getMessage());
}
}
}
}
运行结果:
服务端
客户端1
客户端2
上述代码只是简单演示了如何使用BIO模型一个服务端接收并处理多个客户端请求情况。在这里创建了3个类,分别为服务端 BIOServer、多线程客户端处理类ServerThread和 客户端BIOClient,接着分别启动服务端BIOServer和两个客户端BIOClient,并在客户端中接收键盘输入的字符串发送给服务端,最后服务端把接收到的数据再返回给客户端。
由于BIO的特性,所以在服务端中需要为每个连接创建一个线程,也可以通过线程池进行优化,这里只是简单演示不做过多设计。
NIO是Java 1.4引入的新IO模型,也称为同步非阻塞IO,它提供了一种基于事件驱动的方式来处理I/O操作。
相比于传统的BIO模型,NIO采用了Channel、Buffer和Selector等组件,线程可以对某个IO事件进行监听,并继续执行其他任务,不需要阻塞等待。当IO事件就绪时,线程会得到通知,然后可以进行相应的操作,实现了非阻塞式的高伸缩性网络通信。在NIO模型中,数据总是从Channel读入Buffer,或者从Buffer写入Channel,这种模式提高了IO效率,并且可以充分利用系统资源。
NIO主要由三部分组成:选择器(Selector)、缓冲区(Buffer)和通道(Channel)。Channel是一个可以进行数据读写的对象,所有的数据都通过Buffer来处理,这种方式避免了直接将字节写入通道中,而是将数据写入包含一个或者多个字节的缓冲区。在多线程模式下,一个线程可以处理多个请求,这是通过将客户端的连接请求注册到多路复用器上,然后由多路复用器轮询到连接有I/O请求时进行处理。
对于NIO,如果从特性来看,它是非阻塞式IO,N是Non-Blocking的意思;如果从技术角度,NIO对于BIO来说是一个新技术,N的意思是New的意思。所以NIO也常常被称作Non-Blocking I/O或New I/O。
NIO适用于连接数目多且连接比较短(轻操作)的架构,例如聊天服务器、弹幕系统、服务器间通讯等。它通过引入非阻塞通道的概念,提高了系统的伸缩性和并发性能。同时,NIO的使用也简化了程序编写,提高了开发效率。
NIO模型
优点:
缺点:
NIO适合一些复杂的、高频的、长连接的通信场景,例如聊天室、网络游戏等。
在看代码之前先了解NIO中3个非常重要的组件,选择器(Selector)、缓冲区(Buffer) 和 通道(Channel):
get()
和put()
等方法来读写缓冲区中的数据。NIO的操作流程如下:
select()
方法等待事件发生。下面通过两段代码展示一下NIO的操作流程和使用方式。
服务端代码:
public class NIOServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
// 创建一个ServerSocketChannel并绑定到指定的端口
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(9999));
// 设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 将ServerSocketChannel注册到Selector上,并监听OP_ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器已启动,等待客户端连接...");
while (true) {
// 阻塞,等待事件发生
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) { // 处理连接请求事件
SocketChannel client = serverSocketChannel.accept();
client.configureBlocking(false);
//监听OP_ACCEPT事件
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
client.getRemoteAddress();
//分配缓存区容量
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer);
String output = new String(buffer.array()).trim();
Socket socket = client.socket();
InetAddress inetAddress = socket.getInetAddress();
int port = socket.getPort();
String clientInfo = inetAddress+":"+port;
String message = String.format("来自客户端 %s , 消息:%s", clientInfo , output);
System.out.println(message);
System.out.print("回复消息: ");
writeMessage(selector, client, buffer);
}
keyIterator.remove();
}
}
}
private static void writeMessage(Selector selector, SocketChannel client, ByteBuffer buffer) throws IOException {
Scanner scanner = new Scanner(System.in);
String message = scanner.nextLine();
buffer.clear();
buffer.put(message.getBytes());
//从写模式切换到读模式
buffer.flip();
while (buffer.hasRemaining()) {
client.write(buffer);
}
// 重新监听OP_ACCEPT事件
client.register(selector, SelectionKey.OP_READ);
}
}
客户端代码:
/**
* @author 公众号:索码理(suncodernote)
*/
public class NIOClient {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("localhost", 9999));
socketChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isConnectable()) {
SocketChannel client = (SocketChannel) key.channel();
if (client.isConnectionPending()) {
client.finishConnect();
}
System.out.print("Enter message to server: ");
Scanner scanner = new Scanner(System.in);
String message = scanner.nextLine();
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
client.write(buffer);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer);
String output = new String(buffer.array()).trim();
System.out.println("来自客户端的消息: " + output);
System.out.print("输入消息: ");
// 和服务端代码一样
writeMessage(selector, client, buffer);
}
keyIterator.remove();
}
}
}
}
运行结果:
server端
猎鹰
苍狼
上面代码新建了两个类:服务端(NIOServer
)和客户端(NIOClient
), 通过上面代码和运行结果可以发现,在服务端和客户端进行通信时,我们并没有新建线程类进行通信,这也是NIO和BIO最大的区别之一。
需要注意的是,虽然NIO提高了系统的并发性能和伸缩性,但也带来了更高的编程复杂度和更难的调试问题。因此,在使用Java NIO时,需要仔细考虑其适用场景和编程模型。
Java AIO(Asynchronous I/O)是Java提供的异步非阻塞IO编程模型,从Java 7版本开始支持,AIO又称NIO 2.0。
相比于NIO模型,AIO模型更进一步地实现了异步非阻塞IO,提高了系统的并发性能和伸缩性。在NIO模型中,虽然可以通过多路复用器处理多个连接请求,但仍需要在每个连接上进行读写操作,这仍然存在一定的阻塞。而在AIO模型中,所有的IO操作都是异步的,不会阻塞任何线程,可以更好地利用系统资源。
AIO模型有以下特性:
优点:
缺点:
AIO适合一些极端的、超高频的、超长连接的通信场景,例如云计算、大数据等。
需要注意的是,目前AIO模型还没有广泛应用,Netty等网络框架仍然是基于NIO模型。
服务端:
/**
* @author 公众号:索码理(suncodernote)
*/
public class AIOServer {
public static void main(String[] args) throws Exception {
// 创建一个新的异步服务器套接字通道,绑定到指定的端口上
final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(5000));
System.out.println("服务端启动成,等待客户端连接。");
// 开始接受新的客户端连接
serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel clientChannel, Void att) {
// 当一个新的连接完成时,再次接受新的客户端连接
serverChannel.accept(null, this);
// 创建一个新的缓冲区来读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
InetSocketAddress clientAddress = (InetSocketAddress) clientChannel.getRemoteAddress();
InetAddress clientIP = clientAddress.getAddress();
int clientPort = clientAddress.getPort();
System.out.println("客户端 "+ clientIP + ":" + clientPort + " 连接成功。");
} catch (IOException e) {
e.printStackTrace();
}
// 从异步套接字通道中读取数据
clientChannel.read(buffer, buffer, new ReadCompletionHandler(clientChannel));
}
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("Failed to accept a connection");
}
});
// 保持服务器开启
Thread.sleep(Integer.MAX_VALUE);
}
}
读处理程序:
/**
* @author 公众号:索码理(suncodernote)
*/
public class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel channel;
public ReadCompletionHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
@Override
public void completed(Integer result, ByteBuffer attachment) {
// 当读取完成时,反转缓冲区并打印出来
attachment.flip();
byte[] bytes = new byte[attachment.remaining()];
attachment.get(bytes);
System.out.println("收到的消息: " + new String(bytes , StandardCharsets.UTF_8));
attachment.clear();
// 从键盘读取输入
Scanner scanner = new Scanner(System.in);
System.out.print("输入消息: ");
String message = scanner.nextLine();
System.out.println();
// 写入数据到异步套接字通道
channel.write(ByteBuffer.wrap(message.getBytes()));
channel.read(attachment , attachment , new ReadCompletionHandler(channel));
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("Failed to read message");
}
}
客户端:
/**
* @author 公众号:索码理(suncodernote)
*/
public class AIOClient {
public static void main(String[] args) throws Exception {
// 创建一个新的异步套接字通道
AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel.open();
// 连接到服务器
clientChannel.connect(new InetSocketAddress("localhost", 5000), null, new CompletionHandler<Void, Void>() {
@Override
public void completed(Void result, Void attachment) {
System.out.println("连接到服务端成功。");
}
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("Failed to connect server");
}
});
// 从键盘读取输入
Scanner scanner = new Scanner(System.in);
System.out.print("发送消息: ");
String message = scanner.nextLine();
// 写入数据到异步套接字通道
clientChannel.write(ByteBuffer.wrap(message.getBytes()), null, new CompletionHandler<Integer, Void>() {
@Override
public void completed(Integer result, Void attachment) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
clientChannel.read(buffer, buffer, new ReadCompletionHandler(clientChannel));
}
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("Failed to write message");
}
});
// 保持客户端开启
Thread.sleep(Integer.MAX_VALUE);
}
}
测试结果:
服务端界面:
客户端1:
客户端2:
客户端3:
上述示例代码中,通过一个服务端(AIOServer)和3个客户端(AIOClient)的通信,简单演示了AIO的使用。可以发现,AIO和NIO的使用方式基本一致,数据都是从Channel读入Buffer,或者从Buffer写入Channel中,不同的是AIO是实现了异步非阻塞。
Java中的BIO、NIO和AIO都是处理输入/输出(I/O)操作的模型,但它们在处理方式和效率上有所不同。
总之,BIO、NIO和AIO各有优缺点,适用的场景也不同。BIO适合连接数目较少且固定的架构,NIO适合连接数目多,但是并发读写操作相对较少的场景,AIO则适合连接数目多,且并发读写操作也多的场景。在选择使用哪种I/O模型时,需要根据具体的应用场景和需求进行权衡。 - END -
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有