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

TCP多线程C语言聊天应用客户端无法接收消息求助

C语言多线程群聊应用转发问题求助

我正在学习C语言多线程编程,尝试开发一个简单的群聊应用,设计逻辑是客户端连接中心服务器,服务器将客户端发送的消息转发给其他在线客户端。目前服务器能正常接收客户端消息,也已将客户端Socket文件描述符、接收缓冲区等参数传递给对应的转发函数,但客户端始终无法收到转发的消息。我已经尝试逐步添加日志排查,但没发现逻辑错误,由于对多线程编程不够熟悉,怀疑可能是竞态条件、线程管理问题或是低级代码错误,特此求助。

相关代码

sock_server.c

#include "sock_util.h"

#define SRV_ADDR ""
#define SRV_PORT 2357
#define LST_BACKLOG 2

int main() {
    int server_sfd;
    if ((server_sfd = runTCPServer(SRV_ADDR, SRV_PORT, LST_BACKLOG)) < 0) {
        return -1;
    }

    runThreadConnections(server_sfd);

    shutdown(server_sfd, SHUT_RDWR);
    LOG_INFO_MESSAGE("Application closed.\n");  

    return 0;
}

sock_client.c

#include "sock_util.h"

#define SRV_ADDR "127.0.0.1"
#define SRV_PORT 2357

#define NEWCONN_CMD "//newconn\n"
#define ENDCONN_CMD "//endconn\n"

void *threadMessageReceiver(void *sfd) {
    char buffer[1024];
    int client_sfd = *(int *)sfd;
    ssize_t char_count;

    free(sfd);

    LOG_INFO_MESSAGE("Receiving messages. [SFD %d].\n", client_sfd);

    while (true) {
        if ((char_count = recv(client_sfd, buffer, strlen(buffer), 0)) < 0) {
            printf("Error.\n");
            return NULL;
        }

        if (char_count == 0) {
            printf("Received nothing.\n");
            continue;
        }

        buffer[char_count] = 0;  // Add NULL character to end the string.
        LOG_INFO_MESSAGE("Message received: [ %s ]\n", buffer);
    }

    LOG_INFO_MESSAGE("No longer receiving messages.\n");
    return 0;
}

int runThreadMessageReceiver(int *client_sfd) {
    runInThread(threadMessageReceiver, client_sfd, sizeof(client_sfd));
    return 0;
}

int main() {
    int sfd = createTCPv4Socket();  
    struct sockaddr_in *addr = createIPv4Sockaddr(SRV_ADDR, SRV_PORT);  

    // connect to the address using the socket.
    if (connect(sfd, (struct sockaddr *) addr, sizeof(*addr)) == -1) {
        printf("- something went wrong: %s", strerror(errno));
        return -1; 
    }

    // receive messages assycronously
    // runThreadMessageReceiver(&sfd);
    
    // beginning of chat application
    char *line = NULL;
    size_t line_s = 0;  
    ssize_t char_count;

    while (true) {  
        if ((char_count = getline(&line, &line_s, stdin)) < 0) {
            return -1;
        }
        fflush(stdout);

        // finish conversation.
        if (strlen(line) == 1) {
            send(sfd, ENDCONN_CMD, strlen(ENDCONN_CMD), 0);
            break;
        };  
        
        if (send(sfd, line, char_count, 0) == -1) {
            return -1;
        }
    }   
    
    close(sfd);
    return 0;
}

sock_util.c 相关函数

int runInThread(void *(*routine)(void *), void *routine_arg, size_t arg_size) {
    /*
     Helper function to run any function with any argument in a sepate thread.
     The function will return -1 on error or the thread id on success.
    */

    // Allocate memory for the thread argument
    void *arg_copy = malloc(arg_size);
    if (!arg_copy) {
        LOG_ERROR_MESSAGE("Error with malloc(): %s.\n", strerror(errno));
        return -1;
    }

    // Copy the content of the argument into the allocated memory
    memcpy(arg_copy, routine_arg, arg_size);

    pthread_t id;
    if(pthread_create(&id, NULL, routine, arg_copy) < 0) {
        LOG_ERROR_MESSAGE("Error with pthread(): %s.\n", strerror(errno));
        return -1;
    }
    LOG_DEBUG_MESSAGE("Thread created succesfully: ID %02x.\n", id);

    return id;
}

int runThreadConnections(int sfd_srv) { 
    /*
     Start running each client connection in a separate thread.
     Client messages are handled within a thread in each client's 
     corresponding thread.
    */

    while (true) {
        struct acceptedConn *client_conn = acceptNewConn(sfd_srv);
        if (client_conn->error < 0) {
            printf("- Error with AcceptNewConn(): %s.\n", strerror(errno));
            return -1;
        }

        LOG_DEBUG_MESSAGE("Client connection accepted: SocketFD %d.\n", client_conn->sfd_client);

        int sfd_client = client_conn->sfd_client;
        int *sfd_client_ptr = &sfd_client;

        connections_list[connections_count++] = *client_conn; 
        LOG_DEBUG_MESSAGE("Connections list updated.\n");

        // Run threadNewConn() from a new thread pthread_create()
        LOG_DEBUG_MESSAGE("Calling runInThead() from runThreadConnections().\n");
        runInThread(threadConnections, sfd_client_ptr, sizeof(sfd_client));
    }
}

