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(正常)
问题原因及修复方案
核心原因
- 未处理客户端断开连接的情况:客户端发送一次数据后直接关闭socket,服务器的无限循环会继续执行
read操作,此时read返回0(表示连接已断开),但服务器未做处理,继续解析被bzero清空的buffer,得到float类型的默认值0.0,用户容易误将这次无效解析的结果当成第一次的输出。 - 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
相关产品推荐
相关产品推荐

