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

TCP服务器仅读取首次消息问题:原因排查求助

问题原因分析与解决方案

嘿,我一眼就揪出问题所在了——你的服务器现在的逻辑是每接受一个新连接,只读取一次客户端发来的数据,然后就彻底把这个客户端连接丢在一边,转头去等下一个新连接了!这就是为什么你只能收到每条新连接的第一条消息,后续消息没人处理的核心原因。

具体问题拆解

  • 你把监听套接字fd_serv设置为非阻塞模式,accept会立即返回,但你完全没处理accept返回错误的情况(比如没有新连接时,accept会返回-1,errno为EAGAIN或EWOULDBLOCK),不过这不是最关键的。
  • 核心逻辑缺陷:当accept成功拿到客户端套接字fd_client_serv后,你只执行了一次read,然后直接回到循环顶部再次调用accept。这个客户端套接字之后就被程序彻底遗忘了,没有任何后续逻辑去读取它发来的新数据,也没有关闭它,时间长了还会造成套接字资源泄漏。

解决方案

方案1:单客户端持续处理(简单但无法同时处理多客户端)

如果你暂时只需要处理单个客户端的连续消息,可以在拿到客户端套接字后,进入一个子循环,持续读取数据直到客户端断开连接:

void start_server(void) { 
    int fd_serv, fd_client_serv, bytes_read; 
    struct sockaddr_in client_serv_addr; 
    char buf[2048]; 
    socklen_t sin_len_serv = sizeof(client_serv_addr); 

    fd_serv = initialize_server(get_serving_port()); 
    // 监听套接字非阻塞,但客户端套接字默认是阻塞的,这里可以保持默认
    // fd_set_blocking(fd_serv, 0); 

    while(1) { 
        fd_client_serv = accept(fd_serv, (struct sockaddr *) &client_serv_addr, &sin_len_serv); 
        // 处理accept错误:没有新连接时跳过
        if (fd_client_serv == -1) {
            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                perror("accept error");
            }
            usleep(1000); // 避免空循环占用CPU
            continue;
        }
        puts("New client connected!!!"); 
        write(fd_client_serv, "thank you for connecting", 24); 

        // 子循环:持续读取该客户端的消息
        while(1) {
            memset(buf, 0, sizeof(buf)); 
            // 留一个位置给'\0',避免字符串越界
            bytes_read = read(fd_client_serv, buf, sizeof(buf)-1); 
            if (bytes_read == -1) {
                perror("read error");
                break;
            } else if (bytes_read == 0) {
                puts("Client disconnected");
                break;
            }
            printf("Received %d bytes: %s\n", bytes_read, buf); 
            fflush(stdout); 
        }
        close(fd_client_serv); // 关闭客户端套接字,释放资源
    } 
}

方案2:IO多路复用(推荐,支持多客户端同时连接)

如果需要同时处理多个客户端的消息,推荐使用select实现IO多路复用,这样可以同时监听监听套接字(等待新连接)和所有已连接的客户端套接字(等待消息):

#include <sys/select.h>

void start_server(void) { 
    int fd_serv, fd_client_serv, bytes_read; 
    struct sockaddr_in client_serv_addr; 
    char buf[2048]; 
    socklen_t sin_len_serv = sizeof(client_serv_addr); 
    fd_set read_fds;
    int max_fd;

    fd_serv = initialize_server(get_serving_port()); 
    fd_set_blocking(fd_serv, 0); // 监听套接字设为非阻塞

    FD_ZERO(&read_fds);
    FD_SET(fd_serv, &read_fds);
    max_fd = fd_serv;

    while(1) { 
        fd_set temp_fds = read_fds; // 每次select都要重新设置,因为select会修改集合
        int ret = select(max_fd + 1, &temp_fds, NULL, NULL, NULL);
        if (ret == -1) {
            perror("select error");
            exit(1);
        }

        // 检查监听套接字是否有新连接
        if (FD_ISSET(fd_serv, &temp_fds)) {
            fd_client_serv = accept(fd_serv, (struct sockaddr *) &client_serv_addr, &sin_len_serv); 
            if (fd_client_serv == -1) {
                if (errno != EAGAIN && errno != EWOULDBLOCK) {
                    perror("accept error");
                }
                continue;
            }
            puts("New client connected!!!"); 
            write(fd_client_serv, "thank you for connecting", 24); 
            fd_set_blocking(fd_client_serv, 0); // 客户端套接字也设为非阻塞
            FD_SET(fd_client_serv, &read_fds);
            if (fd_client_serv > max_fd) {
                max_fd = fd_client_serv;
            }
        }

        // 检查已连接的客户端是否有消息
        for (int i = fd_serv + 1; i <= max_fd; i++) {
            if (FD_ISSET(i, &temp_fds)) {
                memset(buf, 0, sizeof(buf)); 
                bytes_read = read(i, buf, sizeof(buf)-1); 
                if (bytes_read == -1) {
                    if (errno != EAGAIN && errno != EWOULDBLOCK) {
                        perror("read error");
                        close(i);
                        FD_CLR(i, &read_fds);
                    }
                    continue;
                } else if (bytes_read == 0) {
                    printf("Client %d disconnected\n", i);
                    close(i);
                    FD_CLR(i, &read_fds);
                    // 更新max_fd,避免无效遍历
                    if (i == max_fd) {
                        while (max_fd >= 0 && !FD_ISSET(max_fd, &read_fds)) {
                            max_fd--;
                        }
                    }
                    continue;
                }
                printf("Client %d sent %d bytes: %s\n", i, bytes_read, buf); 
                fflush(stdout); 
            }
        }
    } 
}

额外注意点

  • 处理非阻塞套接字的read/write时,一定要判断返回值和errno:如果返回-1且errno是EAGAIN或EWOULDBLOCK,这不是错误,只是当前没有数据可读写,稍后再试即可。
  • 无论哪种方案,客户端断开连接后一定要调用close()关闭套接字,避免资源泄漏。

内容的提问来源于stack exchange,提问作者Darren Col

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:34:07