搞懂BIO与NIO

网络编程是现代软件开发中不可或缺的一部分,而I/O(输入/输出)模型则是网络编程的基石。在Java中,我们常常遇到三种主要的I/O模型:BIO(Blocking I/O)、NIO(New I/O)和AIO(Asynchronous I/O)

BIO

(Blocking IO)阻塞I/O是最传统、最简单的I/O模型之一。在BIO中,当一个线程执行了I/O操作(如读取文件或网络数据),它会一直阻塞等待,直到数据准备好。

搞懂BIO与NIO_第1张图片

// ServerSocket
ServerSocket类用于创建服务器端套接字,它在指定的端口上监听客户端的连接请求。
一旦有客户端连接请求到达,ServerSocket会接受该连接并返回一个新的Socket实例,用于与客户端进行通信.
// Socket
Socket类代表客户端与服务器端的连接。
通过Socket,客户端可以向服务器端发送请求,并接收服务器端的响应。
在服务器端,每个接受的客户端连接都会对应一个Socket实例,通过这个Socket实例可以与客户端进行通信.
因此,Socket在客户端和服务端之间扮演着关键的角色,用于实现双向的通信 

示例代码

// 服务端
 public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8888);
            while (true){
                System.out.println("等待客户端连接");
                // 阻塞
                Socket socket = serverSocket.accept();
                // 处理服务端数据
                threadPoolRun(socket);

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
// 线程池处理
private static void threadPoolRun(Socket socket){
        threadPool.execute(()->{
            try {
                handleClient(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

	// 处理服务端数据
    private static void handleClient(Socket socket) throws IOException {
        byte[] bytes = new byte[1024];
        System.out.println("开始读数据");
        int read = socket.getInputStream().read(bytes);
        System.out.println("接收到端数据为:"+new String(bytes,0,read));
        // 返回客户端数据
        socket.getOutputStream().write("ACK".getBytes());
        socket.getOutputStream().flush();
    }
// 客户端
public static void main(String[] args) {
        try {
        	// 与服务端建立连接 
            Socket socket = new Socket("localhost",8888);
            // 给服务端传输数据
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            out.println("Hello,Server");
            // 接收服务端响应数据
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            byte[] bytes = new byte[1024];
            int read = dis.read(bytes);
            String response = new String(bytes, 0, read);
            System.out.println("response:"+ response);
            out.close();
            dis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

总结

1. 每个连接对应一个线程,导致线程数量随着连接数增加而增加,可能导致资源消耗过大。
2. 读取和写入操作是阻塞的,可能导致性能瓶颈,特别是在大量并发连接的情况下。
3. 适用于连接数较少且通信量不大的场景,但在高并发情况下性能表现不佳。

NIO

(Non Blocking IO)同步非阻塞,服务器实现模式为一个线程可以处理多个请求(连接),客户端发送的连接请求都会注册到多路复用器selector上,多路复用器轮询到连接有IO请求就进行处理,JDK1.4开始引入。

关键概念

// ServerSocketChannel
ServerSocketChannel是用于监听服务端套接字的通道。它允许我们监听新进来的TCP连接,就像标准的ServerSocket一样。当ServerSocketChannel接收到一个连接请求时,可以创建一个SocketChannel来与客户端进行通信。
// SocketChannel
SocketChannel是用于进行TCP连接的通道。它可以连接到服务端并进行数据的读写操作。与传统的Socket不同,SocketChannel是非阻塞的,这意味着它可以在没有数据可读写时立即返回,而不会阻塞线程。
// Buffer
BufferNIO中用于数据读写的缓冲区。它是一个线性、有限的数据结构,可以暂时存储数据。在NIO中,数据的读写都是通过Buffer进行的,它提供了对数据的高效操作,包括读取、写入、翻转等操作。常见的Buffer类型包括ByteBufferCharBufferIntBuffer// Selector
SelectorJava NIO中的多路复用器,它可以检测多个通道(Channel)上是否有事件发生,如连接请求、数据到达等。通过Selector,一个单独的线程可以管理多个通道,从而提高了系统的并发处理能力。
// SelectionKey
SelectionKey是一个标识符,表示了一个通道在Selector上的注册。它包含了通道和Selector的关联关系,以及通道所关注的事件类型(如读、写、连接等)。当一个通道在Selector上注册时,会返回一个SelectionKey对象,通过它可以获取通道和关注的事件类型。

示例代码

// 服务端
public static void main(String[] args) throws Exception {
        // 创建ServerSocketChannel并绑定到指定端口
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(9898));
        // 设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);

        // 创建Selector
        Selector selector = Selector.open();
        // 将ServerSocketChannel注册到Selector上,监听连接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true){
            // 阻塞直到有事件发生
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
            System.out.println("事件列表:"+JSON.toJSONString(selectionKeys));
            while (keyIterator.hasNext()){
                SelectionKey key = keyIterator.next();
                if(key.isAcceptable()){
                    // 如果是连接事件
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    // 接收客户端连接
                    SocketChannel client = server.accept();
                    client.configureBlocking(false);
                    // 将客户端SocketChannel注册到Selector上,监听读事件
                    client.register(selector,SelectionKey.OP_READ);

                }else if(key.isReadable()){
                    // 如果是读事件
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    // 读取数据到Buffer
                    client.read(buffer);
                    buffer.flip();
                    System.out.println("Received: " + new String(buffer.array()).trim());
                }
                keyIterator.remove();
            }
            Thread.sleep(500);

        }
    }
// 客户端
public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 9898));
        // 设置为非阻塞模式
        socketChannel.configureBlocking(false);

        String message = "Hello,NIO Server";
        ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
        socketChannel.write(buffer);
        socketChannel.close();

    }

执行过程

1.服务端启动,创建一个ServerSocketChannel,绑定制定的端口,创建Selector多路复用器,ServerSocketChannel注册到Selector上,监听客户端的连接事件.
2.客户端创建一个SocketChannel,连接到服务端制定的地址和接口.
3.当客户端连接请求大到达时,Selector会检测到,然后交给服务端的线程处理
4.服务端线程接收连接请求,并用ServerSocketChannel为这个连接创建一个新的SocketChannel
5.服务端将新创建的SocketChannel 注册到Selector上,用于监听改通道的读写事件.
6.此时客户端和服务端的SocketChannel之间可以进行数据的读写操作,当有读写事件发生时,Selector会检测到,通知相应的事件进行数据处理和响应操作.
7.通信过程中,客户端和服务端的数据读写都是通过buffer进行的,可以提高读写效率.

集合流程图理解:搞懂BIO与NIO_第2张图片

总结

NIO是Java中用于高性能I/O操作的一种机制。它提供了Channel和Buffer的抽象,以及Selector的多路复用能力,使得单个线程可以有效地管理多个通道的I/O操作。NIO相对于传统的I/O(即BIO)具有更高的并发性能和可扩展性,适用于需要处理大量连接的网络应用。通过NIO,可以实现高效的网络通信、文件操作和数据传输,同时提供了对非阻塞I/O操作的支持 .

你可能感兴趣的:(nio)