void *threadConnections(void *arg_sfd_client) {
    int sfd_client = *(int *)arg_sfd_client; 
    free(arg_sfd_client);

    listenConn(sfd_client); 
    close(sfd_client);

    return NULL;
}

int listenConn(int sfd_client) {
    /*
     Process a connection by listening to a received accepted connection from `acceptedConn()` using
     the `listen()^ function.
     Loop indifinitely until the connection is closed by the client or an error arise.
    */

    char buff_recv[1024];
    ssize_t recv_content = 0;
    while (true) {
        //BUG Verify the recv_content errno case and when recv_content = 1 || 0.

        if ( (recv_content = recv(sfd_client, buff_recv, 1024, 0)) < 0) {
            if (errno == 104) {  // if connection reset exit gracefully
                LOG_DEBUG_MESSAGE("Client disconnected. ClientFD %d.\n", sfd_client);
                break;
            }
            printf("- error with recv(): %s.\n", strerror(errno));
            return -1;
        }

        if(recv_content == 1 || recv_content == 0) {
            break;
        }

        buff_recv[recv_content - 1] = 0;  // We assume all received messages end with a breakline \n.

        LOG_DEBUG_MESSAGE("Client [SocketFD %d] message received: ", sfd_client);
        printf("[ %s ]\n", buff_recv);

        //TODO: Check for messages starting with "//" (Commands)
        if (strstr(buff_recv, "//") == NULL) {
            runThreadReply(buff_recv, sfd_client, 0);
        }
    }

    return 0;
} 

int runThreadReply(char* buffer, int sender_sfd, int receiver_sfd) {
    /*
     Send the sender_sfd received buffer to receiver_sfd.
     if receiver_sfd is 0, the buffer is sent to every client connected with the server.
    */
    LOG_DEBUG_MESSAGE("Calling runInThead() from runThreadReplies().\n");

    for (int i = 0; i < connections_count; i++) {
        int sfd_client = connections_list[i].sfd_client;

        // if (sfd_client == sender_sfd) continue;

        LOG_DEBUG_MESSAGE("Buffer from %d to %d.\n", sender_sfd, sfd_client);

        struct recv_info *info = malloc(sizeof(struct recv_info));
        info->recv_sfd = sfd_client;
        info->buffer = buffer;
        info->buffer_size = strlen(buffer);
        info->flags = 0;

        send(sfd_client, buffer, strlen(buffer), 0);

        if (sfd_client == receiver_sfd || receiver_sfd == 0) {
            // runInThread(threadReply, info, sizeof(struct recv_info));    
        }
    }
    
    return 0;
}

void *threadReply(void *recv_info) {
    struct recv_info info = *(struct recv_info *)recv_info;
    
    if (strlen(info.buffer) <= 0) {
        send(info.recv_sfd, info.buffer, strlen(info.buffer), info.flags);
    }

    LOG_DEBUG_MESSAGE("Message %s sended to %d.\n", info.buffer, info.recv_sfd);

    return NULL;
}

问题排查与修复建议

  • 客户端未启动接收线程:客户端main函数中runThreadMessageReceiver(&sfd);被注释,导致客户端没有后台线程监听服务器转发的消息。取消注释该行,让客户端能异步接收消息。
  • 客户端recv参数错误:threadMessageReceiver中recv(client_sfd, buffer, strlen(buffer), 0)的第三个参数错误,此时buffer未初始化,strlen(buffer)返回随机值,应改为sizeof(buffer) - 1,避免缓冲区溢出,同时保证留位置存字符串结束符。
  • 服务器消息截断错误:listenConn中buff_recv[recv_content - 1] = 0;假设消息以换行结尾,但如果客户端发送的消息没有换行,或者recv_content为0时会导致数组越界。正确做法是直接用buff_recv[recv_content] = 0;,前提是recv的长度是sizeof(buff_recv)-1,这样无论消息是否带换行,都能正确终止字符串。
  • 连接列表线程不安全:connections_list和connections_count在多线程环境下被读写(新连接添加、转发遍历),没有互斥锁保护,会导致竞态条件。添加pthread_mutex_t互斥锁,在修改和遍历连接列表时加锁。
  • threadReply逻辑反转:threadReply中if (strlen(info.buffer) <= 0)才发送消息,逻辑完全错误,应改为if (strlen(info.buffer) > 0)。
  • send返回值未检查:runThreadReply中直接调用send但未检查返回值,无法得知发送是否成功。应添加错误处理,比如:
    ssize_t sent = send(sfd_client, buffer, strlen(buffer), 0);
    if (sent < 0) {
        LOG_ERROR_MESSAGE("Failed to send to client %d: %s\n", sfd_client, strerror(errno));
    }
    
  • 跳过发送者自身:runThreadReply中注释掉了if (sfd_client == sender_sfd) continue;,导致消息会回发给发送者自己,取消注释该行,避免重复接收。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 04:10:54