TCP粘包处理

本文探讨了protobuf序列化过程中遇到的粘包问题及原因分析。详细解释了TCP协议特性如何导致数据包边界模糊,进而引发序列化数据包大小异常增加的现象。并提出了在程序设计中应对该问题的方法。

使用protobuf序列化数据包的时候报错:ProtoBuf.ProtoException: Invalid wire-type; this usually means you have over-written a file without truncating or setting the length; see http://stackoverflow.com/q/2152978/23354

打印出数据包的大小,发现异常大,之前都是60左右,错误的包是120,翻倍了。

后来发现这就是TCP的粘包现象吧。。。

TCP的数据传输就像一种水流一样,并不区分不同数据包之间的界限。 

TCP协议允许发送端将几次发送的数据包缓存起来合成一个数据包发送到网络上去,因为这样可以获得更高的效率,这一行为通常是在操作系统提供的SOCKET中实现,所以在应用层对此毫无所觉。 

所以我们在程序中调用SOCKETsend发送了数据后操作系统有可能缓存了起来,等待后续的数据一起发送,而不是立即发送出去。send的文档中对此也有说明。

所以应用发送方和接收方需要指定好协议,进行数据拆包。

后面再补充....

### 解决TCP问题的方法 #### 方法一:固定长度的消息分隔符 一种常见的解决方法是约定消息的固定长度。发送方按照固定的字节数发送数据,而接收方则按此长度读取数据并解析。 这种方法的优点在于实现简单,缺点则是不够灵活,尤其当消息大小不一致时可能难以满足需求。 ```c #include <stdio.h> #include <string.h> #define MESSAGE_LENGTH 1024 void send_fixed_length_message(int socket_fd, const char *message) { int length = strlen(message); if (length > MESSAGE_LENGTH) { printf("Message too long\n"); return; } // 补齐至固定长度 char buffer[MESSAGE_LENGTH]; memset(buffer, '\0', sizeof(buffer)); strncpy(buffer, message, length); write(socket_fd, buffer, MESSAGE_LENGTH); // 发送固定长度的数据 } char* receive_fixed_length_message(int socket_fd) { static char buffer[MESSAGE_LENGTH]; read(socket_fd, buffer, MESSAGE_LENGTH); // 接收固定长度的数据 return buffer; // 返回接收到的内容 } ``` 上述代码展示了如何通过填充字符使每条消息达到相同长度来避免[^1]。 --- #### 方法二:自定义消息头部结构 另一种更通用的方式是在每次发送数据前先发送一个表示消息体长度的字段作为消息头。这种方式允许动态调整消息大小,灵活性更高。 以下是基于该策略的一个例子: ```c typedef struct MessageHeader { unsigned short msg_len; // 存储后续数据的实际长度 } Header; // 函数用于向套接字写入带长度标记的信息 int send_with_header(int sockfd, const void *data, size_t data_size) { Header header; header.msg_len = htons((unsigned short)data_size); // 将主机序转换成网络序 if (send(sockfd, &header, sizeof(header), 0) == -1 || send(sockfd, data, data_size, 0) == -1) { // 连续两次调用send函数分别传递头部和主体部分 perror("Send failed"); return -1; } return 0; } size_t recv_all(int sock_fd, void *buffer, size_t total_bytes) { size_t received_so_far = 0; while(received_so_far < total_bytes){ ssize_t just_received = recv(sock_fd, ((uint8_t*)buffer)+received_so_far, total_bytes-received_so_far, MSG_WAITALL); if(just_received <= 0){return received_so_far;} received_so_far +=just_received ; } return received_so_far; } // 函数负责从套接字读取消息及其对应的有效载荷 ssize_t recv_with_header(int sockfd,void **payload_ptr,size_t *payload_len_ptr){ Header header; if(recv_all(sockfd,&header,sizeof(Header))!=sizeof(Header)){ fprintf(stderr,"Error reading header.\n"); return -1; } *payload_len_ptr=ntohs(header.msg_len); if(*payload_len_ptr>0){ *payload_ptr=calloc((*payload_len_ptr)+1 ,1 ); // 额外分配空间存储字符串结束标志'\0' if(!recv_all(sockfd,*payload_ptr,*payload_len_ptr)){ free(*payload_ptr); *payload_ptr=NULL; *payload_len_ptr=0; return -1; } }else{ *payload_ptr=NULL; } return (*payload_len_ptr)?1:0; } ``` 以上代码片段实现了带有消息头的通信方式,其中`msg_len`字段用来指示跟随其后的有效负载的具体尺寸[^5]。 --- #### 方法三:特殊分隔符法 还可以采用特定字符序列(如`\r\n\r\n`)作为不同帧之间的界限。不过需要注意的是,如果业务数据中可能出现这些分割符号,则需额外编码处理以防冲突。 例如,在HTTP协议里就是利用空白行(`\r\n\r\n`)区分请求/响应首部与实体内容的边界的实例之一[^3]。 --- ### 总结 针对TCP现象的不同场景可以选择不同的应对措施。对于简单的应用场景可考虑使用定长消息;而对于复杂度较高的环境推荐引入专门设计的消息格式含必要的元信息以便于双方能够准确无误地完成交互操作[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鱼蛋-Felix

如果对你有用,可以请我喝杯可乐

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值