for(inti=0;i<nfds;++i){if(events[i].data.fd==listen_sockfd){// --- 处理新连接 ---structsockaddr_inclient_addr{};socklen_t client_len=sizeof(client_addr);intconn_sockfd=accept(listen_sockfd,(structsockaddr*)&client_addr,&client_len);if(conn_sockfd<0){perror("accept failed");continue;}std::cout<<"New connection from "<<inet_ntoa(client_addr.sin_addr)<<":"<<ntohs(client_addr.sin_port)<<std::endl;set_non_blocking(conn_sockfd);// 新连接的套接字也必须是非阻塞的// 将新连接的套接字加入epoll监控event.events=EPOLLIN|EPOLLET;// 使用边缘触发(ET)模式event.data.fd=conn_sockfd;if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,conn_sockfd,&event)<0){perror("epoll_ctl add client failed");close(conn_sockfd);}}else{// --- 处理客户端数据 ---intconn_sockfd=events[i].data.fd;charbuffer[1024];ssize_t count=read(conn_sockfd,buffer,sizeof(buffer));if(count<0){// 非阻塞模式下,EAGAIN或EWOULDBLOCK表示数据已读完if(errno==EAGAIN||errno==EWOULDBLOCK){// std::cerr << "Read finished for fd " << conn_sockfd << std::endl;}else{perror("read failed");}}elseif(count==0){// 客户端关闭连接std::cout<<"Client "<<conn_sockfd<<" disconnected."<<std::endl;epoll_ctl(epoll_fd,EPOLL_CTL_DEL,conn_sockfd,nullptr);close(conn_sockfd);}else{// 收到数据,回显给客户端// 注意:在实际高性能场景中,write也可能需要处理EAGAINif(write(conn_sockfd,buffer,count)<0){perror("write failed");}}}}epoll 事件处理循环
这是整个 epoll 服务器的「业务核心」!前面所有代码(socket、bind、listen、epoll_create)都是为了这一段代码服务。
整段代码的核心作用(大白话)
遍历所有触发事件的 socket: 如果是【监听套接字】有动静 → 有新客户上门,接待连接 如果是【普通客户端】有动静 → 客户发消息了,读取并回复这就是高并发服务器处理成千上万客户端的逻辑!
第一部分:遍历事件
for(inti=0;i<nfds;++i){nfds:epoll_wait返回的就绪事件数量- 作用:把这次所有触发了事件的 socket挨个处理一遍
第二部分:判断事件类型(核心分支)
if(events[i].data.fd==listen_sockfd){原理:
我们之前把监听套接字加入了 epoll 监控。
当它触发EPOLLIN事件 =内核队列里有新连接等待被接收!
所以进入这个if,一定是来新客户端了!
分支1:处理新客户端连接
1. 接收连接(accept)
intconn_sockfd=accept(listen_sockfd,...);- 作用:从内核队列里取出一个连接,创建一个通信套接字
- listen_sockfd:只负责接客
- conn_sockfd:专门用来和这个客户端收发数据(一对一专属)
2. 打印客户端信息
inet_ntoa(client_addr.sin_addr)// IP转字符串ntohs(client_addr.sin_port)// 端口转主机序- 打印:客户端从哪个IP、哪个端口连过来的
3. 设置非阻塞(超级重要)
set_non_blocking(conn_sockfd);- 只要用 epoll(ET模式),所有套接字必须是非阻塞!
- 保证
read/write不会卡住服务器
4. 把新客户端加入 epoll 监控
event.events=EPOLLIN|EPOLLET;// 监控可读 + 边缘触发event.data.fd=conn_sockfd;// 带上通信套接字epoll_ctl(ADD...);// 加入监控列表重点:EPOLLET 边缘触发
- ET 模式:epoll最高性能模式
- 只在数据从无到有时通知一次
- 效率极高,是高并发服务器标配
至此:
新客户端连接成功!epoll 开始监控它的消息。
分支2:处理客户端数据(else)
进入 else =不是新连接,是已连接客户端发数据来了!
1. 获取客户端套接字
intconn_sockfd=events[i].data.fd;- 拿到和这个客户端通信的专属套接字
2. 读取数据
ssize_tcount=read(conn_sockfd,buffer,sizeof(buffer));- 从客户端读取消息到缓冲区
- 返回值
count:读到的字节数
3. 三种返回值处理
① count < 0:读取出错
if(errno==EAGAIN){// 正常!非阻塞ET模式下,数据读完了}else{// 真出错了}② count == 0:客户端断开连接
// 从epoll移除 + 关闭套接字epoll_ctl(DEL...);close(conn_sockfd);- 客户端关闭了连接,服务器清理资源
③ count > 0:读到有效数据
write(conn_sockfd,buffer,count);- 回显服务器:收到什么,原样发回给客户端
完整流程示意图(秒懂)
- 客户端发起连接
- 监听套接字触发 EPOLLIN
- accept 接收,得到 conn_sockfd
- 将 conn_sockfd 加入 epoll
- 客户端发消息
- conn_sockfd 触发 EPOLLIN
- read 读取,write 回写
- 客户端断开 → close 清理
极简总结(封神级)
- if (listen_sockfd):处理新连接,accept + 加入epoll
- else:处理客户端消息,read + write
- EPOLLET:高性能边缘触发
- 非阻塞:所有socket必须非阻塞
- 核心:一个线程,处理成千上万客户端,不卡顿!
整个服务器代码完整闭环
你现在已经看完了Linux高并发epoll服务器全部代码:
- socket → 创建套接字
- setsockopt → 端口复用
- bind → 绑定IP端口
- listen → 开始监听
- epoll_create1 → 创建监控
- epoll_ctl(ADD) → 加入监听socket
- epoll_wait → 等待事件
- for循环处理→ 你刚学的核心逻辑
这就是 Nginx、Redis、SRS 等高并发中间件的底层原理!
最终一句话总结
这段代码,让单线程服务器,能同时处理几万客户端连接和消息,这就是 epoll 的威力!