第一章Netty,如何实现I/O多路复用的功能

基于前文对 ‌I/O 多路复用原理‌、‌Epoll 机制‌及 ‌Java NIO‌ 的讨论,实现 I/O 多路复用的核心在于利用操作系统提供的系统调用(如 Linux 的 epoll)或语言层面的封装(如 Java 的 Selector)。

以下分别以 ‌Java NIO(跨平台通用)‌ 和 ‌Linux C (epoll,高性能底层)‌ 为例,展示如何实现。

一、 Java NIO 实现(基于 Selector)

Java 通过 java.nio.channels 包屏蔽了底层 OS 差异,在 Linux 下自动映射为 epoll,在 Windows 下映射为 IOCP/select。

1. 核心步骤

创建 Selector‌:打开选择器。

注册 Channel‌将 ServerSocketChannel 设置为非阻塞,并注册 OP_ACCEPT 事件。

循环监听‌:调用 selector.select() 阻塞等待事件。

处理事件‌:遍历 selectedKeys,根据事件类型(接受连接、读取数据)执行逻辑。

2. 代码示例

java 复制代码
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NioServer {
    public static void main(String[] args) throws Exception {
        // 1. 打开 Selector
        Selector selector = Selector.open();
        
        // 2. 创建 ServerSocketChannel 并配置为非阻塞
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.bind(new InetSocketAddress(8080));
        
        // 3. 注册 OP_ACCEPT 事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        System.out.println("Server started on port 8080...");

        while (true) {
            // 4. 阻塞等待就绪事件
            selector.select();
            
            // 5. 获取就绪的 SelectionKey 集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys.iterator();
            
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove(); // 必须手动移除,防止重复处理

                try {
                    if (key.isAcceptable()) {
                        // 处理新连接
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        SocketChannel clientChannel = ssc.accept();
                        clientChannel.configureBlocking(false);
                        // 注册 OP_READ 事件,关注客户端数据
                        clientChannel.register(selector, SelectionKey.OP_READ);
                        System.out.println("New connection accepted: " + clientChannel.getRemoteAddress());
                    } else if (key.isReadable()) {
                        // 处理数据读取
                        SocketChannel clientChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        int bytesRead = clientChannel.read(buffer);
                        
                        if (bytesRead > 0) {
                            buffer.flip();
                            String message = new String(buffer.array(), 0, buffer.remaining());
                            System.out.println("Received: " + message);
                            
                            // 简单回声
                            clientChannel.write(ByteBuffer.wrap(("Echo: " + message).getBytes()));
                        } else if (bytesRead == -1) {
                            // 连接关闭
                            clientChannel.close();
                            System.out.println("Connection closed");
                        }
                    }
                } catch (Exception e) {
                    key.cancel();
                    key.channel().close();
                }
            }
        }
    }
}

二、 Linux C 实现(基于 Epoll)

这是高性能服务器(如 Nginx、Redis)的底层实现方式,直接操作内核数据结构,效率最高。

1. 核心步骤

epoll_create‌:创建 epoll 实例。

epoll_ctl‌:将监听 socket 添加到 epoll 树中,设置 EPOLLIN 事件。

epoll_wait‌:等待事件发生,返回就绪的 fd 数组。

处理事件‌:遍历就绪数组,区分是监听 socket(接受新连接)还是客户端 socket(读取数据)。

2. 代码示例(伪代码/关键片段)

java 复制代码
#include <sys/epoll.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>

#define MAX_EVENTS 1024
#define PORT 8080

int main() {
    int listen_fd, epoll_fd, client_fd;
    struct sockaddr_in addr;
    struct epoll_event ev, events[MAX_EVENTS];
    
    // 1. 创建监听 socket
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    // ... bind 和 listen 省略 ...
    
    // 2. 创建 epoll 实例
    epoll_fd = epoll_create1(0);
    
    // 3. 注册监听 socket
    ev.events = EPOLLIN; // 关注读事件(包括新连接)
    ev.data.fd = listen_fd;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);
    
    while (1) {
        // 4. 等待事件,超时时间 -1 表示无限阻塞
        int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        
        for (int i = 0; i < n; i++) {
            if (events[i].data.fd == listen_fd) {
                // 5. 处理新连接
                client_fd = accept(listen_fd, NULL, NULL);
                // 设置非阻塞
                fcntl(client_fd, F_SETFL, O_NONBLOCK);
                
                // 注册新连接到 epoll
                ev.events = EPOLLIN | EPOLLET; // ET 模式
                ev.data.fd = client_fd;
                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);
            } else {
                // 6. 处理数据读取
                int fd = events[i].data.fd;
                char buf;
                int bytes = read(fd, buf, sizeof(buf));
                
                if (bytes > 0) {
                    write(fd, buf, bytes); // 回声
                } else if (bytes <= 0) {
                    close(fd); // 连接关闭或出错
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
                }
            }
        }
    }
    return 0;
}

三、 实现关键点总结

非阻塞设置‌:无论是 Java 的 configureBlocking(false) 还是 C 的 fcntl(O_NONBLOCK),‌必须‌将 Channel/FD 设置为非阻塞模式,否则读写操作仍会阻塞线程,导致多路复用失效。

事件移除‌:在 Java 中,selectedKeys 不会自动清空,必须手动 iterator.remove(),否则下次循环会重复处理同一事件。

ET 模式处理‌:若使用 Edge Triggered (ET) 模式(如 C 示例中的 EPOLLET),必须‌一次性读完所有数据‌(循环 read 直到返回 EAGAIN),否则剩余数据将不会再次触发通知,导致数据滞留。

异常处理‌:网络环境复杂,必须妥善处理连接断开、读写异常,及时关闭资源并从 Selector/Epoll 中注销,防止内存泄漏或无效事件堆积。

通过上述实现,单线程即可高效管理成千上万个并发连接,这正是高并发网络服务的核心基础。

相关推荐
ywl47081208711 小时前
第一章Netty,NIO零拷贝详解
netty·nio·selector
小bo波13 天前
从"任意文件复制"深挖Java I/O:字符流与字节流的本质抉择
java·nio·io流·后端开发·文件复制
swordbob17 天前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio
swordbob18 天前
NIO 的 Channel 里有多个 BIO 吗?
linux·网络·nio
starsky7623820 天前
NIO与BIO的区别
java·服务器·nio
东南门吹雪21 天前
JAVA TCP socket编程框架
java·高并发·socket·tcp·nio
JackSparrow41422 天前
彻底理解Java NIO(三)Java实现 I/O多路复用+Reactor模式及开源框架代码解读
java·c语言·开发语言·后端·nio·reactor模式
布朗克16823 天前
25 IO流高级操作——序列化、NIO与Files工具类
java·数据库·io·nio
aidou131423 天前
Kotlin中自定义RadioGroup实现多个RadioButton自动换行
android·开发语言·kotlin·shape·radiobutton·selector·radiogroup