本文写于国庆长假第一天早晨,正好碰到今天热线值班,终于不用假期出去添堵折腾了(14年来[自离开高中],从来没有过过一个完整的可以休息的假期!预定了N次在家的假期,失败了N次,谎称过几次加班,但也不是长计,因为必须要离开家,实在也没地方去,我觉得此生假期难自由了,然而如果公司硬性规定必须在家值班,那也是不啻一种上好的方法啊!哈哈),作文一篇,以表达对假期自由的感慨!

最开始的事实

首先,我们先明确Linux TCP实现中的3个事实,这些叫做“事实”的论述是颇为主观的,它们只是我积累下来的所谓“事实”,这些事实即:

1.Linux作为数据接收端的时候,默认不会Delay ACK,直到...
直到Linux接收端发现了处在交互模式,即pingpong设置为1的时候,才会开启Delay ACK。你试下在Linux上用wget去一个web服务器下载一个文件并抓包,你会发现对接收到的数据的ACK并没有Delay。这也是与Windows的Delay ACK不同的地方,详情参见《 TCP之Delay ACK在Linux和Windows上实现的异同-Linux的自适应ACK》
2.按序接收的快速路径中,TCP接收端如果收到已经收到的数据,会直接丢弃
这个事实可以引申到部分重叠模式的数据接收。举个例子,如果收到一个按序的长度为10的skb,携带序列号10-20,那么当再次收到长度为15,携带序列号15-25的skb时,会将已经收到的15-20丢弃,仅仅保存21-25的新数据。不光如此,当收到这样的部分重叠数据时,会立即回复一个ACK,不管当前是不是处在交互行为的Delay ACK模式下。
3.乱序接收的慢速SACK路径中,TCP接收端如果收到已经收到的数据,会用新数据覆盖老数据
这显然是合理的,因为在乱序模式下,意味着可能出现了丢包或者多径延时,这将使TCP离开正常的状态,此时不管是发送端还是接收端所有的努力都是趋向于将TCP带回到按序处理的快速路径,接收端会尽可能快速回复ACK以反馈信息,发送端会重传可能丢失的数据,由于接收端并不对乱序数据做任何存储上的保证,因此其假设直到最终填洞完毕,之前的数据都可能无效!但是确实一定是这样吗?请跟着在下面实验中找答案。
        以上3个事实看起来并不是显然的,因此需要涉及一些用例来验证。能够探测TCP行为的轻量级工具,我选packetdrill,因此我们就用这个工具来验证吧。

packetdrill验证事实

现在我们来用packetdrill验证一下上述的事实。由于需要验证事实2和事实3,因此需要打印出接收端收到的数据,当前的packetdrill并不支持这个功能,所以需要修改它的代码。
1.为packetdrill构造的数据段增加可以用来区别的payload信息并打印
为此,我们需要修改tcp_packet.c的new_tcp_packet函数,增加简单的payload:

struct packet *new_tcp_packet(int address_family,enum direction_t direction,enum ip_ecn_t ecn,const char *flags,u32 start_sequence,u16 tcp_payload_bytes,u32 ack_sequence,s32 window,const struct tcp_options *tcp_options,char **error)
{...const int ip_bytes =ip_header_bytes + tcp_header_bytes + tcp_payload_bytes;// 增加一个static变量,每次递增,以可打印的ASCII码为起始。static int pad = 70;...packet = packet_new(ip_bytes);memset(packet->buffer, 0, ip_bytes);if (tcp_payload_bytes) {memset(packet->buffer + ip_header_bytes + tcp_header_bytes, pad++, tcp_payload_bytes);memset(packet->buffer + ip_header_bytes + tcp_header_bytes + tcp_payload_bytes -1, 0, 0);printf("send:\n%s\n-end send-\n", packet->buffer + ip_header_bytes + tcp_header_bytes);}...
}

