C多线程服务器客户端通信异常问题排查求助
问题描述
我正在参照一个GitHub项目实现C多线程服务器客户端程序,期望实现无限循环收发消息,直到客户端发送SIGINT、SIGSTOP信号或消息“stop”时终止。目前仅客户端发送的第一条消息能被服务器接收,服务器会回复“abcd”;从第二条消息开始,客户端无法收到服务器回复,第三条消息发送后客户端自动退出,而服务器仍在运行。我怀疑读写操作存在问题,以下是我的代码及终端输出:
server.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/wait.h> #include <signal.h> #include <pthread.h> #include <sys/time.h> #include <sys/syscall.h> //thread_id #define PORT 7507 #define BUFFER_SIZE 2048 #define BACKLOG 10 #define gettid() ((pid_t)syscall(SYS_gettid)) typedef struct pthread_args { int sockfd; struct sockaddr_in client_address; clock_t start, stop; char* message; float seconds; float delay; } pthread_args; void* handler(void* args) { pthread_args* pthread_arg = (pthread_args*)args; int sockfd = pthread_arg->sockfd; struct sockaddr_in client_address = pthread_arg->client_address; float seconds = pthread_arg->seconds; float delay = pthread_arg->delay; char* message = pthread_arg->message; free(args); printf("Message received from client (thread id=%d): %s\n", gettid(), pthread_arg->message); if((write(sockfd, "abcd", 4))<0) { perror("write"); exit(EXIT_FAILURE); } close(sockfd); return NULL; } int main() { int sockfd, new_sockfd, bytes; char buffer[BUFFER_SIZE]; struct sockaddr_in server_address; pthread_attr_t pthread_attr; pthread_args* pthread_arg; pthread_t thread; socklen_t client_len; memset(&server_address, 0, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_port = htons(PORT); server_address.sin_addr.s_addr = INADDR_ANY; //socket if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(EXIT_FAILURE); } //bind if (bind(sockfd, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) { perror("bind"); exit(EXIT_FAILURE); } //listen if (listen(sockfd, BACKLOG) == -1) { perror("listen"); exit(EXIT_FAILURE); } if (pthread_attr_init(&pthread_attr) != 0) { perror("pthread_attr_init"); exit(EXIT_FAILURE); } if (pthread_attr_setdetachstate(&pthread_attr, PTHREAD_CREATE_DETACHED) != 0) { perror("pthread_attr_setdetachstate"); exit(EXIT_FAILURE); } while(1) { pthread_arg = (pthread_args*)malloc(1*sizeof(pthread_args*)); if(!pthread_arg) { perror("malloc"); exit(EXIT_FAILURE); } //accept client_len = sizeof(pthread_arg->client_address); new_sockfd = accept(sockfd, (struct sockaddr*)&pthread_arg->client_address, &client_len); if(new_sockfd == -1) { perror("accept"); free(pthread_arg); exit(EXIT_FAILURE); } bzero(buffer, BUFFER_SIZE-1); if((read(new_sockfd, &buffer, BUFFER_SIZE-1))<0) { perror("read"); exit(EXIT_FAILURE); } pthread_arg->sockfd = new_sockfd; pthread_arg->message=(char*)malloc(strlen(buffer)); if(!pthread_arg->message) { perror("malloc"); exit(EXIT_FAILURE); } strcpy(pthread_arg->message, buffer); pthread_arg->seconds = 0; pthread_arg->delay = 0; if(pthread_create(&thread, &pthread_attr, handler, (void*)pthread_arg) != 0) { perror("pthread_create"); free(pthread_arg); exit(EXIT_FAILURE); } } shutdown(sockfd, SHUT_RDWR); close(sockfd); return 0; }
client.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #define PORT 7507 #define SERVER "localhost" #define BUFFER_SIZE 2048 int main(int argc, char *argv[]) { int sockfd, bytes; struct hostent* server_host; struct sockaddr_in server_address; char message_sent[BUFFER_SIZE]; char message_received[BUFFER_SIZE]; if(argc>1) { printf("too many arguments\n"); exit(EXIT_FAILURE); } server_host = gethostbyname(SERVER); memset(&server_address, 0, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_port = htons(PORT); memcpy(&server_address.sin_addr.s_addr, server_host->h_addr, server_host->h_length); //socket if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(EXIT_FAILURE); } //connect if (connect(sockfd, (struct sockaddr *)&server_address, sizeof server_address) == -1) { perror("connect"); exit(EXIT_FAILURE); } while(1) { bzero(message_sent, sizeof(message_sent)); printf("Your message: "); fgets(message_sent, BUFFER_SIZE-1, stdin); message_sent[strcspn(message_sent, "\n")]=0; //sending to server if((write(sockfd, &message_sent, strlen(message_sent)))<0) { perror("write"); exit(EXIT_FAILURE); } bzero(message_received, sizeof(message_received)); //reading from server if ((read(sockfd, &message_received, BUFFER_SIZE-1))<0) { perror("read"); exit(EXIT_FAILURE); } printf("Server says: %s\n", message_received); } close(sockfd); return 0; }
终端输出
客户端:
Your message: message1 Server says: abcd Your message: message2 Server says: Your message: message3 Server says: *客户端自动停止*
服务器:
Message received from client (thread id=x): message1 *此处停止,未收到message2和message3,但服务器仍运行*
问题分析与解决方案
核心问题
- 内存分配错误:服务器
main函数中malloc(1*sizeof(pthread_args*))仅分配了指针大小的内存,远小于结构体实际大小,导致内存越界破坏数据。 - 连接处理逻辑错误:服务器每接收一个连接后只处理一次消息就关闭连接,但客户端是在同一个连接上持续发消息,连接关闭后后续读写必然失败。
- 野指针访问:
handler函数中free(args)后仍访问pthread_arg->message,属于未定义行为,会导致程序异常。 - TCP流边界未处理:TCP是字节流,代码未添加消息结束符,也未处理粘包/拆包,导致消息读取不完整。
修复步骤
1. 修复服务器内存分配
将main中的内存分配代码改为:
pthread_arg = (pthread_args*)malloc(sizeof(pthread_args));
2. 重构服务器连接处理逻辑
服务器应为每个客户端连接创建线程,在线程内持续循环处理消息,而非单次处理后关闭连接:
void* handler(void* args) { pthread_args* pthread_arg = (pthread_args*)args; int sockfd = pthread_arg->sockfd; char buffer[BUFFER_SIZE]; ssize_t bytes_read; free(args); // 先释放结构体内存,避免后续误访问 // 持续处理该连接的消息 while (1) { bzero(buffer, BUFFER_SIZE); bytes_read = read(sockfd, buffer, BUFFER_SIZE-1); if (bytes_read < 0) { perror("read"); break; } else if (bytes_read == 0) { printf("Client disconnected (thread id=%d)\n", gettid()); break; } // 处理终止命令 if (strcmp(buffer, "stop") == 0) { printf("Client sent stop command (thread id=%d)\n", gettid()); write(sockfd, "stopping", 8); break; } printf("Received from client (thread id=%d): %s\n", gettid(), buffer); // 回复时添加换行符,确保客户端能识别完整消息 if (write(sockfd, "abcd\n", 5) < 0) { perror("write"); break; } } close(sockfd); return NULL; }
同时删除main中提前读取消息的代码,将连接直接交给线程处理:
// 移除main中的这段代码: /* bzero(buffer, BUFFER_SIZE-1); if((read(new_sockfd, &buffer, BUFFER_SIZE-1))<0) { perror("read"); exit(EXIT_FAILURE); } pthread_arg->message=(char*)malloc(strlen(buffer)); if(!pthread_arg->message) { perror("malloc"); exit(EXIT_FAILURE); } strcpy(pthread_arg->message, buffer); */ // 改为初始化结构体字段 pthread_arg->sockfd = new_sockfd; pthread_arg->message = NULL; pthread_arg->seconds = 0; pthread_arg->delay = 0;
3. 修复客户端读写逻辑
- 修正
write的参数(数组名本身就是指针,无需取地址),并发送字符串结束符:
if(write(sockfd, message_sent, strlen(message_sent)+1) <0) { perror("write"); exit(EXIT_FAILURE); }
- 处理服务器关闭连接的情况,并确保字符串结束:
bytes = read(sockfd, message_received, BUFFER_SIZE-1); if (bytes < 0) { perror("read"); exit(EXIT_FAILURE); } else if (bytes == 0) { printf("Server closed connection\n"); break; } message_received[bytes] = '\0';
- 添加客户端终止逻辑:
// 发送消息后判断是否终止 if (strcmp(message_sent, "stop") == 0) { printf("Stopping client\n"); break; } // 接收回复后判断是否终止 if (strcmp(message_received, "stopping") == 0) { printf("Server acknowledged stop command\n"); break; }
4. 信号处理(可选)
添加信号处理函数,响应SIGINT、SIGSTOP信号:
#include <signal.h> volatile sig_atomic_t stop = 0; void handle_signal(int sig) { stop = 1; } int main() { // ... 其他代码 ... signal(SIGINT, handle_signal); signal(SIGSTOP, handle_signal); while(!stop) { // ... 原有循环代码 ... if (stop) break; } close(sockfd); return 0; }
内容的提问来源于stack exchange,提问作者Bodvub
相关产品推荐
相关产品推荐

