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

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;
    
}

问题核心分析

  1. 头部协议设计完全错误:用十进制拼接total和sendNo的方式,无法正确解析总长度,当total超过10时,msg[0]/10的逻辑完全失效
  2. 消息填充错误:memset(message, newTcp, len)是按字节填充,会把unsigned int的低8位重复填充到每个字节,而非设置int类型的消息内容
  3. 错误处理缺失:服务器未处理recv返回-1的异常情况,直接继续循环导致程序崩溃
  4. 缓冲区溢出风险:全局变量totalMsg在拼接十六进制字符串时,未检查是否超出MAXBUF限制
  5. 字节序问题:直接传递整数未考虑网络字节序,跨平台时会出现解析错误

修复后的代码

协议重新设计

定义固定大小的消息头部,包含总长度和消息编号,并用网络字节序保证跨平台兼容性:

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;
}

修复要点说明

  1. 明确协议头部:用结构体定义固定大小的头部,包含总长度和消息编号,并用htonl/ntohl处理网络字节序
  2. 正确填充消息体:用malloc分配内存,逐个设置int类型的值,避免memset的字节填充错误
  3. 完整接收逻辑:实现recv_full函数循环接收,确保获取完整的头部和消息体
  4. 完善错误处理:捕获recv的异常返回值,处理连接关闭、内存分配失败等情况
  5. 缓冲区安全检查:限制消息体大小不超过MAXBUF,避免溢出风险
  6. 输入合法性校验:客户端添加输入校验,避免非法数值导致的逻辑错误
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 08:50:41