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

C语言Socket通信问题:服务端进程无法接收客户端后续消息

问题排查:TCP长连接下服务端卡在accept、客户端发送无响应

我正在开发一个客户端-服务端项目,客户端采用多线程架构,计算完成后将结果发送至另一进程的服务端。需求是客户端仅建立1次Socket连接并发送多条消息,但目前首次发送正常后便停止;客户端无报错持续发送,服务端却卡在accept函数处无法接收后续消息。相关代码如下:


服务端代码

int listenToSocket(int p) {
    int socket_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len;
    SocketInterface data;

    // Create socket
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd == -1) {
        perror("Error creating socket");
        return -1;
    }

    // Prepare server address structure
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // Bind the socket to the specified port
    if (bind(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Error binding socket");
        close(socket_fd);
        return -1;
    }

    // Start listening on the socket
    if (listen(socket_fd, 1) < 0) {
        perror("Error listening on socket");
        close(socket_fd);
        return -1;
    }

    // Accept client connections and receive data continuously
    for(;; ) {
        client_len = sizeof(client_addr);

        client_fd = accept(socket_fd, (struct sockaddr *)&client_addr, &client_len);
        if (client_fd < 0) {
            perror("Error accepting client connection");
            continue;
        }


        // Receive data into the struct
        if (read(client_fd, &data, sizeof(data)) < 0) {
            perror("Error receiving data");
            close(client_fd);
            continue;
        }

        if(data.over){
                break;
        }

        // Process the received struct data
        printf("%d      %3.2f        %3.2f            %50s\n", data.fd.n, data.fd.avg, data.fd.std, data.fd.path);

        //close(client_fd);
        memset(&client_addr, 0, sizeof (client_addr));
    }

    // Close the socket
    close(socket_fd);

    return 0;
}

客户端Socket创建代码(main.c)

// create socket connection
int sockfd;
struct sockaddr_in server_addr;

// Create socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
    perror("Error opening socket");
}

// Configure server address
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;

// Connect to server
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
    close(sockfd);
    perror("Error connecting to server");
    kill(pid, SIGTERM);
    return -1;
}

客户端线程发送函数

int sendThroughSocket(FileData *fd, int sockfd) {


    SocketInterface isock;
    if(fd != NULL){
        strcpy(isock.fd.path, fd->path);
        isock.fd.avg = fd->avg;
        isock.fd.std = fd->std;
        isock.fd.n = fd->n;
        isock.over = 0;
    }

    if (fd == NULL) {
        isock.over = 1;
    }

    if(fd != NULL){
        printf("Filename: %s, avg: %f std: %f\n",fd->path, fd->avg, fd->std);
    }else{
        printf("NUll is being sent\n");
    }
    if (write(sockfd, &isock, sizeof(isock)) < 0) {
        printf("Over: %d\n", isock.over);
        perror("Error sending data");
        return -1;
    }

    if(isock.over == 1){
        close(sockfd);
    }

    return 0;
}

核心问题分析

  • 服务端逻辑错误:重复调用accept
    服务端的for(;;)循环中每次都会执行accept,意味着处理完第一次连接的一条消息后,会再次阻塞等待新连接。但客户端用同一个长连接发送多条消息,不会发起新连接,导致服务端卡在accept。
  • 服务端消息接收逻辑不连续
    当前服务端在accept后仅调用一次read,处理完一条消息就回到循环开头执行accept,没有持续读取同一个连接的后续消息。
  • 多线程发送的线程安全隐患
    多个线程共用同一个sockfd发送数据,可能导致TCP数据流粘包或发送操作互相干扰。

修复方案

服务端修改:固定连接,持续读取消息

将accept移出循环,接受一次连接后,持续从该连接读取消息直到结束:

int listenToSocket(int p) {
    int socket_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len;
    SocketInterface data;

    // Create socket
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd == -1) {
        perror("Error creating socket");
        return -1;
    }

    // Prepare server address structure
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // Bind the socket to the specified port
    if (bind(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Error binding socket");
        close(socket_fd);
        return -1;
    }

    // Start listening on the socket
    if (listen(socket_fd, 1) < 0) {
        perror("Error listening on socket");
        close(socket_fd);
        return -1;
    }

    // 仅接受一次客户端长连接
    client_len = sizeof(client_addr);
    client_fd = accept(socket_fd, (struct sockaddr *)&client_addr, &client_len);
    if (client_fd < 0) {
        perror("Error accepting client connection");
        close(socket_fd);
        return -1;
    }

    // 持续从同一连接读取消息
    for(;; ) {
        memset(&data, 0, sizeof(data)); // 清空结构体,避免残留数据
        ssize_t read_len = read(client_fd, &data, sizeof(data));
        if (read_len < 0) {
            perror("Error receiving data");
            break;
        } else if (read_len == 0) {
            printf("Client closed connection\n");
            break;
        }

        if(data.over){
                printf("Received end signal\n");
                break;
        }

        printf("%d      %3.2f        %3.2f            %50s\n", data.fd.n, data.fd.avg, data.fd.std, data.fd.path);
    }

    close(client_fd);
    close(socket_fd);

    return 0;
}

客户端优化:线程安全发送+完善错误处理

  1. 给发送操作加互斥锁,避免并发干扰:
#include <pthread.h>

pthread_mutex_t sock_mutex = PTHREAD_MUTEX_INITIALIZER;

int sendThroughSocket(FileData *fd, int sockfd) {
    SocketInterface isock;
    memset(&isock, 0, sizeof(isock)); // 初始化结构体,清除垃圾数据

    if(fd != NULL){
        strcpy(isock.fd.path, fd->path);
        isock.fd.avg = fd->avg;
        isock.fd.std = fd->std;
        isock.fd.n = fd->n;
        isock.over = 0;
    } else {
        isock.over = 1;
    }

    if(fd != NULL){
        printf("Filename: %s, avg: %f std: %f\n",fd->path, fd->avg, fd->std);
    }else{
        printf("Null is being sent\n");
    }

    // 加锁保证发送操作原子性
    pthread_mutex_lock(&sock_mutex);
    ssize_t write_len = write(sockfd, &isock, sizeof(isock));
    pthread_mutex_unlock(&sock_mutex);

    if (write_len < 0) {
        printf("Over: %d\n", isock.over);
        perror("Error sending data");
        return -1;
    } else if (write_len != sizeof(isock)) {
        printf("Warning: only sent %zd bytes out of %zd\n", write_len, sizeof(isock));
        // 可补充剩余数据的发送逻辑
    }

    if(isock.over == 1){
        close(sockfd);
        pthread_mutex_destroy(&sock_mutex);
    }

    return 0;
}
  1. 完善Socket创建的错误处理:
// Create socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
    perror("Error opening socket");
    kill(pid, SIGTERM);
    return -1;
}

额外注意事项

  • TCP粘包问题:当前固定结构体长度的读取方式可工作,若后续结构体有变动,建议在消息前添加长度字段,避免粘包导致解析错误。
  • 连接异常处理:服务端和客户端需处理连接中断情况,比如read返回0(客户端关闭)或-1(错误)时,及时关闭套接字并退出循环。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 16:52:57