Epoll服务器无法检测客户端断开的问题排查与优化求助
Epoll服务器检测客户端断开的问题修复
核心问题分析
仅依赖EPOLLERR/EPOLLHUP事件检测客户端断开并不全面,客户端正常退出时,服务器端recv()返回0是最可靠的断开信号;另外原判断语句虽逻辑正确,但可优化写法提升可读性。
关键改进点
- 优化事件判断逻辑:将
events[i].events & EPOLLERR || events[i].events & EPOLLHUP改为events[i].events & (EPOLLERR | EPOLLHUP),用位或合并掩码,逻辑更清晰。 - 处理读操作返回值:触发
EPOLLIN事件时,调用recv(),返回0表示客户端正常断开;返回-1时,区分errno为EAGAIN(无数据可读)和其他异常错误。 - 及时清理资源:检测到断开后,将文件描述符从epoll实例移除并关闭,避免资源泄漏。
- 确保事件注册正确:使用
epoll_ctl注册事件时包含EPOLLIN,边缘触发(EPOLLET)模式下需循环读取直到EAGAIN。
修复后的server.cc代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <sys/epoll.h> #include <arpa/inet.h> #include <errno.h> #include <fcntl.h> #define MAX_EVENTS 1024 #define BUF_SIZE 1024 int main() { int listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd == -1) { perror("socket create failed"); exit(EXIT_FAILURE); } int opt = 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(8080); if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("bind failed"); close(listen_fd); exit(EXIT_FAILURE); } if (listen(listen_fd, 128) == -1) { perror("listen failed"); close(listen_fd); exit(EXIT_FAILURE); } int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1 failed"); close(listen_fd); exit(EXIT_FAILURE); } struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = listen_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) == -1) { perror("epoll_ctl add listen_fd failed"); close(listen_fd); close(epoll_fd); exit(EXIT_FAILURE); } struct epoll_event events[MAX_EVENTS]; while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait failed"); break; } for (int i = 0; i < nfds; ++i) { if (events[i].data.fd == listen_fd) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len); if (conn_fd == -1) { perror("accept failed"); continue; } printf("New client connected: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 设置非阻塞模式 int flags = fcntl(conn_fd, F_GETFL, 0); fcntl(conn_fd, F_SETFL, flags | O_NONBLOCK); ev.events = EPOLLIN | EPOLLET; ev.data.fd = conn_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev) == -1) { perror("epoll_ctl add conn_fd failed"); close(conn_fd); continue; } } else { int conn_fd = events[i].data.fd; // 处理异常断开事件 if (events[i].events & (EPOLLERR | EPOLLHUP)) { printf("Client %d disconnected (error/hangup)\n", conn_fd); epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn_fd, NULL); close(conn_fd); continue; } // 处理读事件 if (events[i].events & EPOLLIN) { char buf[BUF_SIZE]; ssize_t n; while ((n = recv(conn_fd, buf, BUF_SIZE - 1, 0)) > 0) { buf[n] = '\0'; printf("Received from client %d: %s\n", conn_fd, buf); } if (n == 0) { // 客户端正常断开 printf("Client %d disconnected normally\n", conn_fd); epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn_fd, NULL); close(conn_fd); } else if (n == -1) { if (errno != EAGAIN) { // 异常错误导致断开 perror("recv failed"); epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn_fd, NULL); close(conn_fd); } // EAGAIN表示当前无数据,边缘触发下无需额外处理 } } } } } close(listen_fd); close(epoll_fd); return 0; }
客户端client.cc代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #define BUF_SIZE 1024 int main() { int sock_fd = socket(AF_INET, SOCK_STREAM, 0); if (sock_fd == -1) { perror("socket create failed"); exit(EXIT_FAILURE); } struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); server_addr.sin_port = htons(8080); if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("connect failed"); close(sock_fd); exit(EXIT_FAILURE); } const char* msg = "Hello from client!"; ssize_t n = send(sock_fd, msg, strlen(msg), 0); if (n == -1) { perror("send failed"); } else { printf("Sent message: %s\n", msg); } // 发送后立即退出,触发断开 close(sock_fd); return 0; }
编译运行说明
- 编译服务器:
g++ server.cc -o server - 编译客户端:
g++ client.cc -o client - 启动服务器:
./server - 启动客户端:
./client - 服务器会输出客户端连接、消息接收及断开的完整日志。
内容的提问来源于stack exchange,提问作者marsevilspirit
相关产品推荐
相关产品推荐

