网络编程

BIO(Blocking I/O)

传统阻塞式 IO,一个连接对应一个线程。

// 服务端
ServerSocket server = new ServerSocket(8080);
while (true) {
    Socket socket = server.accept(); // 阻塞等待连接
    new Thread(() -> handle(socket)).start(); // 每个连接新建线程
}
 
private void handle(Socket socket) {
    try (BufferedReader reader = new BufferedReader(
             new InputStreamReader(socket.getInputStream()));
         PrintWriter writer = new PrintWriter(socket.getOutputStream(), true)) {
        String line;
        while ((line = reader.readLine()) != null) { // 阻塞读
            writer.println("Echo: " + line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
 
// 客户端
Socket socket = new Socket("localhost", 8080);
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
BufferedReader reader = new BufferedReader(
    new InputStreamReader(socket.getInputStream()));
writer.println("hello");
System.out.println(reader.readLine());

缺点:连接数增多时线程数线性增长,资源耗尽。

NIO(Non-blocking I/O)

Java 1.4 引入,基于 ChannelBufferSelector

核心组件

组件说明
Channel双向数据通道,替代 Stream
Buffer数据读写缓冲区
Selector多路复用器,单线程管理多个 Channel
// Selector 模型
Selector selector = Selector.open();
 
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);                    // 非阻塞
serverChannel.register(selector, SelectionKey.OP_ACCEPT);  // 注册感兴趣的事件
 
while (true) {
    selector.select(); // 阻塞直到有事件就绪
    Set<SelectionKey> keys = selector.selectedKeys();
    Iterator<SelectionKey> it = keys.iterator();
 
    while (it.hasNext()) {
        SelectionKey key = it.next();
        it.remove();
 
        if (key.isAcceptable()) {
            SocketChannel client = serverChannel.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
 
        } else if (key.isReadable()) {
            SocketChannel client = (SocketChannel) key.channel();
            ByteBuffer buf = ByteBuffer.allocate(1024);
            int n = client.read(buf);
            if (n == -1) { client.close(); continue; }
            buf.flip();                         // 切换为读模式
            byte[] data = new byte[buf.remaining()];
            buf.get(data);
            System.out.println("收到: " + new String(data));
        }
    }
}

Buffer 操作

ByteBuffer buf = ByteBuffer.allocate(1024);
 
// 写模式:position=0, limit=capacity
buf.put("hello".getBytes());
 
// 切换到读模式:limit=position, position=0
buf.flip();
byte[] data = new byte[buf.remaining()];
buf.get(data);
 
// 重置为写模式(保留未读数据)
buf.compact();
 
// 重置为写模式(丢弃数据)
buf.clear();
 
// 标记与重置
buf.mark();    // 标记当前 position
buf.reset();   // 回到 mark 位置

AIO(Asynchronous I/O,NIO.2)

Java 7 引入,真正异步,操作完成后回调。

AsynchronousServerSocketChannel server =
    AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080));
 
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
    @Override
    public void completed(AsynchronousSocketChannel client, Void att) {
        server.accept(null, this); // 继续接受下一个连接
 
        ByteBuffer buf = ByteBuffer.allocate(1024);
        client.read(buf, buf, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer n, ByteBuffer buffer) {
                buffer.flip();
                client.write(buffer); // 回写数据
            }
 
            @Override
            public void failed(Throwable e, ByteBuffer buffer) { }
        });
    }
 
    @Override
    public void failed(Throwable e, Void att) { }
});

IO 模型对比

模型特点适用场景
BIO同步阻塞,一连接一线程连接数少、并发低
NIO同步非阻塞,多路复用高并发、长连接
AIO异步非阻塞,回调通知超高并发(实际使用少)

生产环境高并发网络编程推荐使用 Netty,它对 NIO 进行了深度封装。

UDP

// 服务端
DatagramSocket server = new DatagramSocket(8080);
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
server.receive(packet); // 阻塞接收
String msg = new String(packet.getData(), 0, packet.getLength());
 
// 客户端
DatagramSocket client = new DatagramSocket();
byte[] data = "hello".getBytes();
DatagramPacket packet = new DatagramPacket(
    data, data.length,
    InetAddress.getByName("localhost"), 8080);
client.send(packet);

URL 与 HTTP

// 简单 HTTP GET(Java 11+)
HttpClient client = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(5))
    .build();
 
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/users"))
    .header("Authorization", "Bearer " + token)
    .GET()
    .build();
 
HttpResponse<String> response = client.send(request,
    HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
 
// 异步请求
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenAccept(System.out::println);
 
// POST JSON
HttpRequest post = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/users"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString("{\"name\":\"Alice\"}"))
    .build();

InetAddress

InetAddress addr = InetAddress.getByName("www.example.com");
addr.getHostAddress();   // IP 地址字符串
addr.getHostName();      // 主机名
addr.isReachable(3000);  // 是否可达
 
InetAddress local = InetAddress.getLocalHost();

相关链接