2.在系统调用read之后,打印事实上收到的payload信息
为此,我们修改run_system_call.c的syscall_read函数,打印输出:

static int syscall_read(struct state *state, struct syscall_spec *syscall,struct expression_list *args, char **error)
{...result = read(live_fd, buf, count);printf("payload:\n%s\n", buf);...
}

修改后直接make之,很容易便生成了新的packetdrill。接下来我们将用这个新编译的packetdrill进行一切实验。首先我为事实1和事实2设计了以下的脚本,执行之并抓包:

0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
0.000 bind(3, ..., ...) = 0
0.000 listen(3, 1) = 00.000 < S 0:0(0) win 32792 <mss 1000, sackOK, nop, nop, nop, wscale 7>
0.000 > S. 0:0(0) ack 1 <...>  0.000 < . 1:1(0) ack 1 win 2570.000 accept(3, ..., ...) = 4   // 以下开启Delay ACK
0.100 < . 1:11(10) ack 1 win 257
0.100 write(4, ..., 20) = 20
0.100 < . 11:11(0) ack 21 win 2570.100 < . 11:21(10) ack 21 win 257
1.000 read(4, ..., 20) = 20// 由于已经Delay ACK了一次,其Oneshot特性关闭了自身,以下再次触发开启Delay ACK的序列
1.100 < . 21:31(10) ack 21 win 257
1.100 write(4, ..., 20) = 20
1.100 < . 31:31(0) ack 41 win 2571.100 < . 26:41(15) ack 41 win 257  //这一次发送一个overlap的数据段,部分重叠旧数据,部分包含新数据
2.000 read(4, ..., 20) = 20// Receiver ACKs all data.
10.000 < . 1:1(0) ack 6001 win 257 

在分析抓包之前,我们先看一下打印输出:

这个打印输出基本上与事实2所描述的是一致的,数据段26:31之前已经被收到,已经被ACK过了,因此接收了其包含的部分新数据31:41,旧数据26:31直接丢弃!然后再看下抓包:

我们可以看到,当webcache端口也就是8080接收到第一个段,即1:11的时候,立即回复了ACK,这印证了事实1,之后在收到11:21的时候,却Delay了ACK,至于说这个序列是怎么开启Delay ACK的,不属于本文范畴,我在《 TCP之Delay ACK在Linux和Windows上实现的异同-Linux的自适应ACK》中已经说的比较明确了,现在的重点是,请注意在第一批数据,即1:21被read之后,我触发了一个与1:21数据同样的序列,唯一不同的是最后的26:41属于重叠模式,这一次的ACK是立即回复的,关于深层次的原因,本文不讨论,可以去看RFC或者Linux内核源码关于overlap和乱序的部分,本文仅仅给出一些感官上的体验。
        最后,我们构造一个场景来验证一下事实3。我构造了以下的脚本:

0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
0.000 bind(3, ..., ...) = 0
0.000 listen(3, 1) = 00.000 < S 0:0(0) win 32792 <mss 1000, sackOK, nop, nop, nop, wscale 7>
0.000 > S. 0:0(0) ack 1 <...>  0.000 < . 1:1(0) ack 1 win 2570.000 accept(3, ..., ...) = 4   0.100 < . 21:31(10) ack 1 win 257  // 模拟1:21丢失
0.100 < . 6:21(15) ack 1 win 257  // 模拟重传6:21,这些数据从未被接收过
0.100 < . 16:31(15) ack 1 win 257 // 模拟重传16:31,这些数据部分(21:31)被接收过
0.100 < . 1:11(10) ack 1 win 257  // 模拟重传1:11,这些数据部分(6:11)被接收过,部分(1:6)是新数据
1.000 read(4, ..., 30) = 30// Receiver ACKs all data.
10.000 < . 1:1(0) ack 6001 win 257 

这是一个多么乱的场景啊,按照事实3,还是给个图示比较方便看:

脚本的运行输出结果是:

