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
相关产品推荐
相关产品推荐

