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

TCP/IP传输Protobuf float值出现数据损坏问题求助

问题:Protobuf TCP传输中部分float值解析异常

我有两个基于C++开发的应用,通过TCP/IP协议通信,使用Protobuf库交换由proto消息定义的数据。相关代码文件如下:

floatExample.proto

syntax = "proto3";

message FloatMessage {
  optional float value = 1;
}

serverFloatExample.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdint>
#include <iomanip>
#include "floatExample.pb.h"

void error(const char *msg)
{
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[])
{
     int sockfd, newsockfd, portno;
     socklen_t clilen;
     char buffer[1024];
     struct sockaddr_in serv_addr, cli_addr;
     int n;

     if (argc < 2) {
         fprintf(stderr,"ERROR, no port provided\n");
         exit(1);
     }

     // create a socket
     // socket(int domain, int type, int protocol)
     sockfd =  socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0)
        error("ERROR opening socket");

     // clear address structure
     bzero((char *) &serv_addr, sizeof(serv_addr));

     portno = atoi(argv[1]);

     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);

     if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) 
     {
        error("ERROR on binding");
        return -1;
    }

     listen(sockfd,5);

     // The accept() call actually accepts an incoming connection
     clilen = sizeof(cli_addr);


     newsockfd = accept(sockfd,
                 (struct sockaddr *) &cli_addr, &clilen);
     if (newsockfd < 0) {
          error("ERROR on accept");
          return -1;
     }

    printf("server: got connection from %s port %d\n",
    inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));

    while(true) 
    {
        sleep(2);
        bzero(buffer,1024);

        n = read(newsockfd,buffer,1024);
        if (n < 0) error("ERROR reading from socket");
        {
            printf("Here is the message size: <%d>\n", n);
        }   

        FloatMessage receivedMsg = FloatMessage();
        receivedMsg.ParseFromString(buffer);

        printf("VALUE: <%.2f>\n", receivedMsg.value());
        receivedMsg.PrintDebugString();
    }

    close(newsockfd);
    close(sockfd);

    return 0;
}

clientFloatExample.cpp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include "floatExample.pb.h" 

void error(const char *msg)
{
    perror(msg);
    exit(0);
}

int main(int argc, char *argv[])
{
    int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;

    char buffer[256];
    if (argc < 3) {
       fprintf(stderr,"usage %s hostname port\n", argv[0]);
       exit(0);
    }
    portno = atoi(argv[2]);
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) 
        error("ERROR opening socket");
    server = gethostbyname(argv[1]);
    if (server == NULL) {
        fprintf(stderr,"ERROR, no such host\n");
        exit(0);
    }
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr, 
         (char *)&serv_addr.sin_addr.s_addr,
         server->h_length);
    serv_addr.sin_port = htons(portno);
    if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) 
        error("ERROR connecting");

    FloatMessage float_message;
    float_message.set_value(1.0);  
    float_message.PrintDebugString();

    std::string serialized_message = float_message.SerializeAsString();

    n = write(sockfd, serialized_message.c_str(), serialized_message.size());
    if (n < 0) 
    {
        error("ERROR writing to socket");
    } 

    close(sockfd);

    return 0;
}

问题现象

服务器接收并解析的部分float值与客户端发送的不一致:

  • 客户端序列化floatValue=1.0 → 服务器解析结果为0(错误)
  • 客户端序列化floatValue=1.01 → 服务器解析结果为1.01(正常)
  • 客户端序列化floatValue=2.5 → 服务器解析结果为0(错误)
  • 客户端序列化floatValue=2.51 → 服务器解析结果为2.51(正常)

问题原因及修复方案

核心原因

  1. 未处理客户端断开连接的情况:客户端发送一次数据后直接关闭socket,服务器的无限循环会继续执行read操作,此时read返回0(表示连接已断开),但服务器未做处理,继续解析被bzero清空的buffer,得到float类型的默认值0.0,用户容易误将这次无效解析的结果当成第一次的输出。
  2. Protobuf解析方式存在隐患:使用ParseFromString(buffer)依赖buffer中的\0来确定数据长度,若序列化数据中包含\0(虽然本例中没有)或buffer有效长度与字符串长度不匹配,会导致解析失败,返回默认值。

修复后的服务器关键代码

while(true) 
{
    sleep(2);
    bzero(buffer,1024);

    n = read(newsockfd,buffer,1024);
    if (n < 0) {
        error("ERROR reading from socket");
    } else if (n == 0) {
        printf("Client disconnected\n");
        break; // 客户端断开,退出循环
    }

    printf("Here is the message size: <%d>\n", n);   

    FloatMessage receivedMsg;
    // 使用ParseFromArray传入实际读取的字节数,避免字符串截断问题
    bool parse_success = receivedMsg.ParseFromArray(buffer, n);
    if (!parse_success) {
        printf("Failed to parse message\n");
        continue;
    }

    printf("VALUE: <%.2f>\n", receivedMsg.value());
    receivedMsg.PrintDebugString();
}

额外注意事项

  • 确保使用的Protobuf版本≥3.12:proto3的optional字段是在3.12版本引入的,低版本可能存在兼容性问题。
  • 若需要持续通信,客户端不要发送一次就关闭socket,保持连接状态;若仅单次通信,服务器解析完成后可直接退出循环,避免无效操作。

内容的提问来源于stack exchange,提问作者Tex'N'Duet

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 06:27:03