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

C语言TCP流拆分消息的正确实现与多消息传输故障排查

C语言TCP套接字连续收发阻塞问题排查与修复

问题背景

开发基于C语言的客户端-服务端TCP数据交互项目时,已知TCP为字节流协议,单次send调用提交的数据可能需要多次recv调用才能完整读取,因此最初实现了如下循环收发的基础函数:

初始发送函数

void send_message(int clientSocket, char message [], int length ){
    int sent = 0;
    while(sent < length){
        sent += send(clientSocket, message + sent , length - sent , 0);
    }
}

初始接收函数

void receive_message(int clientSocket, char message [], int length){
    int received = 0;
    while(received < length){
    received += recv(clientSocket, message + received , length - received , 0);
    }
}

单条消息传输场景下上述函数运行正常,但连续发送多条固定长度消息时,发送端可正常退出发送循环(表面代表所有字节已提交到套接字发送缓冲区),接收端却无法完成全部数据接收,会在某次recv调用时持续返回0字节导致程序阻塞卡死。

初始测试代码

客户端初始代码

#include<stdlib.h>
#include<string.h>
#include<inttypes.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include<sys/socket.h>

void send_message(int clientSocket, char message [], int length ){
    int sent = 0;
    while(sent < length){
        sent += send(clientSocket, message + sent , length - sent , 0);
    }
}
void receive_message(int clientSocket, char message [], int length){
    int received = 0;
    while(received < length){
        received += recv(clientSocket, message + received , length - received , 0);
    }
}
int main(){
    //初始化套接字
    struct sockaddr_in serverAddr;
    socklen_t addr_size;
    int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(7899);
    //将SERVER_IP_ADDRESS替换为实际服务端地址
    inet_pton(AF_INET,"SERVER_IP_ADDRESS9999",&serverAddr.sin_addr);
    memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero);
    addr_size = sizeof serverAddr;
    connect(clientSocket, (struct sockaddr *) &serverAddr, addr_size);

    // 测试逻辑
    char message [3000]= "";
    memset(message, 0,3000);
    for(int i = 0 ; i< 20; i ++){
        //填充测试字符串
        for(int j=0; j<2999; j++){
            message[j] = 'A' + i;
        }
        send_message(clientSocket, message, 3000);
        printf("i = %d \n %s\n",i, message);
    }
}

服务端初始代码

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<inttypes.h>
#include <netinet/in.h>
#include<sys/socket.h>

void send_message(int clientSocket, char message [], int length ){
    int sent = 0;
    while(sent < length){
        sent += send(clientSocket, message + sent , length - sent , 0);
    }
}
void receive_message(int clientSocket, char message [], int length){
    int received = 0;
    while(received < length){
        received += recv(clientSocket, message + received , length - received , 0);
    }
}
int main(){
    //初始化套接字
    int welcomeSocket, clientSocket;
    struct sockaddr_in serverAddr;
    struct sockaddr_storage serverStorage;
    socklen_t addr_size;
    welcomeSocket = socket(AF_INET, SOCK_STREAM, 0);
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(7899);
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero);
    bind(welcomeSocket, (struct sockaddr *) &serverAddr, sizeof(serverAddr));
    if (listen(welcomeSocket,1)==0)
        printf("Listening\n");
    else
        printf("Error\n");
    addr_size = sizeof serverStorage;
    
    //接收到达的客户端连接
    clientSocket = accept(welcomeSocket, (struct sockaddr *) &serverStorage, &addr_size);

    printf("accepted");

    // 测试逻辑
    char message [3000]= "";
    memset(message, 0,3000);
    for(int i = 0 ; i< 20; i ++){
        receive_message(clientSocket, message, 3000);
        printf("i:= %d\n %s\n",i, message);
    }
} 

