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进行写入?讲出“可以”或“不可以”的原因。

  • 发表于:
  • 原文链接:http://kuaibao.qq.com/s/20171218G0FQOB00?refer=cp_1026

相关快讯

扫码关注云+社区