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

📅 2026/7/3 14:47:14
第一章Netty,如何实现I/O多路复用的功能
基于前文对 ‌I/O 多路复用原理‌、‌Epoll 机制‌及 ‌Java NIO‌ 的讨论实现 I/O 多路复用的核心在于利用操作系统提供的系统调用如 Linux 的 epoll或语言层面的封装如 Java 的 Selector。以下分别以 ‌Java NIO跨平台通用‌ 和 ‌Linux C (epoll高性能底层)‌ 为例展示如何实现。一、 Java NIO 实现基于 SelectorJava 通过 java.nio.channels 包屏蔽了底层 OS 差异在 Linux 下自动映射为 epoll在 Windows 下映射为 IOCP/select。1. 核心步骤创建 Selector‌打开选择器。注册 Channel‌将 ServerSocketChannel 设置为非阻塞并注册 OP_ACCEPT 事件。循环监听‌调用 selector.select() 阻塞等待事件。处理事件‌遍历 selectedKeys根据事件类型接受连接、读取数据执行逻辑。2. 代码示例importjava.net.InetSocketAddress;importjava.nio.ByteBuffer;importjava.nio.channels.*;importjava.util.Iterator;importjava.util.Set;publicclassNioServer{publicstaticvoidmain(String[]args)throwsException{// 1. 打开 SelectorSelectorselectorSelector.open();// 2. 创建 ServerSocketChannel 并配置为非阻塞ServerSocketChannelserverChannelServerSocketChannel.open();serverChannel.configureBlocking(false);serverChannel.bind(newInetSocketAddress(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 集合SetSelectionKeyselectedKeysselector.selectedKeys();IteratorSelectionKeyiteratorselectedKeys.iterator();while(iterator.hasNext()){SelectionKeykeyiterator.next();iterator.remove();// 必须手动移除防止重复处理try{if(key.isAcceptable()){// 处理新连接ServerSocketChannelssc(ServerSocketChannel)key.channel();SocketChannelclientChannelssc.accept();clientChannel.configureBlocking(false);// 注册 OP_READ 事件关注客户端数据clientChannel.register(selector,SelectionKey.OP_READ);System.out.println(New connection accepted: clientChannel.getRemoteAddress());}elseif(key.isReadable()){// 处理数据读取SocketChannelclientChannel(SocketChannel)key.channel();ByteBufferbufferByteBuffer.allocate(1024);intbytesReadclientChannel.read(buffer);if(bytesRead0){buffer.flip();StringmessagenewString(buffer.array(),0,buffer.remaining());System.out.println(Received: message);// 简单回声clientChannel.write(ByteBuffer.wrap((Echo: message).getBytes()));}elseif(bytesRead-1){// 连接关闭clientChannel.close();System.out.println(Connection closed);}}}catch(Exceptione){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. 代码示例伪代码/关键片段#includesys/epoll.h#includenetinet/in.h#includeunistd.h#includestdio.h#defineMAX_EVENTS1024#definePORT8080intmain(){intlisten_fd,epoll_fd,client_fd;struct sockaddr_in addr;struct epoll_event ev,events[MAX_EVENTS];// 1. 创建监听 socketlisten_fdsocket(AF_INET,SOCK_STREAM,0);// ... bind 和 listen 省略 ...// 2. 创建 epoll 实例epoll_fdepoll_create1(0);// 3. 注册监听 socketev.eventsEPOLLIN;// 关注读事件包括新连接ev.data.fdlisten_fd;epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_fd,ev);while(1){// 4. 等待事件超时时间 -1 表示无限阻塞intnepoll_wait(epoll_fd,events,MAX_EVENTS,-1);for(inti0;in;i){if(events[i].data.fdlisten_fd){// 5. 处理新连接client_fdaccept(listen_fd,NULL,NULL);// 设置非阻塞fcntl(client_fd,F_SETFL,O_NONBLOCK);// 注册新连接到 epollev.eventsEPOLLIN|EPOLLET;// ET 模式ev.data.fdclient_fd;epoll_ctl(epoll_fd,EPOLL_CTL_ADD,client_fd,ev);}else{// 6. 处理数据读取intfdevents[i].data.fd;charbuf;intbytesread(fd,buf,sizeof(buf));if(bytes0){write(fd,buf,bytes);// 回声}elseif(bytes0){close(fd);// 连接关闭或出错epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);}}}}return0;}三、 实现关键点总结非阻塞设置‌无论是 Java 的 configureBlocking(false) 还是 C 的 fcntl(O_NONBLOCK)‌必须‌将 Channel/FD 设置为非阻塞模式否则读写操作仍会阻塞线程导致多路复用失效。事件移除‌在 Java 中selectedKeys 不会自动清空必须手动 iterator.remove()否则下次循环会重复处理同一事件。ET 模式处理‌若使用 Edge Triggered (ET) 模式如 C 示例中的 EPOLLET必须‌一次性读完所有数据‌循环 read 直到返回 EAGAIN否则剩余数据将不会再次触发通知导致数据滞留。异常处理‌网络环境复杂必须妥善处理连接断开、读写异常及时关闭资源并从 Selector/Epoll 中注销防止内存泄漏或无效事件堆积。通过上述实现单线程即可高效管理成千上万个并发连接这正是高并发网络服务的核心基础。