看似与事实3的论述是一致的。
        猛一看,与按序接收的事实2相反,这里在乱序接收时,确实是以最后收到的数据为准的。但是注意接收16:31时,16:21这段数据并没有被覆盖,事实也确实如此,这是为什么呢?除此之外,还有一个疑问可能待解析,那就是,是不是只要收到被ACK的数据,就会丢弃新数据(显然这是毫无疑问的),只要收到没有被ACK的数据,就会新数据覆盖老数据呢?在数据没有被ACK的情况下,丝毫不理会是按序接收还是乱序接收,统统覆盖老数据呢??
        为了答疑这个疑问,我又构造了一个脚本,这次我用Delay ACK来模拟积压未被ACK的顺序接收的数据,脚本如下:

0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
0.000 bind(3, ..., ...) = 0
0.000 listen(3, 1) = 00.000 < S 0:0(0) win 32792 <mss 1000, sackOK, nop, nop, nop, wscale 7>
0.000 > S. 0:0(0) ack 1 <...>  0.000 < . 1:1(0) ack 1 win 2570.000 accept(3, ..., ...) = 4   // 以下两句开启交互pingpong行为,进入Delay ACK模式
0.100 < . 1:11(10) ack 1 win 257
0.100 write(4, ..., 20) = 20
0.100 < . 11:11(0) ack 21 win 257// 首先传输一个10字节的段
0.100 < . 11:21(10) ack 21 win 257
// 然后部分重叠地推进数据接收
0.100 < . 16:26(10) ack 21 win 257
1.000 read(4, ..., 25) = 25// Receiver ACKs all data.
10.000 < . 1:1(0) ack 6001 win 257

以下是抓包以及输出结果的分析,首先看抓包:


输出结果如下:

关于这个,在RFC 793里面有一段论述:
  When a segment overlaps other already received segments we reconstruct
  the segment to contain just the new data, and adjust the header fields
  to be consistent.

通过以上的实验,我们能不能把事实2和事实3总结成更加通用的说法呢?诚然,按照顺序接收路径和乱序填洞路径来分类重叠数据接收行为是不错的,但是我们发现了一个“黑天鹅”,即在非顺序接收16:31数据时,并没有覆盖掉16:21段的数据,这意味着事实3是有问题的,通过以上的实验,我们将结论总结成以下的说法:
1.如果新接收到的数据在后面与之前接收到的数据前面重叠,则新数据据覆盖旧数据;
2.如果新接收到的数据在前面与之前接收到的数据后面重叠,则丢弃新数据重叠的部分。

我构造了本节最后一个脚本来印证结论:

0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
0.000 bind(3, ..., ...) = 0
0.000 listen(3, 1) = 00.000 < S 0:0(0) win 32792 <mss 1000, sackOK, nop, nop, nop, wscale 7>
0.000 > S. 0:0(0) ack 1 <...>  0.000 < . 1:1(0) ack 1 win 2570.000 accept(3, ..., ...) = 4   0.100 < . 21:31(10) ack 1 win 257
0.100 < . 26:31(5) ack 1 win 257
0.100 < . 21:26(5) ack 1 win 257
0.100 < . 1:16(15) ack 1 win 257
0.100 < . 11:26(15) ack 1 win 257
//0.100 < . 11:21(10) ack 1 win 257
1.000 read(4, ..., 30) = 30// Receiver ACKs all data.
10.000 < . 1:1(0) ack 6001 win 257 

无注释,无输出,请自行运行。
        好了,认识完了这些事实,我们接下来考虑一个小小的优化,这个涉及到了超时重传。

超时重传的Delay ACK

现在我们注意一下上述packetdrill脚本中的第一个Delay ACK,即收到数据11:21时候的ACK,请问接收端怎么知道这个数据段是原始数据段还是超时重发的数据段呢?答案是没有办法知道!我们稍微改一下这个脚本,让11:21段的发送“显得像是在超时重传”,即将其时间往后移一秒!

