深入BIO与NIO

BIO

在 JDK1.4 之前,我们建立网络连接的时候只能采用 BIO,需要先在服务端启动一个ServerSocket,然后在客户端启动 Socket 来对服务端进行通信,默认情况下服务端需要对每个请求建立一个线程等待请求,而客户端发送请求后,先咨询服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝,如果有的话,客户端线程会等待请求结束后才继续执行,这就是阻塞式IO。
以下示例了一个服务端程序:绑定端口号 9999,accept 方法用来监听客户端连接, 如果没有客户端连接,就一直等待,程序会阻塞到这里。

//BIO 服务器端程序
public class TCPServer {
public static void main(String[] args) throws Exception {
//1.创建ServerSocket对象
System.out.println("服务端 启动....");
System.out.println("初始化端口 9999 ");
ServerSocket ss=new ServerSocket(9999); //端口号
while (true) {
//2.监听客户端
Socket s = ss.accept(); //阻塞
//3.从连接中取出输入流来接收消息
InputStream is = s.getInputStream(); //阻塞
byte[] b = new byte[10];
is.read(b);
String clientIP = s.getInetAddress().getHostAddress();
System.out.println(clientIP + "说:" + new String(b).trim());
//4.从连接中取出输出流并回话
OutputStream os = s.getOutputStream();
os.write("没钱".getBytes());
//5.关闭
s.close();
}
}
}

BIO 客户端程序,通过 9999 端口连接服务器端,getInputStream 方法用来等待服务器端返回数据,如果没有返回,就一直等待,程序会阻塞到这里。:

 //BIO 客户端程序
    public class TCPClient {
        public static void main(String[] args) throws Exception {
            while (true) {
                //1.创建Socket对象
                Socket s = new Socket("127.0.0.1", 9999);
                //2.从连接中取出输出流并发消息
                OutputStream os = s.getOutputStream();
                System.out.println("请输入:");
                Scanner sc = new Scanner(System.in);
                String msg = sc.nextLine();
                os.write(msg.getBytes());
                //3.从连接中取出输入流并接收回话
                InputStream is = s.getInputStream(); //阻塞
                byte[] b = new byte[20];
                is.read(b);
                System.out.println("老板说:" + new String(b).trim());
                //4.关闭
                s.close();
            }
        }
    }

NIO

java.nio 全称 Java Non-Blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO)。新增了许多用于处理输入输出的类,这些类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写,新增了满足 NIO 的功能。
NIO 和 BIO 有着相同的目的和作用,但是它们的实现方式完全不同;

  • BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 IO 的效率比流 IO 高很多。
  • NIO 是非阻塞式的,这一点跟 BIO 也很不相同,使用它可以提供非阻塞式的高伸缩性网络。

NIO 主要有三大核心部分:

  • Channel通道
  • Buffer缓冲区
  • Selector选择器
    传统的 BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel和 Buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。
    使用 Java 的 NIO,用非阻塞的 IO 方式处理
    优点:这种模式可以用一个线程,处理大量的客户端连接
    缺点:代码复杂度较高,不易理解

多路复用的Selector选择器

Selector 是 Java NIO(Non-blocking I/O)框架中的关键组件之一,它提供了一种机制来检测一组 SelectableChannel 对象(如 ServerSocketChannel, SocketChannel, DatagramChannel 和 FileChannel)上的 I/O 事件。Selector 能够高效地监控多个通道的读、写、连接等事件,从而允许一个单线程处理多个网络连接。使用 Selector 可以显著提高服务器的可扩展性和效率,因为它避免了为每个连接创建单独线程的开销,减少了上下文切换,并且只在实际有事件发生时才进行 I/O 操作,这样可以最大化地利用 CPU 和 I/O 资源。

import java.nio.channels.*;
import java.net.*;

public class SelectorExample {
    public static void main(String[] args) throws Exception {
        // 创建一个 Selector
        Selector selector = Selector.open();
        
        // 打开 ServerSocketChannel 并绑定到本地地址
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));
        
        // 设置通道为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        
        // 注册通道到 Selector,监听连接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        while (true) {
            // 选择就绪的通道
            int readyChannels = selector.select();
            
            if (readyChannels == 0) continue;
            
            // 遍历所有就绪的 SelectionKey
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                
                if (key.isAcceptable()) {
                    // 如果是连接请求,接受新的 SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    // 如果是读就绪,读取数据
                    // ...
                }
                
                iterator.remove();
            }
        }
    }
}

这段代码展示了一个基本的 TCP 服务器,它使用 Selector 来监听连接请求和读事件。根据实际需求,你可以扩展此代码来处理更多类型的事件和更复杂的业务逻辑。

使用 NIO 编写了一个聊天程序

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class ChatServer {

    private static final int PORT = 8080;
    private static final int BUFFER_SIZE = 1024;
    private static final ByteBuffer buffer = ByteBuffer.allocate(BROADCAST_SIZE);

    public static void main(String[] args) {
        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("Chat Server started on port " + PORT);

            while (true) {
                selector.select();
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectedKeys.iterator();

                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();

                    if (key.isAcceptable()) {
                        acceptNewConnection(serverSocketChannel, selector);
                    } else if (key.isReadable()) {
                        readFromClient(key);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void acceptNewConnection(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
        SocketChannel clientChannel = serverSocketChannel.accept();
        clientChannel.configureBlocking(false);
        clientChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("New client connected: " + clientChannel.getRemoteAddress());
    }

    private static void readFromClient(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        buffer.clear();
        int bytesRead = clientChannel.read(buffer);

        if (bytesRead > 0) {
            buffer.flip();
            byte[] data = new byte[bytesRead];
            buffer.get(data);
            String message = new String(data);
            System.out.println("Received from client: " + message);
            broadcastToAll(message, clientChannel);
        } else if (bytesRead < 0) {
            key.cancel();
            clientChannel.close();
        }
    }

    private static void broadcastToAll(String message, SocketChannel sender) throws IOException {
        for (SelectionKey key : selector.keys()) {
            Channel targetChannel = key.channel();
            if (targetChannel instanceof SocketChannel && targetChannel != sender) {
                ((SocketChannel) targetChannel).write(ByteBuffer.wrap(message.getBytes()));
            }
        }
    }
}

客户端代码示例:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class ChatClient {

    private static final int PORT = 8080;
    private static final String SERVER_ADDRESS = "localhost";
    private static final int BUFFER_SIZE = 1024;
    private static final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);

    public static void main(String[] args) {
        try (SocketChannel socketChannel = SocketChannel.open()) {
            socketChannel.connect(new InetSocketAddress(SERVER_ADDRESS, PORT));
            socketChannel.configureBlocking(false);
            System.out.println("Connected to the chat server.");

            while (true) {
                System.out.print("Enter your message: ");
                String message = System.console().readLine();
                //特殊命令:退出
                if (message.equalsIgnoreCase("exit")) {
                    break;
                }
                buffer.put(message.getBytes());
                buffer.flip();
                socketChannel.write(buffer);
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jpq+

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值