TCP数据收发两问题的排查
目录
1、TCP发送数据的“丢失”问题
2.1 问题描述
2.2 问题分析
2.3 问题解决
2、TCP接收数据的“丢失”问题
2.1 问题描述
2.2 问题分析
2.3 问题解决
3、最后
我们协议栈的某条业务线数据的收发基于TCP连接的,在测试过程中发现,TCP数据的接收与发送各有一次数据“丢失”的问题。我们知道TCP数据的收发是可靠的,不会发生数据丢失的情况,本文将讲的数据“丢失”是出问题时给人的一种假象。下面本文就来详细地讲述一下收发数据失败问题的排查过程。
1、TCP发送数据的“丢失”问题
2.1 问题描述
平台的同事向我们反馈,换了新版本的协议栈后,发送大的消息(发送的是1669个字节的数据)会有概率失败的情况。失败不是说,发送端调用我们协议栈的接口发送数据时接口就返回失败,而是返回了成功,但接收端却并没有收到此消息。
2.2 问题分析
老版本的协议栈和新版本的协议栈差异化还是很大的,所以不可能直接去对比所有协议栈的内容。所以只能一步步从这个问题的源头查起。
首先分析是发送端的问题还是接收端的问题,经过在给接口处加打印、数据收发处加打印以及抓包分析后发现,接口处传入的数据都是正确的,但是从包里看却发现是发送端出了数据“丢失”、“乱序”的问题。
因为数据流太大,协议栈会进行分包发送,每包最大512bytes。那么1699bytes的数据就应该分为4包发送,且按顺组成的内容应该跟原始数据一致。如图所示:
数据中包括一个TPKT头(03 00 06 a3),其中06 a3指示数据大小为1699bytes,4包数据为一个完整数据。
上图是正确情况下的内容,出问题时就会出现如下图所示的“乱序”问题:
从上图中我们可以看到第四包数据的内容也出现了一个TPKT头(03 00 06 a3),实际上数据中有和头部相同的数据是没什么奇怪的,但是分析发现第四包数据内容和第一包数据一模一样。
对比打印中正确的数据,第四包的数据就是错的,而且打印中发现,业务是发送了两次非标数据,那么第四包的数据应该就是第二次发送的数据头,这样的话给我们的感觉就是TCP竟然出现了“乱序”!检查了包里的内容发现,后边缺失的数据也没看到有发送,那么分析下来就是出现“丢失”数据了,而不是“乱序”!
分析代码时经过高人指点,发现协议栈发送数据时是切分数据后直接循环调用send发送,会不会是发送太快又频繁而导致TCP的发送缓冲区不够用了呢。那么在每次发送后都sleep上几毫秒后测试,每次发送都是正确的了。
2.3 问题解决
我们知道,调用套接字的send函数后,返回>0的值并不代表tcp已经把这么多数据发送给对方了,而是拷贝给了发送缓冲区多少个字节(非阻塞模式)。Tcp从缓冲区中发送数据也是需要消耗时间的,那么使用sleep就可以多给tcp点时间去把缓冲区的数据发送出去从而移除掉。
sleep虽然可以解决这个问题,但是sleep多长时间合适呢,而老版本协议栈为什么没有问题呢?此时再去查看老版本协议栈的代码,发现它并没有用sleep,那么只有放大缓冲区大小可以解决这个问题了。经查看,4.0协议栈确实在创建socket的时候,都会把收发缓冲区的大小设置下。
新版本的协议栈也使用此方法后问题解决。设置方法就是使用setsockopt函数,套接字选项为SO_SNDBUF、SO_RCVBUF。
肯定有人会问,当发送缓冲区满时,send会返回-1的啊,或者要发送的数据大于缓冲区中剩余的数据也回返回实际放入的数据值啊。好吧,确实是我们使用的失误,对返回值的操作没有处理好,而是自以为的都发送成功了,从而导致了数据“丢失”。所以当时看协议栈打印并没有报错的地方,从而把人引入更晕乎的状态。赶紧修改之。
2、TCP接收数据的“丢失”问题
2.1 问题描述
测试人员反馈,在他的一台win7电脑上测试软件的某一项功能时会大概率性失败,而其他电脑则没有这个问题。
2.2 问题分析
首先分析失败原因,从打印中看,是因为平台侧认为超时没收到此win7电脑的MSD(主从决定)消息,从而导致MSDACK无法正确完成,从而导致问题。
但抓win7电脑和平台侧的包后发现,win7侧信令有发送出去,平台侧的抓包看是有收到此包数据的。但是看平台侧打印就是没有收到此信令的打印,以至于解析win7电脑发来的MSD消息的TPKT头的打印都没有。查看解析数据的代码,并没有看到有什么特殊处理的地方,为什么只针对此win7电脑有问题呢,而且还不是必现?!
在分析打印的时候,看到平台侧收到win7电脑致邻发送的TCS(能力集)数据流,数据的末尾和某类型的TPKT头格式相同,都是06 00 xx xx的格式。而其他电脑致邻的TCS数据却不是这样,会不会是因为数据和TPKT头相似而导致的解析错误呢?
协议栈的数据处理过程:首先它会读取数据的TPKT头部分,根据头部指示数据的大小再读入相应大小的数据。那么即使数据部分和头部相同应该也不会有把数据部分当做头部处理的情况。
在正确的情况下:
接收MSD消息打印分析收到win7发送的MSD消息过程:首先读取数据的前4个字节(TPKT头大小)的数据,TPKT头前两字节固定是03 00(某类型的TPKT头是06 00,会转化成03 00),后两个字节是数据大小,那么数据大小是00 0B=11bytes,减去TPKT头大小4bytes,剩下的就是7bytes,那么再读取7bytes的数据从而组成一个完整的MSD消息。
有问题时的抓包如下:
对比上面两个图,01 00 32 80就是MSD的数据部分,后续再读取的3个字节总共7个字节就是MSD消息(传入的len=512,是因为发现之前读的数据不符合预期中的TPKT头,那么就一次读入512数据放到临时缓冲中,在从缓冲中逐个字节去找TPKT头,防止因为tcp数据流的传输方式导致一次的错误而把后续的有效消息也丢失的问题),但是为什么没有MSD消息的TPKT头部分呢?难道tcp数据出现了“丢失”?!但从wireshark抓包来看,其收到的数据是完整的!
在某特殊类型的产品中,把原来标准的TPKT头03 00 xx xx变为06 00 xx xx,并且数据部分插入4个bytes的0。这个处理被放到了调用send时转换为该特殊类型的产品数据,在recv收到数据后再恢复到标准数据。所以根据协议处理数据原理,先读TPKT头,然后根据头部读数据。在图4中的体现是:TPKT头“丢”了,而直接读取的是插入的4个0数据,发现不符合TPKT头格式再继续以4个字节大小尝试读取头部数据。
检查了tcp的socket接收缓冲区大小,已经被设置为挺大的值了,那么看来tcp数据的“丢失”不是对socket的设置导致,那就只能是我们代码哪里处理有问题了。因为win7电脑的TCS数据的“特殊化”,又继续分析了其TCS数据。对比成功和失败时候的打印,虽然测试人员说什么都没有改动,但是从打印中能看出来,成功和失败时候win7电脑致邻发送能力的大小是不一样的!
成功时接收TCS消息打印如下:
发送失败时接收TCS消息打印如下:
然后就注意到了,上面接收失败时读取数据时候出现了一个错误。虽然返回值是没有错误,而且打印也没有报错,但是从打印中看到我们要读取len=4字节长度的时候,实际received却是8!对于recv函数来说,除非接收buffer中数据长度小于你要读取的数据长度时,会返回和你要读取的数据不一样的值,否则你要读取多少字节就应该返回多少字节的数据。所以这个返回值肯定就是出现问题的元凶!
那么成功和失败时候的能力区别在哪里呢,对比包发现,呼叫时候的码率不同会导致能力字节大小不一样,所以在呼叫码率是8M的时候是必现,其他小点的码率就不出现。
2.3 问题解决
协议解析数据会先读取TPKT头数据的大小,为了解析某特殊类型产品的数据,在recv到数据后判断如果是该特殊类型产品的TPKT头,那么要做的就是改变头内容(把06变为03,指示数据长度的地方减去插入的4个bytes)、去掉后续是4个0的数据。
在错误情况下解析TCS数据的时,它刚好暴露了我们代码中的一个错误点。如图6所示,因为它的能力字节大小是520byets,去掉头部4bytes后还有516个。协议栈读数据时如果其大小超过512块大小就分块读取。所以先读512bytes,再读剩下的4bytes。而每次读取4bytes时,我们都会判断是否是某特殊类型产品的TPKT头,根据2.2.2中怀疑点,TCS的最后四个字节刚好和该特殊类型产品的TPKT头格式相同。而刚好因为其字节数的大小,与块大小的原因我们单独读取了这四个字节!
在判断是该特殊类型产品的TPKT头后,我们做了头部操作,然后再读取4个字节,判断是不是全0,如果是就去掉,否则就返回8(返回8是因为已经读了8个字节)。所以出现了接收失败时我们要读取4bytes但是返回8的现象。
所以最简单的解决方案就是实现“预读”。recv函数原型:
int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);
参数flags值:
MSG_DONTROUTE 绕过路由表查找。
MSG_DONTWAIT 仅本操作非阻塞。
MSG_OOB 发送或接收带外数据。
MSG_PEEK 窥看外来消息。
MSG_WAITALL 等待所有数据。
一般情况下,我们recv的第四个值都会写为0,那么我们调用了recv后,已经读过的数据就会从socket的缓冲区中移除。而MSG_PEEK值却可以为我们实现“预读”功能,即我们预读的数据就不会从socket接收缓冲区移除,在预读发现如果不是4个0字节,那么就直接返回已读的TPKT头大小数据,否则就使用从socket接收缓冲区中移除的方式读取完成删除插入的4个0字节的功能。
3、最后
在对分析问题的过程中,并没有像上述分析过程那样顺利,整个过程时艰难曲折的,中途查阅了不少资料。经历了这两个问题详细排查过程,使得我们对TCP的读写数据的原理及细节有了更为深刻的认识。
TCP数据收发两问题的排查相关推荐
- 使用Wi-Fi实现ESP32与手机网络助手进行TCP数据收发
在ESP32板块,开发环境搭建完成的基础上,做一个简单的TCP客户端与服务器端的数据收发实验,开发环境是基于乐鑫官方IDF,Linux环境下发开,本实验有助于对例程的理解和应用. 实验前期准备:在手机 ...
- TCP 数据收发过程抓包分析
本文简单对 TCP 协议的三次握手.数据传输.四次挥手过程进行抓包分析. 一. 抓包准备 首先本地通过套接字实现一个 TCP 通信,然后通过 Wireshark 抓包,套接字通信代码如下: Serve ...
- 一般来说,GET产生一个TCP数据包;POST产生两个TCP数据包。
TCP 是传输层协议 应用层协议里的 GET 和 POST GET和POST还有一个重大区别,简单的说: 一般来说,GET产生一个TCP数据包:POST产生两个TCP数据包. 长的说: 对于GET方式 ...
- 服务器数据收发测试软件,Packet Sender(UDP/TCP网络测试工具) v7.0.5
Packet Sender(UDP/TCP网络测试工具)是一个开源实用程序,允许发送和接收TCP.UDP和SSL(加密的TCP)数据包,主线分支正式支持Windows.Mac和桌面Linux,其他地方 ...
- s6-4 TCP 数据段
传输控制协议 TCP (Transmission Control Protocol) 是专门为了在不可靠的互联网络上提供可靠的端到端字节流而设计的 TCP必须动态地适应不同的拓扑.带宽.延迟. ...
- 千兆以太网PHY芯片调试-88E1111(RGMII接口-数据收发ECHO测试) Verilog实现python测试
千兆以太网PHY芯片调试-基于RGMII接口的88E1111(数据收发ECHO测试) 先放结果: Py测试代码: import socket #网络通信 TCP,UDP DST_IP = '192.1 ...
- 【stm32f429igt6】的WiFi模块数据收发。
[stm32f429igt6]的WiFi模块数据收发. 主要模块:串口7 .串口3 esp8266 stm429igt6.网络调试助手.串口助手. 对上图的个人理解哈! 1:u3和WiFi是一对情侣 ...
- 网络编程—使用C语言实现发送TCP数据包,以命令行形式运行:SendTCP source_ip source_port dest_ip dest_port;(原理和常见错误分析)
任务要求: 1.以命令行形式运行:SendTCP source_ip source_port dest_ip dest_port: 2.头部参数自行设定,数据字段为"This is my h ...
- Linux: 网络数据收发流程简析
文章目录 1. 前言 2. 背景 3. 网卡数据收发流程 3.1 网络数据接收流程 3.1.1 网卡数据接收流程 3.1.2 网卡数据向上传递给L3,L4的流程 3.2 网卡数据发送流程 1. 前言 ...
最新文章
- IIS8 添加配置 WCF服务
- 1874畅通工程续(dijkstra算法)
- python学习(十八) 程序打包
- QT的 QAndroidJniObject类的使用
- java ip吸附_IP层的封装(Java的InetAddress类的C++实现)
- 带着灵魂去旅行的骑者-重新认识自我
- 14-Clothes衣服
- 评价类模型:1.层次分析法
- linux python3 装pip,linux 安装pip 和python3(示例代码)
- flutter实现画中国地图
- 基于STM32设计的NB-IOT电量采集系统(超级详细)--2.STM32连接M5311及HLW8032测试
- 清明节到五一的加班感触
- 【数据分析项目实战】篇1:游戏数据分析——新增、付费和用户行为评估
- 光缆弹性模量计算_光纤光缆布线基础知识及系统设计
- MongoDB 3.4安装及配置
- Unit3D打包android时出错 CommandInvokationFailure: Unable to list target platforms. Please make sure the a
- java excel 判断组重复_Java判断Excel某列是否有重复值
- Android studio输入m自动提示成员变量名称
- 数据论《西游记》关系网:猪八戒最主动喜欢别人
- 【300+精选大厂面试题持续分享】大数据运维尖刀面试题专栏(十四)
热门文章
- linux vim配置bg,简洁的vim配置
- AD20(Altium Designer20)实用技巧系列教程
- lintcode斐波那契数列
- java公告栏js资源_可以文本显示的公告栏的js代码
- ​福瑞泰克高阶自动驾驶解决方案成功定点一汽红旗全新车型平台
- 中国手机扶持联发科,如今联发科却大幅提价割韭菜,可谓白眼狼
- java声明复数类_JAVA声明复数类
- pc串口调试工具与安卓app通讯,pc及手机串口配对方法
- MATLAB中太赫兹时域光谱的最大似然参数估计
- CMutex使用时的注意事项,以及CMutex::Unlock何时会返回0