TCP是否会乱序

问题

TCP客户端发送数据一般这样写

发送数据调用的是write函数,第一个参数是表示socket的文件指针,后面是要传送的数据指针和数据长度。如果数据长度超过了MSS(TCP传送的最大单元)那么数据会被拆分成多个TCP数据包发送。问题:两个线程同时写入超过MSS大小的数据包那么发送的数据包是否存在乱序

比如:Thread1写入的数据被拆分成P1、P2、P3三个TCP数据包;Thread2写入的数据被拆分成P4、P5、P6。接收端收到是数据包是否会存在“交叉”的情况——P1、P4、P5、P2……

为了照顾大家的时间先给出答案——不会乱序

实验分析

碰到这种问题一般我是习惯搬出来kernel代码的。今天我决定换一种方式来解释这个问题——利用动态分析。

动态分析是指分析系统运行时的情况(包括查看变量、检查函数调用,甚至修改参数)。自著名的动态分析工具是DTrace,它最早是Sun为Solaris开发的一款工具。系统工程师通过DTrace可以对Solaris的内核“在线调试”(DTrace现在在MacOS中依然存在)。Linux借鉴了这一思想提供了SystemTap来达到类似的目的。

关于SystemTap的介绍和用法不在本文做详细展开,大家可以自行参考网上的资料(SystemTap非常强大,绝对是系统工程师的“核武器”)。Ubuntu上使用SystemTap需要安装内核的调试文件(Debug Symbol Packages),具体方法自行搜索。下面是实验步骤:

(1). 创建两台虚拟机,172.16.46.1通过netcat启动一个TCP进程监听端口3000(),172.16.46.171模拟客户端发送数据。

(2) 客户端程序是用Python写的,a-f每个字母重复指定次数,启动两个线程分别发送a-c、d-f,为了方便查看结果在每一行数据前面加上表示字节数的三位数字,后面加上换行符。

(3) 最关键的一步,为了让一行数据可以被拆分成多个TCP数据包,把网卡的MTU值修改为100

(4) 最后展示一下SystemTap脚本(tcp.stp)

这段脚本非常简单,挂在内核函数、上。其中是tcp的“发送数据”函数(实现应用数据包发送),则完成一个TCP数据包的发送。

实验的时候首先启动,然后调用Python脚本

第一行数据144字节,调用发送,然后调用完成TCP数据包的发送此处mss大小是48(扣除52字节的IP头、TCP头,这两部分包含“选项”所以长度不固定)。

MSS是每个TCP数据包的最大字节数,144字节的数据应该被拆分成3个TCP发送而此处只调用了一次,所以和我们预测的结果并不一致。这是由于现代网卡都支持TCP、UDP、IP分包offload,TCP、UDP、IP的分片、重组工作不再由TCP/IP协议栈的软件实现,可以直接交给网卡,由硬件完成相关工作(offload指把由软件实现的功能交由硬件直接实现)。

通过下面的命令验证网卡的offload功能。

通过下面的命令关闭TSO、GSO

再次执行验证

为了便于观察我把接收端的结果和发送端的TCP函数调用用箭头做了关联。第一次数据包是144,一共被拆分成3个TCP数据包所以被调用三次(48*3=144)。其他数据包也都是相同的原因被拆分成多个TCP数据包发送,从屏幕输出我们观察到a-f字符都是连续输出中间并没有被混入其他字符。

原因分析

两个线程可能同时产生两组不同的TCP数据包,但是这两组数据包在变成TCP数据包的时候并不会出现乱序。究竟是什么原因还是要打开代码一观。打开查看函数一切真相大白了:

无论多少个线程访问socket,写入数据的时候都会变成“依次写入”。

最后几句话

协议栈只保证一次写入(一次write调用)的数据被封装成TCP数据包时是顺序达到,如果你“自作聪明”分两次写入(调用两次write)那么两次write之间的顺序可能出现乱序。比如在试验中只保证一行内的数据有序达到,不保证行和行之间的数据有序。

还没结束

这篇文章是上周写的,巧的是周末一个朋友刚好问我“TCP重传数据包和之前的数据包内容不一样”的问题。这个问题的解决过程非常曲折,得到的答案也让我大跌眼镜。具体内容留到下篇文章详细分析,这里先抛出一个问题:多线程能否对同一个Socket进行写入?讲出“可以”或“不可以”的原因。

本文来自企鹅号 - 写程序的康德媒体

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏后台全栈之路

DNS 报文结构和个人 DNS 解析代码实现——解决 getaddrinfo() 阻塞问题

实际应用中发现一个问题,在某些国家/ 地区的某些 ISP 提供的网络中,程序在请求 DNS 以连接一些服务器的时候,有时候会因为 ISP 的 DNS 递归查询太...

7296
来自专栏安富莱嵌入式技术分享

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

本章节为大家讲解RL-TCPnet的UDP通信实现,学习本章节前,务必要优先学习第16章UDP用户数据报协议基础知识。有了这些基础知识之后,再搞本章节会有事半功...

1623
来自专栏向治洪

百度地图android studio导入开发插件

百度地图SDK v3.5.0开发包下载地址:http://lbsyun.baidu.com/sdk/download?selected=location 开...

1.1K8
来自专栏JavaEdge

压测软件Jmeter使用实例(WIN7环境)百科我们为什么使用JmeterJmeter安装配置Sampler监听器(Listener)点击启动按钮,开启测试Jmeter自定义变量Redis的压测

3655
来自专栏逸鹏说道

【推荐】C#线程篇---你所不知道的线程池(4)

线程的创建和销毁都要耗费大量的时间,有什么更好的办法?用线程池! 太多的线程浪费内存资源,有什么更好的办法?用线程池! 太多线程有损性能,有什么更好的办法?用线...

3758
来自专栏小小挖掘机

整理一些计算机基础知识!

为了使不同计算机厂家生产的计算机能够相互通信,以便在更大的范围内建立计算机网络,国际标准化组织(ISO)在1978年提出了“开放系统互联参考模型”,即著名的OS...

1123
来自专栏熊二哥

快速入门系列--WCF--01基础概念

转眼微软的WCF已走过十个年头,它是微软通信框架的集大成者,将之前微软所有的通信框架进行了整合,提供了统一的应用方式。记得从自己最开始做MFC时,就使用过Nam...

22810
来自专栏云霄雨霁

概念题知识点总结

1990
来自专栏快乐八哥

ASP.NET中使用HttpWebRequest调用WCF

最近项目需要和第三网站进行数据交换,第三方网站基本都是RESTfull形式的API,但是也有的是Web Service,或者.NET里面的WCF。微软鼓励大家使...

4359
来自专栏FreeBuf

负载恶意软件HawkEye的VB Inject样本分析

恶意软件HawkEye的利用大多都是通过钓鱼邮件分发,利用office直接启动HawkEye主体或者一些经过加密的程序,本文中的VB Inject属于后者,也把...

861

扫码关注云+社区

领取腾讯云代金券