0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
0.000 bind(3, ..., ...) = 0
0.000 listen(3, 1) = 00.000 < S 0:0(0) win 32792 <mss 1000, sackOK, nop, nop, nop, wscale 7>
0.000 > S. 0:0(0) ack 1 <...>0.000 < . 1:1(0) ack 1 win 2570.000 accept(3, ..., ...) = 4// 以下开启Delay ACK
0.100 < . 1:11(10) ack 1 win 257
0.100 write(4, ..., 20) = 20
0.100 < . 11:11(0) ack 21 win 257//原始数据段丢失 0.100 < . 11:21(10) ack 21 win 257 // 注意时间戳
//第一次重传数据段丢失 0.080 < . 11:21(10) ack 21 win 257 // 注意时间戳,此例中RTO约80ms
// ...时间戳指数退避
1.100 < . 11:21(10) ack 21 win 257  // 延迟1秒发送,看起来像是原始数据段丢了,这里是指数退避好几次后成功重发的10.000 < . 1:1(0) ack 6001 win 257

结果我就不贴图了,请自行验证!
        事实上,这个“看起来像是”确实很像!因为数据段的中途丢失不会给任何信息给接收端(这里不谈路由器的ECN标志),所以即便是已经指数退避很久的重传段被接收端收到,如果此时依然在交互模式中,那么还是会Delay ACK的!这就会带来至少40ms的延时!

优化超时重传时40ms的Delay ACK延迟

虽然接收端并不知道一个数据段是原始的还是超时重传的,但是发送端知道!因此在发生超时重传的时候,顺带发送一个字节已经被ACK的数据是有益的。
        与上述简单的packetdrill脚本所造场景不同的是,真实场景中发送端往往可一次性发送的数据很多,也就是说窗口比较大,该优化的作用比较有限。由于一个TCP段所携带的数据必须是序列号连续的,因此仅仅针对UNA(尚未确认的第一个字节)后第一个TCP数据段的超时重传可以使用“多发一个字节以促使接收端收到重叠乱序数据而立即回复ACK”这个优化,修改的代码非常简单,仅仅将UNA后重传的第一个数据段的TCP头部的seq域的值减去1即可!因为这1个字节与已经接收的最后1个字节重叠,按照上一小节最后的结论“ 2.如果新接收到的数据在前面与之前接收到的数据后面重叠,则丢弃新数据重叠的部分。

必须要注意的是,针对尚未确认的空洞数据段的重传,没有必要采用重叠模式重传,在此,我们必须明确为什么要重叠模式重传,为的是消除接收端Delay ACK的影响!在由于SACK存在空洞的情况下,接收端会视收到的数据段为乱序数据,这种情况自然就不会Delay ACK了!

