C语言TCP实现:recv长消息接收负数,需整包打印
TCP客户端/服务器实现问题排查与修复
问题背景
需要用C语言实现TCP客户端与服务器,核心需求如下:
- 客户端可指定要发送的消息数量及单条消息长度(消息为
int类型) - 服务器需接收完整内容后以十六进制格式打印,禁止分段输出
- 示例效果:客户端发送「消息数量:4,单条长度:6」,服务器应输出:
RECV_NO: 0, Length: 24, Content: 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3
当前存在的问题:
- 长消息场景下
recv函数返回负数,导致程序异常崩溃 - 用头部标识消息总数的方案,在消息数量超过10条时完全失效
原代码
client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> unsigned int addHeader(int x, int y) { int pow = 10; while(y >= pow) pow *= 10; return x * pow + y; } int sendx(int sock, int num, int len, int par) { char message[10000]; unsigned int newTcp = addHeader(par, num); memset(message, newTcp, len); return send(sock, &message, len, 0); } int main() { struct sockaddr_in myaddr; myaddr.sin_family = AF_INET; myaddr.sin_port = htons(50000); myaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); int socketId = -1, sockConnect = -1, sockSend = -1; int sendLen = 0, count = 0; socketId = socket(AF_INET, SOCK_STREAM, 0); if (socketId < 0) { printf("socket creation failed"); return 1; } printf("Socket is created.\n"); sockConnect = connect(socketId, (struct sockaddr*) &myaddr, sizeof(struct sockaddr)); if(sockConnect < 0) { printf("connect failed\n"); return 1; } printf("Sucessfully conected with server.\n"); while(1) { int sendNo = 0; printf("Enter the number of messages to be sent: "); scanf("%d", &count); printf("Enter the length: "); scanf("%d", &sendLen); int total = count*sendLen; for(int i = 0; i < count; i++) { sockSend = sendx(socketId, sendNo, sendLen, total); if(sockSend < 0) { printf("send failed"); return 1; } sendNo++; } } close(socketId); return 0; }
server.c
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #define MAXBUF 64000 char totalMsg[MAXBUF]; int recvx(int accept, char *msg, int buf, int par) { totalMsg[0] = '\0'; char* p = totalMsg; char str[12]; size_t recvd = -1; recvd = recv(accept, msg, buf, par); int head = msg[0]/10; ssize_t bytesRemaining = head - recvd; if(bytesRemaining == 0) { for(int i = 0; i < recvd; i++) { sprintf(str, "0x%X ", msg[i] % 10); strcat(totalMsg, str); } } else { while(bytesRemaining > 0) { recvd = recv(accept, msg, buf, par); printf("recvd in while: %ld\n", recvd); for(int i = 0; i < recvd; i++) { printf("msg[i]: %d\n", msg[i]); sprintf(str, "0x%X ", msg[i] % 10); strcat(totalMsg, str); } bytesRemaining -= recvd; // keep track of bytes left p += recvd; // advance buffer pointer } } return recvd; } int main() { struct sockaddr_in myaddr; myaddr.sin_family = AF_INET; myaddr.sin_port = htons(50000); myaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); int socketId = -1, recvLen = -1; int sockBind = -1, sockListen = -1, sockAccept = -1, recvNo = 0; socketId = socket(AF_INET, SOCK_STREAM, 0); if (socketId < 0) { printf("socket creation failed"); return 1; } printf("Socket created.\n"); sockBind = bind(socketId, (struct sockaddr*) &myaddr, sizeof(struct sockaddr)); if(sockBind < 0) { perror("bind failed\n"); return 1; } printf("Successful bind.\n"); sockListen = listen(socketId, 5); if(sockListen < 0) { printf("listen failed\n"); return 1; } printf("Waiting for incoming connections...\n"); unsigned int srcAddrLen = sizeof(struct sockaddr); sockAccept = accept(socketId, (struct sockaddr*) &myaddr, &srcAddrLen); if (sockAccept < 0) { perror("accept failed\n"); return 1; } printf("Connection accepted.\n"); while(1) { char client_message[MAXBUF]; memset(client_message, '0', MAXBUF); recvLen = recvx(sockAccept, client_message, MAXBUF, 0); if(recvLen < 0) { printf("recv failed\n"); break; } else if(recvLen == 0) { printf("connection lost\n"); break; } printf("RECV_NO: %d, Laenge: %d, Inhalt: %s\n\n", recvNo, recvLen, totalMsg); recvNo++; } return 0; }
问题核心分析
- 头部协议设计完全错误:用十进制拼接
total和sendNo的方式,无法正确解析总长度,当total超过10时,msg[0]/10的逻辑完全失效 - 消息填充错误:
memset(message, newTcp, len)是按字节填充,会把unsigned int的低8位重复填充到每个字节,而非设置int类型的消息内容 - 错误处理缺失:服务器未处理
recv返回-1的异常情况,直接继续循环导致程序崩溃 - 缓冲区溢出风险:全局变量
totalMsg在拼接十六进制字符串时,未检查是否超出MAXBUF限制 - 字节序问题:直接传递整数未考虑网络字节序,跨平台时会出现解析错误
修复后的代码
协议重新设计
定义固定大小的消息头部,包含总长度和消息编号,并用网络字节序保证跨平台兼容性:
typedef struct { uint32_t total_len; // 本次要发送的总字节数(网络字节序) uint32_t msg_no; // 当前消息的编号(网络字节序) } MsgHeader;
修复后的client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <stdint.h> typedef struct { uint32_t total_len; uint32_t msg_no; } MsgHeader; // 发送一条带头部的消息 int send_with_header(int sock, int msg_no, int single_len) { MsgHeader header; // 转换为网络字节序 header.total_len = htonl((uint32_t)(single_len * sizeof(int))); header.msg_no = htonl((uint32_t)msg_no); // 先发送头部 ssize_t sent = send(sock, &header, sizeof(MsgHeader), 0); if (sent != sizeof(MsgHeader)) { return -1; } // 填充消息体:每个int值等于msg_no int *msg_body = malloc(single_len * sizeof(int)); if (!msg_body) { return -1; } for (int i = 0; i < single_len; i++) { msg_body[i] = msg_no; } // 发送消息体 sent = send(sock, msg_body, single_len * sizeof(int), 0); free(msg_body); return sent == (single_len * sizeof(int)) ? 0 : -1; } int main() { struct sockaddr_in myaddr; memset(&myaddr, 0, sizeof(myaddr)); myaddr.sin_family = AF_INET; myaddr.sin_port = htons(50000); myaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); int socketId = socket(AF_INET, SOCK_STREAM, 0); if (socketId < 0) { perror("socket creation failed"); return 1; } printf("Socket is created.\n"); if (connect(socketId, (struct sockaddr*)&myaddr, sizeof(myaddr)) < 0) { perror("connect failed"); close(socketId); return 1; } printf("Successfully connected with server.\n"); while(1) { int count = 0, single_len = 0; printf("Enter the number of messages to be sent: "); if (scanf("%d", &count) != 1 || count <= 0) { printf("Invalid input, please enter positive integer.\n"); while(getchar() != '\n'); // 清空输入缓冲区 continue; } printf("Enter the length of each message (number of ints): "); if (scanf("%d", &single_len) != 1 || single_len <= 0) { printf("Invalid input, please enter positive integer.\n"); while(getchar() != '\n'); continue; } for(int i = 0; i < count; i++) { if (send_with_header(socketId, i, single_len) < 0) { perror("send failed"); close(socketId); return 1; } printf("Sent message No.%d\n", i); } } close(socketId); return 0; }
修复后的server.c
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <stdint.h> #include <stdlib.h> #define MAXBUF 64000 typedef struct { uint32_t total_len; uint32_t msg_no; } MsgHeader; // 接收完整的缓冲区数据 ssize_t recv_full(int sock, void *buf, size_t len) { size_t received = 0; char *ptr = buf; while (received < len) { ssize_t ret = recv(sock, ptr, len - received, 0); if (ret <= 0) { return ret; // 返回错误或连接关闭 } received += ret; ptr += ret; } return received; } int main() { struct sockaddr_in myaddr; memset(&myaddr, 0, sizeof(myaddr)); myaddr.sin_family = AF_INET; myaddr.sin_port = htons(50000); myaddr.sin_addr.s_addr = INADDR_ANY; int socketId = socket(AF_INET, SOCK_STREAM, 0); if (socketId < 0) { perror("socket creation failed"); return 1; } printf("Socket created.\n"); int opt = 1; setsockopt(socketId, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 端口复用 if (bind(socketId, (struct sockaddr*)&myaddr, sizeof(myaddr)) < 0) { perror("bind failed"); close(socketId); return 1; } printf("Successful bind.\n"); if (listen(socketId, 5) < 0) { perror("listen failed"); close(socketId); return 1; } printf("Waiting for incoming connections...\n"); struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int sockAccept = accept(socketId, (struct sockaddr*)&client_addr, &client_len); if (sockAccept < 0) { perror("accept failed"); close(socketId); return 1; } printf("Connection accepted from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); int recvNo = 0; while(1) { MsgHeader header; // 先接收头部 ssize_t ret = recv_full(sockAccept, &header, sizeof(MsgHeader)); if (ret == 0) { printf("Connection closed by client.\n"); break; } else if (ret < 0) { perror("recv header failed"); break; } uint32_t total_len = ntohl(header.total_len); uint32_t msg_no = ntohl(header.msg_no); // 检查长度是否超出缓冲区限制 if (total_len > MAXBUF) { printf("Message too large, discarding.\n"); // 丢弃多余数据 char discard_buf[MAXBUF]; while (total_len > 0) { ssize_t disc = recv(sockAccept, discard_buf, sizeof(discard_buf), 0); if (disc <= 0) break; total_len -= disc; } continue; } // 接收消息体 int *msg_body = malloc(total_len); if (!msg_body) { perror("malloc failed"); break; } ret = recv_full(sockAccept, msg_body, total_len); if (ret != total_len) { printf("Incomplete message received, discarding.\n"); free(msg_body); if (ret == 0) break; continue; } // 格式化输出十六进制内容 printf("RECV_NO: %d, Length: %u, Content: ", recvNo++, (unsigned int)total_len); int int_count = total_len / sizeof(int); for (int i = 0; i < int_count; i++) { printf("0x%X", msg_body[i]); if (i != int_count - 1) { printf(", "); } } printf("\n\n"); free(msg_body); } close(sockAccept); close(socketId); return 0; }
修复要点说明
- 明确协议头部:用结构体定义固定大小的头部,包含总长度和消息编号,并用
htonl/ntohl处理网络字节序 - 正确填充消息体:用
malloc分配内存,逐个设置int类型的值,避免memset的字节填充错误 - 完整接收逻辑:实现
recv_full函数循环接收,确保获取完整的头部和消息体 - 完善错误处理:捕获
recv的异常返回值,处理连接关闭、内存分配失败等情况 - 缓冲区安全检查:限制消息体大小不超过
MAXBUF,避免溢出风险 - 输入合法性校验:客户端添加输入校验,避免非法数值导致的逻辑错误
相关产品推荐
相关产品推荐