异常现象汇总

  • 发送端在每条消息发送间隔增加短暂等待后,程序可正常运行,初步怀疑为短时间发送字节量过大导致套接字过载
  • 同机部署(本地环回通信)时代码运行正常,跨机器远程部署时稳定复现接收阻塞问题
  • 发送端循环内增加printf打印语句时程序无异常,移除打印语句后服务端立即复现接收卡住的问题

根因分析

  1. 原代码未处理套接字接口的异常返回:recv返回值为0时代表对端已正常关闭连接,返回-1时代表出现网络错误,原逻辑直接将返回值累加到接收字节计数中,一旦连接提前断开,返回0会导致接收计数永远无法达到预期长度,循环无限执行表现为程序卡死。
  2. 原客户端发送完所有数据后未执行优雅关闭流程,进程直接退出会触发连接异常中断,发送缓冲区中未传输完成的数据可能被丢弃。本地环回场景下网络传输延迟极低、缓冲区充足,几乎不会出现数据未传完连接就断开的情况;跨网络场景下受Nagle算法、链路延迟、设备缓冲区调度影响,高速连续发送时很容易出现连接提前断开的情况,增加sleep、打印语句本质是放缓了发送速率,给数据传输留出了足够时间,因此会掩盖真实问题。

修复方案

核心修复逻辑共两点:

  • 为recv(生产环境还需包含send)增加返回值校验,返回值小于等于0时立即打印错误信息、跳出循环,避免无限阻塞
  • 所有数据发送完成后调用shutdown执行优雅套接字关闭,确保发送缓冲区的所有数据完成传输后再断开连接

修复后服务端代码

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<inttypes.h>
#include <netinet/in.h>
#include<sys/socket.h>

void receive_message(int clientSocket, char message [], int length){
    int received = 0; int r =0;
    while(received < length){
        r = recv(clientSocket, message + received , length - received , 0);
        if( r<= 0){
            printf("!\n\nERROR- skip \n\n\n!!!!!!!!");
            break;
        }else{
            received += r;
        }
    }
}
int main(){
    //初始化套接字
    int welcomeSocket, clientSocket;
    struct sockaddr_in serverAddr;
    struct sockaddr_storage serverStorage;
    socklen_t addr_size;
    welcomeSocket = socket(AF_INET, SOCK_STREAM, 0);
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(7899);
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero);
    bind(welcomeSocket, (struct sockaddr *) &serverAddr, sizeof(serverAddr));
    if (listen(welcomeSocket,1)==0)
        printf("Listening\n");
    else
        printf("Error\n");
    addr_size = sizeof serverStorage;
    
    //接收到达的客户端连接
    clientSocket = accept(welcomeSocket, (struct sockaddr *) &serverStorage, &addr_size);

    printf("accepted");

    // 测试逻辑
    char message [3000]= "";
    memset(message, 0,3000);
    for(int i = 0 ; i< 30; i ++){
        receive_message(clientSocket, message, 3000);
        printf("i:= %d\n %s\n",i, message);
    }
}

修复后客户端代码

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<inttypes.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include<sys/socket.h>

void send_message(int clientSocket, char message [], int length ){
    int sent = 0;
    while(sent < length){
        sent += send(clientSocket, message + sent , length - sent , 0);
    }
}

int main(){
    //初始化套接字
    struct sockaddr_in serverAddr;
    socklen_t addr_size;
    int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(7899);
    //将SERVER_IP_ADDRESS替换为实际服务端地址
    inet_pton(AF_INET,"15.161.236.166",&serverAddr.sin_addr);
    memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero);
    addr_size = sizeof serverAddr;
    connect(clientSocket, (struct sockaddr *) &serverAddr, addr_size);

    // 测试逻辑
    printf("Start\n");
    char message [3000]= "";
    memset(message, 0,3000);
    for(int i = 0 ; i< 30; i ++){
        //填充测试字符串
        for(int j=0; j<2999; j++){
            message[j] = 'A' + (i % 26);//(rand() %20) ;
        }
        message[4] = 0;
        send_message(clientSocket, message, 3000);
    }
    shutdown(clientSocket, 2);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 22:24:18