前言:
小弟能力不足,认知有限,难免考虑不全面,希望大佬能给出更好的建议,指出存在的问题和不足,在此跪谢。
IO发展史
Java中对于I/O能力的支持主要分为三个比较关键的阶段:
BIO
第一个阶段是起步阶段JDK1.0 ~ JDK1.3,这个阶段JDK是处于BIO阶段的,也就是同步阻塞模式,该阶段的类库还非常的初级,对系统层面的一些网络编程的API都没有进行实现,因此这个阶段的很多大型应用服务器都采用C或者C++语言来进行开发的,因为C或者C++可以直接调用操作系统提供的非阻塞I/O能力;
NIO
第二个阶段从JDK1.4开始的,从JDK1.4开始,Java新增了java.nio的包,正式支持的NIO,提供了许多非阻塞I/O开发的API和类库;
AIO
第三个阶段是从JDK1.7开始的,这一次是对原来的NIO类库进行了升级,官方称为NIO 2.0,该版本不但强化了原来的基于I/O多路复用模型的NIO模式,同时新增了异步的AIO功能,所以也有很多人称之为AIO。
各个IO介绍
BIO
在Java中,BIO(Blocking I/O)指的是阻塞式I/O,是一种基本的I/O模型。它的实现原理相对简单,但在高并发场景下性能较差。下面我将详细介绍BIO的实现原理。
-
阻塞式I/O :
在BIO中,当一个线程在进行I/O操作时,如果数据没有准备好,该线程会被阻塞,直到数据准备好并被读取或写入。这意味着一个线程只能处理一个连接,如果有大量连接同时到来,就需要大量线程来处理,这会导致资源消耗过大。
-
实现原理:
- 服务端:服务端通过ServerSocket监听客户端的连接请求。当有连接请求到来时,服务端会创建一个新的线程来处理该连接。
- 客户端:客户端通过Socket向服务端发起连接请求。一旦连接建立,客户端和服务端之间可以进行数据的读取和写入。
-
服务端示例代码:
思路:在服务端的代码中,我们创建了一个固定大小的线程池,用于处理客户端的连接请求。每当有客户端连接时,就会将连接交给线程池中的一个线程来处理,这样可以提高并发处理能力。同时,我们定义了一个
ClientHandler
类来处理客户端的请求,这样可以更好地组织代码逻辑。这样的设计可以更好地满足企业级生产环境的要求,提高了系统的并发处理能力和稳定性。当然你还可以加入日志打印,更好的排查问题,但是因为这种已经过时,所以只是简单示例。
javaimport java.io.*; import java.net.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Server { public static void main(String[] args) { ServerSocket serverSocket = null; ExecutorService executor = Executors.newFixedThreadPool(10); // 创建一个固定大小的线程池 try { serverSocket = new ServerSocket(8080); System.out.println("Server started. Waiting for client..."); while (true) { // 等待客户端连接 Socket clientSocket = serverSocket.accept(); System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress()); // 使用线程池处理客户端请求 executor.execute(new ClientHandler(clientSocket)); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (serverSocket != null) { serverSocket.close(); } executor.shutdown(); // 关闭线程池 } catch (IOException e) { e.printStackTrace(); } } } private static class ClientHandler implements Runnable { private Socket clientSocket; public ClientHandler(Socket clientSocket) { this.clientSocket = clientSocket; } @Override public void run() { try { // 获取输入流 BufferedReader input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); // 获取输出流 PrintWriter output = new PrintWriter(clientSocket.getOutputStream(), true); // 读取客户端发送的数据 String clientMessage = input.readLine(); System.out.println("Received from client: " + clientMessage); // 向客户端发送数据 output.println("Hello, client!"); // 关闭流和连接 input.close(); output.close(); clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
-
客户端实例代码实现:
javaimport java.io.*; import java.net.*; public class Client { public static void main(String[] args) { Socket socket = null; try { socket = new Socket("localhost", 8080); System.out.println("Connected to server."); // 获取输入流 BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 获取输出流 PrintWriter output = new PrintWriter(socket.getOutputStream(), true); // 向服务端发送数据 output.println("Hello, server!"); // 读取服务端发送的数据 String serverMessage = input.readLine(); System.out.println("Received from server: " + serverMessage); // 关闭流和连接 input.close(); output.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (socket != null) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
-
适用场景 :
BIO适用于连接数较少且吞吐量要求不高的场景,例如传统的Socket通信应用。
-
局限性 :
由于BIO的阻塞特性,它在高并发场景下表现较差,因为大量线程会因为I/O阻塞而处于等待状态,导致资源浪费。
总的来说,BIO是一种简单直观的I/O模型,但在高并发场景下存在性能瓶颈。随着业务的发展,通常会选择更高效的NIO(Non-blocking I/O)或者AIO(Asynchronous I/O)来替代BIO。
NIO
在Java中,NIO(New I/O)是一种非阻塞I/O模型,相比于传统的BIO(Blocking I/O),NIO具有更高的并发处理能力。下面我将详细介绍NIO的实现原理。
-
非阻塞I/O :
NIO的核心是非阻塞I/O,它允许一个线程处理多个连接,当一个连接上的I/O操作不可立即完成时,线程可以去处理其他连接,而不是被阻塞。
-
核心组件:
- 通道(Channel):用于读取和写入数据,可以是文件、套接字等。
- 缓冲区(Buffer):用于临时存储数据,读取数据到缓冲区或将缓冲区中的数据写入通道。
- 选择器(Selector):用于监听多个通道的事件,例如连接就绪、读就绪、写就绪等。
-
实现原理:
- 服务端:服务端通过ServerSocketChannel监听连接请求,一旦有连接到来,会将该连接注册到Selector上,并监听连接就绪事件。
- 客户端:客户端通过SocketChannel向服务端发起连接请求,连接建立后也会注册到Selector上。
-
NIO服务器端示例代码:
实现思路:
在以下代码中,我们引入了日志打印服务,使用了Java自带的Logger类来记录日志。同时,我们使用了线程池来处理客户端的连接请求和数据读写操作,以提高并发处理能力。对于各种可能遇到的问题,比如连接超时、网络异常、数据读写异常等,我们在相应的位置进行了异常处理,并记录了相应的日志,以便于排查和解决问题。
这样的设计更加符合企业级生产规范,提高了系统的并发处理能力和稳定性,并且对各种异常情况进行了处理,使得系统更加健壮可靠。
javaimport java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Logger; public class NIOServer { private static final Logger logger = Logger.getLogger(NIOServer.class.getName()); private static final ExecutorService executor = Executors.newFixedThreadPool(10); public static void main(String[] args) { try { Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(8080)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); keyIterator.remove(); if (key.isAcceptable()) { executor.execute(() -> handleAccept(key, selector)); } else if (key.isReadable()) { executor.execute(() -> handleRead(key)); } } } } catch (IOException e) { logger.severe("Error in NIO server: " + e.getMessage()); } } private static void handleAccept(SelectionKey key, Selector selector) { try { ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); SocketChannel clientChannel = serverSocketChannel.accept(); clientChannel.configureBlocking(false); clientChannel.register(selector, SelectionKey.OP_READ); } catch (IOException e) { logger.severe("Error in handleAccept: " + e.getMessage()); } } private static void handleRead(SelectionKey key) { try { SocketChannel clientChannel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = clientChannel.read(buffer); if (bytesRead == -1) { clientChannel.close(); key.cancel(); } else if (bytesRead > 0) { buffer.flip(); byte[] data = new byte[bytesRead]; buffer.get(data); logger.info("Received from client: " + new String(data)); // 可以在这里处理接收到的数据 } } catch (IOException e) { logger.severe("Error in handleRead: " + e.getMessage()); } } }
-
NIO客户端的代码:
javaimport java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Logger; public class NIOClient { private static final Logger logger = Logger.getLogger(NIOClient.class.getName()); private static final ExecutorService executor = Executors.newFixedThreadPool(10); public static void main(String[] args) { try { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("localhost", 8080)); while (!socketChannel.finishConnect()) { // 等待连接完成 } executor.execute(() -> { try { String message = "Hello, server!"; ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); socketChannel.write(buffer); buffer.clear(); int bytesRead = socketChannel.read(buffer); if (bytesRead > 0) { buffer.flip(); byte[] data = new byte[bytesRead]; buffer.get(data); logger.info("Received from server: " + new String(data)); // 可以在这里处理接收到的数据 } } catch (IOException e) { logger.severe("Error in NIO client: " + e.getMessage()); } finally { try { socketChannel.close(); } catch (IOException e) { logger.severe("Error in closing socket channel: " + e.getMessage()); } } }); } catch (IOException e) { logger.severe("Error in NIO client: " + e.getMessage()); } } }
-
适用场景 :
NIO适用于高并发的网络应用,例如Web服务器、聊天服务器等,能够更高效地处理大量连接。
总的来说,NIO通过Selector、Channel和Buffer的组合,实现了非阻塞I/O,提高了系统的并发处理能力。然而,NIO编程相对复杂,需要处理事件的就绪状态,因此在实际应用中通常会使用NIO框架或者基于NIO的高级框架,如Netty。
AIO
在Java中,AIO(Asynchronous I/O)是一种基于事件和回调机制的I/O模型,相比于传统的BIO(Blocking I/O)和NIO(Non-blocking I/O),AIO更加适用于处理大量并发连接。下面我将详细介绍AIO的实现原理。
-
异步I/O :
AIO的核心是异步I/O,它允许一个线程在等待数据就绪的同时继续做其他事情,当数据就绪后通过回调机制来处理数据。这种模型相比于NIO更加灵活,因为不需要手动检查就绪状态,而是通过事件通知来处理。
-
核心组件:
- 异步通道(AsynchronousChannel):用于进行异步I/O操作,包括文件和套接字等。
- 异步操作结果(AsynchronousResult):用于存储异步操作的结果,可以通过回调方式获取结果。
- 异步处理器(AsynchronousHandler):用于处理异步操作完成后的回调。
-
实现原理:
- 服务端:服务端通过AsynchronousServerSocketChannel监听连接请求,一旦有连接到来,会调用accept方法,并通过回调方式处理连接就绪事件。
- 客户端:客户端通过AsynchronousSocketChannel向服务端发起连接请求,连接建立后也可以通过回调方式处理后续的读写操作。
-
简单的AIO服务器示例代码 :
实现思路:针对企业级生产环境中高可用的通信需求,以下是一个更完善的AIO服务端和客户端的Java代码。该代码考虑了各种可能遇到的问题,并给出了切实可行的异常解决方案。同时,引入了日志打印服务,使用了Java自带的Logger类来记录日志,并使用了线程池来处理客户端的连接请求和数据读写操作,以提高并发处理能力。
javaimport java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; public class AIOServer { private static final Logger logger = Logger.getLogger(AIOServer.class.getName()); private static final ExecutorService executor = Executors.newFixedThreadPool(10); public static void main(String[] args) { try { AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(8080)); serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() { @Override public void completed(AsynchronousSocketChannel clientChannel, Void attachment) { serverSocketChannel.accept(null, this); // 接受下一个连接 executor.execute(() -> handleRead(clientChannel)); } @Override public void failed(Throwable exc, Void attachment) { logger.severe("Error in accepting connection: " + exc.getMessage()); } }); // 阻止主线程退出 Thread.currentThread().join(); } catch (IOException | InterruptedException e) { logger.severe("Error in AIO server: " + e.getMessage()); } } private static void handleRead(AsynchronousSocketChannel clientChannel) { ByteBuffer buffer = ByteBuffer.allocate(1024); clientChannel.read(buffer, null, new CompletionHandler<Integer, Void>() { @Override public void completed(Integer bytesRead, Void attachment) { if (bytesRead == -1) { try { clientChannel.close(); } catch (IOException e) { logger.severe("Error in closing client channel: " + e.getMessage()); } return; } buffer.flip(); byte[] data = new byte[bytesRead]; buffer.get(data); logger.info("Received from client: " + new String(data)); // 可以在这里处理接收到的数据 buffer.clear(); clientChannel.read(buffer, null, this); // 继续读取数据 } @Override public void failed(Throwable exc, Void attachment) { logger.severe("Error in reading from client: " + exc.getMessage()); } }); } }
-
简单的AIO客户端示例代码:
javaimport java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Logger; public class AIOClient { private static final Logger logger = Logger.getLogger(AIOClient.class.getName()); private static final ExecutorService executor = Executors.newFixedThreadPool(10); public static void main(String[] args) { try { AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open(); socketChannel.connect(new InetSocketAddress("localhost", 8080), null, new CompletionHandler<Void, Void>() { @Override public void completed(Void result, Void attachment) { String message = "Hello, server!"; ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); socketChannel.write(buffer, null, new CompletionHandler<Integer, Void>() { @Override public void completed(Integer bytesWritten, Void attachment) { if (buffer.hasRemaining()) { socketChannel.write(buffer, null, this); // 继续写入数据 } else { buffer.clear(); socketChannel.read(buffer, null, new CompletionHandler<Integer, Void>() { @Override public void completed(Integer bytesRead, Void attachment) { buffer.flip(); byte[] data = new byte[bytesRead]; buffer.get(data); logger.info("Received from server: " + new String(data)); // 可以在这里处理接收到的数据 } @Override public void failed(Throwable exc, Void attachment) { logger.severe("Error in reading from server: " + exc.getMessage()); } }); } } @Override public void failed(Throwable exc, Void attachment) { logger.severe("Error in writing to server: " + exc.getMessage()); } }); } @Override public void failed(Throwable exc, Void attachment) { logger.severe("Error in connecting to server: " + exc.getMessage()); } }); // 阻止主线程退出 Thread.currentThread().join(); } catch (IOException | InterruptedException e) { logger.severe("Error in AIO client: " + e.getMessage()); } } }
-
适用场景 :
AIO适用于需要处理大量并发连接且对性能要求较高的场景,例如高性能的网络服务器、金融交易系统等。
总的来说,AIO通过异步I/O和事件回调机制,实现了高效的并发处理能力,相比于NIO更加灵活和高效。然而,AIO在Java中的实现相对较新,需要较高的技术要求,因此在实际应用中通常会使用成熟的AIO框架或者基于AIO的高级框架。
在Java中,有一些成熟的AIO框架或者基于AIO的高级框架,它们提供了更加便捷和高效的异步I/O编程方式。以下是一些常用的框架:
-
Netty :
Netty是一个基于NIO的高性能网络通信框架,但它也提供了对AIO的支持。Netty的异步事件驱动模型和高度可定制的架构使得它成为构建高性能、可扩展的网络应用程序的理想选择。Netty提供了丰富的功能,包括TCP/UDP传输、HTTP编解码、WebSocket支持等,广泛应用于网络服务器、分布式系统等领域。
-
Grizzly :
Grizzly是一个基于NIO的高性能网络框架,它提供了对AIO的支持,并且具有高度可扩展性和灵活性。Grizzly可以用于构建高性能的Web服务器、应用服务器等网络应用。
-
Apache MINA :
Apache MINA是一个基于NIO的网络应用框架,它提供了对AIO的支持,并且具有良好的扩展性和灵活性。MINA可以用于构建各种类型的网络应用,包括游戏服务器、即时通讯服务器等。
这些框架都提供了对AIO的封装和抽象,简化了异步I/O编程的复杂性,同时提供了丰富的功能和高性能的网络通信能力。在实际应用中,选择合适的框架取决于具体的需求和项目背景,但无论选择哪个框架,都可以极大地简化异步I/O编程的复杂性,提高开发效率和系统性能。