You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 20:58:23