TCP接收到重叠数据(overlap)后的行为解析-附带一个有关Delay ACK和超时重传的优化相关推荐

  1. STM32 HAL库 串口DMA接收不定长数据

    STM32 HAL库 串口DMA接收不定长数据 整体思路:我是用的CUBEMX软件生成的工程,使能了两个串口,串口2用来接收不定长的数据,串口1用来发送串口2接收到的数据:串口2我找了一个UBLOX卫 ...

  2. TCP之超时重传机制

    TCP协议是一种面向连接的可靠的传输层协议,它保证了数据的可靠传输,对于一些出错.超时丢包等问题TCP设计了超时重传机制,其基本原理:在发送一个数据之后,就开启一个定时器,若是在这个时间内没有收到发送 ...

  3. TCP的定时器系列 — 超时重传定时器(有图有代码有真相!!!)

    转载 主要内容:TCP定时器概述,超时重传定时器.ER延迟定时器.PTO定时器的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd Q:一条TCP连接 ...

  4. TCP/IP协议栈:TCP超时重传机制

    目录 基础概念 重传超时时间RTO RTO的设定 连接往返时间RTT RTT的计算 Karn算法 往返时间测量 重传 拥塞避免算法 快速重传和快速恢复算法 重新分组 网络数据包丢失,重传和重复确认 是 ...

  5. TCP的拥塞避免、超时重传、快速重传、快速恢复

    转自:http://blog.csdn.net/itmacar/article/details/12278769 感谢博主的辛勤成果! 为了防止网络的拥塞现象,TCP提出了一系列的拥塞控制机制.最初由 ...

  6. pythonsocket中tcp通信接收不到数据_TCP 为什么三次握手而不是两次握手(正解版)...

    先说结论 为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的. 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已 ...

  7. 基础问题:在一个 Activity 中定义的串口接收程序,如果 Activity 切换到其它 Activity 后还能接收到串口数据吗?...

    ====================问题描述==================== RT:基础问题:在一个 Activity 中定义的串口接收程序,如果 Activity 切换到其它 Activ ...

  8. vlc-android对于通过Live555接收到音视频数据包后的处理分析

    转载地址:https://blog.csdn.net/c_m_deng/article/details/8487456 通过ndk-gdb跟踪调试vlc-android来分析从连接到RTSP服务器并接 ...

  9. TCP/IP传输层协议实现 - TCP接收窗口/发送窗口/通告窗口(lwip)

    1.tcp通告窗口/接收窗口/发送窗口 接收端有一个接收窗口大小,接收端只能接收这么多数据,接收窗口的数据需要被上层接收后才释放更大接收空间,才可以接收更多数据:接收窗口之前的数据已经被接收,再次接收 ...

  10. TCP/IP 笔记 - 用户数据报协议和IP分片

    关于本章中的IP分片部分,参考第五章IP分片头部知识点.需要注意的是,TCP有超时重传,UDP的超时重传则依赖上层应用程序实现. 用户数据报协议(UDP) UDP是一个简单的面向无连接.不可靠的数据报 ...

最新文章

  1. 服 务 器 时 间 设 置
  2. mysql修改表结构语句
  3. 计算机与pmac2型卡串口怎么通信,PMAC多轴运动控制卡学习硬件.doc
  4. 基于JWT的API权限校验:需求分析
  5. 与专门团队一起持续交付
  6. MugLife app是一款可以将静态照片变成3D动画的手机应用
  7. SublimeText3 初探(工欲善其事,必先利其器)
  8. Python符号计算入门及隐函数图像绘制
  9. 贾跃亭自觉“无辜”;《绝地求生》外挂案件逮捕 34 人;VS Code 1.29 发布! | 极客头条...
  10. UVa 11584 - Partitioning by Palindromes(线性DP + 预处理)
  11. initShaders P30 在webgl内部建立和初始化着色器
  12. 数字信号处理技术在各个领域(电信、音频、图像、雷达、声呐等)的用途
  13. 2019数据安装勾选_Origin2019下载和安装教程
  14. 武汉理工大学计算机学院转专业细则,计算机学院武汉理工大学2009年各学院转专业工作实施细则.doc...
  15. OutLook2016添加exchange 邮箱遇到的问题
  16. 【荐】JS实现类似星球仿flash效果的动态菜单
  17. STM32学习(一)
  18. 【讲座】02 写作英文学术论文
  19. 【Celery】Celery的简易部署和应用
  20. 森林防火三维电子沙盘指挥系统

热门文章

  1. 思科网络模拟器7.3.1版本的下载和安装
  2. onselectstart和onselect的使用
  3. 微信公众号线上和线下推广吸粉的各种技巧!你知道几个?
  4. Excel画竖着的折线图
  5. 在实习的过程中规划自己的未来职业蓝图
  6. docker安装gitlab
  7. 常见设计模式之(五):观察者模式
  8. MATLAB命令行窗口常见命令与功能
  9. itunes备份文件夹更换
  10. Jzoj5424 凤凰院凶真