最新的勘误已经发表,请先对照最新的勘误,如有疑问,随时联络,谢谢。

勘误链接: 《 TCP拥塞控制图解(不包括RTO,因为它太简单了) 【勘误1】》

五一假期放假,我感到莫名地轻松,因为这是一个三天无比快乐的工作时间,今天一天在家,修正了上周末的图表,终于完成了初稿。千万不要吵醒熟睡中的老婆,一旦吵醒了就什么都完了,那就必须通宵了,可是明天还要去西冲,到头来垂头丧气,还是完蛋!不管怎么说,今天总的东西希望对别的人有用(如果你觉得对你没有用的话)

1.网上有很多讲TCP拥塞控制的文章,但是几乎没有一篇能够讲清楚的,关于很多细节充其量只是描述一下代码,想必作者也没有真懂。唯一觉得比较好的两位博主:

a).CSDN的 http://blog.csdn.net/zhangskd
 b).chinaunix的 http://blog.chinaunix.net/uid/28387257
 其它的基本没什么可以看的了,代码解释谁都会,if解释成如果这些就是网络的垃圾,幸运的是,如今我也加入了他们,希望能成为NO.3,为大家抛砖引玉,只有大家站在同一个层面,才会有公平的PK。
 2.在分析TCP拥塞控制的时候,不要动不动就摆出“拥塞状态机”,事实上这是Linux的独家奉献,如果看BSD或者其它的实现,很多根本就没有拥塞状态机的概念,只要完全按照RFC的要求或者建议去实现【有时候,也不必完全按照RFC】,你的TCP一样可以完美。
 3.对于实现而言,Linux的TCP协议栈是一个很烂的实现,然而这是有理由的,Linux相比BSD或者lwIP的实现,消除了几乎所有的代码冗余,它希望在一套代码中,在一个很短的函数中,完成所有的一切,这就难免了各种if,&,||等

 
 先上图为好。

需要说明的是,这幅图的制作占用了我宝贵的时间,我白天没时间搞,因为会有无穷多的进度与会议,只能趁着夜晚老婆孩子睡了之后折腾,万一她们醒了,我就会一夜万劫不复,十分艰难,因此,只希望一点,如果有发现错误,及时告知我。另外,我想告知的是,随时随地,因为在我这里,没有时间的概念。
 可是,如果你用2.6.32的内核的话,就是以上这些了,然而如果你升级到4.4,你会看到不一样的结果!
 tcp_may_raise_cwnd在tcp_fastretrans_alert之后,因为在alert中可以更新reordering
 在处理的时候可以在partial ACK之后的una后面没有retrans,且确认数据包的ACK不是由于重传(是由于原始数据包)导致的时候(时间戳或者DSACK判断),可以进入Disorder状态,
 且,如果partial ACK的后面连sack也没有,那么可以直接进入Open。这些都在图中画出了,详见Where to go。
 
 
 4.刚才还没有完,我想来一点代码分析,基于Linux 2.6.32以及Linux 4.3
 以下代码来自2.6.32

 static int tcp_try_undo_partial(struct sock *sk, int acked)
{struct tcp_sock *tp = tcp_sk(sk);/* Partial ACK arrived. Force Hoe's retransmit. */int failed = tcp_is_reno(tp) || (tcp_fackets_out(tp) > tp->reordering);// 确认ACK是由最初的传输产生的还是由重传产生的if (tcp_may_undo(tp)) {/* Plain luck! Hole if filled with delayed* packet, rather than with a retransmit.*/if (tp->retrans_out == 0)tp->retrans_stamp = 0;// 如果可能的话,更新乱序度,可悲的是,Linux2.6.32没有对其做出积极的反应,// 而仅仅是一些消极的反应:只有重复(或者sack)大于reordering才会标记LOST!!!tcp_update_reordering(sk, tcp_fackets_out(tp) + acked, 1);DBGUNDO(sk, "Hoe");tcp_undo_cwr(sk, 0);//仅仅意味着可以多发一些数据,并不改变在快速恢复过程中由ssthresh指示的窗口收敛值NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPARTIALUNDO);/* So... Do not make Hoe's retransmit yet.* If the first packet was delayed, the rest* ones are most probably delayed as well.*/// 这个启发在于,如果真的发生了undo,意味着网络中很可能真的发生了延迟或者乱序,而不是真正的丢包,因此不标记LOST,而是继续发送新数据或者前向重传failed = 0;}return failed;
}static void tcp_fastretrans_alert(struct sock *sk, int pkts_acked, int flag)
{struct inet_connection_sock *icsk = inet_csk(sk);struct tcp_sock *tp = tcp_sk(sk);// (FLAG_DATA-接收端主动数据传输|FLAG_WIN_UPDATE-主动窗口更新|FLAG_ACKED-数据被ACK)// 对于主动发送的携带ACK的数据包,即便ACK重复了,也不算是重复ACKint is_dupack = !(flag & (FLAG_SND_UNA_ADVANCED | FLAG_NOT_DUP));int do_lost = is_dupack || ((flag & FLAG_DATA_SACKED) &&(tcp_fackets_out(tp) > tp->reordering));int fast_rexmit = 0, mib_idx;.../* B. In all the states check for reneging SACKs. */// 详见图中的SACK reneging主动检测if (tcp_check_sack_reneging(sk, flag))return;/* C. Process data loss notification, provided it is valid. */// 详见图中的LOST主动检测if (tcp_is_fack(tp) && (flag & FLAG_DATA_LOST) &&before(tp->snd_una, tp->high_seq) &&icsk->icsk_ca_state != TCP_CA_Open &&tp->fackets_out > tp->reordering) {tcp_mark_head_lost(sk, tp->fackets_out - tp->reordering);NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPLOSS);}...if (icsk->icsk_ca_state == TCP_CA_Open) {WARN_ON(tp->retrans_out != 0);tp->retrans_stamp = 0;// 判断当前的ACK是否覆盖了cover} else if (!before(tp->snd_una, tp->high_seq)) {...case TCP_CA_Disorder:// 如果可以可以undo dasck,代表了之前的重传都是误判。tcp_try_undo_dsack(sk);if (!tp->undo_marker ||/* For SACK case do not Open to allow to undo* catching for all duplicate ACKs. 没有必要如此严格 */// reno无法识别DSACK,因此就不必去检查它了tcp_is_reno(tp) || tp->snd_una != tp->high_seq) {tp->undo_marker = 0;tcp_set_ca_state(sk, TCP_CA_Open);}break;case TCP_CA_Recovery:if (tcp_is_reno(tp))tcp_reset_reno_sack(tp);// 如果是reno模式,那么为了防止不必要(此处应该用"地" )地再次进入"快速重传"状态// 必须要ACK超越cover!详见When to exit recoveryif (tcp_try_undo_recovery(sk))return;tcp_complete_cwr(sk);break;}}/* F. Process state. */switch (icsk->icsk_ca_state) {case TCP_CA_Recovery:if (!(flag & FLAG_SND_UNA_ADVANCED)) {// 这是在模拟reno的sack呢if (tcp_is_reno(tp) && is_dupack)tcp_add_reno_sack(sk);} else// 高版本的内核对此处理完全不一样,请参见图中Where to godo_lost = tcp_try_undo_partial(sk, pkts_acked);break;case TCP_CA_Loss:...default:if (tcp_is_reno(tp)) {if (flag & FLAG_SND_UNA_ADVANCED)tcp_reset_reno_sack(tp);if (is_dupack)tcp_add_reno_sack(sk);}if (icsk->icsk_ca_state == TCP_CA_Disorder)tcp_try_undo_dsack(sk);if (!tcp_time_to_recover(sk)) {// 仅仅在Open,CWR,Disorder状态下才会被调用tcp_try_to_open(sk, flag);return;}/* MTU probe failure: don't reduce cwnd */if (icsk->icsk_ca_state < TCP_CA_CWR &&icsk->icsk_mtup.probe_size &&tp->snd_una == tp->mtu_probe.probe_seq_start) {tcp_mtup_probe_failed(sk);/* Restores the reduction we did in tcp_mtup_probe() */tp->snd_cwnd++;tcp_simple_retransmit(sk);return;}/* Otherwise enter Recovery state */if (tcp_is_reno(tp))mib_idx = LINUX_MIB_TCPRENORECOVERY;elsemib_idx = LINUX_MIB_TCPSACKRECOVERY;NET_INC_STATS_BH(sock_net(sk), mib_idx);tp->high_seq = tp->snd_nxt;tp->prior_ssthresh = 0;tp->undo_marker = tp->snd_una;tp->undo_retrans = tp->retrans_out;if (icsk->icsk_ca_state < TCP_CA_CWR) {if (!(flag & FLAG_ECE))tp->prior_ssthresh = tcp_current_ssthresh(sk);tp->snd_ssthresh = icsk->icsk_ca_ops->ssthresh(sk);TCP_ECN_queue_cwr(tp);}tp->bytes_acked = 0;tp->snd_cwnd_cnt = 0;tcp_set_ca_state(sk, TCP_CA_Recovery);fast_rexmit = 1;}if (do_lost || (tcp_is_fack(tp) && tcp_head_timedout(sk)))tcp_update_scoreboard(sk, fast_rexmit);// 请注意,这是个可以修改的逻辑,在Linux 3.x中,其已经成了prr,然而2.6.32,并不。tcp_cwnd_down(sk, flag);// 按照优先级来传输,参见图中How(to retransmit)tcp_xmit_retransmit_queue(sk);
}
我们看tcp_ack的逻辑:if (tcp_ack_is_dubious(sk, flag)) {/* Advance CWND, if state allows this. */if ((flag & FLAG_DATA_ACKED) && !frto_cwnd &&tcp_may_raise_cwnd(sk, flag))tcp_cong_avoid(sk, ack, prior_in_flight);tcp_fastretrans_alert(sk, prior_packets - tp->packets_out,flag);} else {if ((flag & FLAG_DATA_ACKED) && !frto_cwnd)tcp_cong_avoid(sk, ack, prior_in_flight);}

然后,我们看一下4.3的逻辑:

static bool tcp_try_undo_partial(struct sock *sk, const int acked,const int prior_unsacked, int flag)
{struct tcp_sock *tp = tcp_sk(sk);if (tp->undo_marker && tcp_packet_delayed(tp)) {/* Plain luck! Hole if filled with delayed* packet, rather than with a retransmit.*/tcp_update_reordering(sk, tcp_fackets_out(tp) + acked, 1);/* We are getting evidence that the reordering degree is higher* than we realized. If there are no retransmits out then we* can undo. Otherwise we clock out new packets but do not* mark more packets lost or retransmit more.*/// 仅仅在第一次的时候,undo make明确为UNA的位置,然而收到第一个patial ACK的时候// 会判断是否有数据包在重传中,如果有,就不忙着再标记LOST段了,而是什么都不做,将// 窗口留给新数据if (tp->retrans_out) {tcp_cwnd_reduction(sk, prior_unsacked, 0, flag);return true;}if (!tcp_any_retrans_done(sk))tp->retrans_stamp = 0;DBGUNDO(sk, "partial recovery");// 从此以后,undo make为0,就完全按照sack和reordering的差值来标记LOST了tcp_undo_cwnd_reduction(sk, true);NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPARTIALUNDO);tcp_try_keep_open(sk);return true;}return false;
}
在tcp_ack中:if (tcp_ack_is_dubious(sk, flag)) {// 这里不再针对dubious情形的ack也进行tcp_may_raise_cwnd的判断,// 从而在允许的情况下依然增加拥塞窗口。is_dupack = !(flag & (FLAG_SND_UNA_ADVANCED | FLAG_NOT_DUP));tcp_fastretrans_alert(sk, acked, prior_unsacked,is_dupack, flag);}if (tp->tlp_high_seq)tcp_process_tlp_ack(sk, ack, flag);/* Advance cwnd if state allows */// 在这里进行tcp_may_raise_cwnd判断,保证在高乱序的情况下依然可以增加拥塞窗口// 1.alert中可能会进行update reordering// 2.alert中会在partial ACK之后进入Disorder/Open状态if (tcp_may_raise_cwnd(sk, flag))tcp_cong_avoid(sk, ack, acked);

而且在tcp_may_raise_cwnd中,会对reordering变大的情况做出补偿,因为此时,基本已经可以判定,并不是丢包,而是乱序导致了SACK!
最后,这并不是本系列文章的终结,我本想总结一下TCP拥塞控制的各种计数器,但是觉得那无非又是一番字词句段篇章,毫无意义,如果读懂了RFC,一切都好办了。

Linux TCP实现实在太烂了,但是我不觉得它比OpenSSL更烂,也不比OpenVPN更烂,不是吗?我吐槽过OpenSSL和OpenVPN,然而最终我放弃了OpenSSL,因为我知道It is beyond my ability!如今我不再吐槽了,因为无力做没有意义的事情了。

在此,我纠正一下措辞,马上着手另外一件事去了,不管怎么说,在一件事没有彻底(起码要60%+吧)搞明白之前,最好不要去搞别的,这会产生夹生饭。然而在我们的传统中,这好像毫无必要!因为我们的四大发明(这个关于四大发明的话题我会另外写一篇文章的,敬请期待)没有一个是知道了起码60%的原理后搞出来,这倒不是要反衬西方的实践都是在理解原理的前提下做出的,比如珍妮纺纱机,比如希腊火之类的,我要说的是,我们这里拥有一种魔法,正如中学时的化学老师所说的那样,我们的先人不知道什么是“酸”,但是却可以造出醋!于是我们都深深的受到了影响,于是就出现了大量的未知酸,先有醋的东西。大量的抄袭,大量的盗版,大量的毫无创意的模仿,但始终没有原创,因为大多数人一直都在追求的是一种所谓的捷径,而不是对知识的持续努力的积累,古人说过一句比较好的话,大意就是背诵了唐诗三百首,文章自然就流露出来了(不会写,也会偷),虽然也是教人模仿,但是起码那需要硬努力,要么你花点时间研究一下平仄的规律,要么你就背诵大量的现成的诗去自己总结规律,难道还有别的路吗??如果一开始上来就动笔,拿出来的可能会是一首诗,然而绝大多数是打油诗。

如果只做服务器而不是转发,针对路由子系统的工作就显得没有意义了...

附:Linux 2.6.32和3.x在undo时的窗口处理

我们比较关注TCP在快速恢复结束后窗口会怎样,它是不是被设置成降窗开始时的ssthresh呢?我们先看2.6.32的代码

        case TCP_CA_Recovery:if (tcp_is_reno(tp))tcp_reset_reno_sack(tp);// 如果是reno模式,那么为了防止不必要(此处应该用"地" )地再次进入"快速重传"状态// 必须要ACK超越cover!详见When to exit recoveryif (tcp_try_undo_recovery(sk))return;// 上面的undo中可能存在may undo为真的情况,意味着所有的重传均是误判,因此窗口// 会恢复到之前的大小,然而一切都被下面的complete函数拉回来了,它无条件取当前// 窗口和ssthresh的最小值作为新窗口tcp_complete_cwr(sk);break;

然后再看下3.10的代码:

        case TCP_CA_Recovery:if (tcp_is_reno(tp))tcp_reset_reno_sack(tp);if (tcp_try_undo_recovery(sk))return;// 我把下列函数中的一个注释提到这里:// "/* Reset cwnd to ssthresh in CWR or Recovery (unless it's undone) */"// 这意味着什么?这意味着如果在undo_recovery中undo_marker变成0了,也就是说// may_undo返回了真,那么就不必将窗口reset到ssthresh了,因为undo操作已经将// 窗口恢复到之前的值了。// 这是十分合理的,然而是有条件的,条件就是之前的重传都是误判,均被DSACK了,// 这个条件并不苛刻,既然是误判,当然可以恢复拥塞之前的值了,然而,我们能否// 激进一点呢? :-(tcp_end_cwnd_reduction(sk);break;

其实,围绕这快速恢复结束后窗口应该在哪里这个问题,可以连续扯上一整天,但是我觉得这就好像两个势均力敌的人在扳手腕一样,状态是胶着的。

TCP拥塞控制图解(不包括RTO,因为它太简单了)相关推荐

  1. TCP拥塞控制图解(不包括RTO,因为它太简单了) 【勘误1】

    熬过了几个夜晚,终于把TCP的拥塞处理的Linux撸了一遍,仓促中也总结了一幅巨大的图,然而今天下午的例会讨论后,我自己说着说着发现还有一些值得商榷的地方,有的是笔误,也有的是一些细节依然没有搞清楚, ...

  2. ​TCP 拥塞控制详解

    作者:engleliu,腾讯 PCG 开发工程师 本文主要介绍 TCP 拥塞控制算法,内容多来自网上各个大佬的博客及<TCP/IP 详解>一书,在此基础上进行梳理总结,与大家分享.因水平有 ...

  3. 传输层协议TCP—拥塞控制(12)

    1 拥塞控制简介 拥塞控制讲述的则是从如何避免网络拥塞的视角或者网络已经拥塞的情形下,TCP 对应的算法和处理机制.TCP 拥塞控制(对应 RFC 5681)包括4个算法(机制):慢速启动.拥塞避免. ...

  4. 网络基本功:http报文及TCP拥塞控制机制

    Http报文 HTTP请求报文由请求行.请求头部.空行和请求包体4个部分组成,如下图所示: 1.1.通用报文 General: //通用报文 Request Method: GET Status Co ...

  5. 或许,这是你见过最全的TCP+UDP图解系列

    或许,这是你见过最全的TCP+UDP图解系列 01 图解TCP TCP首部 流量控制 拥塞控制 三次握手,四次挥手 tcp 怎样保证数据正确性? 流量控制是为了让接收方能来得及接收,而拥塞控制是为了降 ...

  6. TCP流量控制-TCP拥塞控制 拥塞控制与流量控制的区别

    拥塞控制与流量控制的区别: 拥塞控制是让网络能够承受现有的网络负荷,是一个全局性的过程,涉及所有的主机.所有的路由器,以及与降低网络传输性能有关的所有因素. 相反,流量控制往往是指点对点的通信量的控制 ...

  7. 计算机网络-基本概念(9)【传输层】TCP拥塞控制 【网络层】拥塞避免

    TCP拥塞控制 对网络中某一资源的需求超过了该资源(带宽.交换节点中的缓存.处理机)所能提供的可用部分.防止过多的数据注入网络中,防止路由器或链路过载,是属于全局性的过程,包括主机.路由器.链路等设备 ...

  8. TCP拥塞控制和TCP流量控制

    转自:https://blog.csdn.net/qq_38623623/article/details/81290265 TCP拥塞控制 提高网络利用率,降低丢包率,并保证网络资源对每条数据流的公平 ...

  9. 千兆TCP拥塞控制算法分析

    作者:Geoff Huston,APNIC 回顾30多年来的互联网从业经验,我发现:促使互联网协议套件成功地成为全球通信系统首选技术的关键,是互联网协议(IP)本身.作为一种重叠协 议,它能够支持几乎 ...

最新文章

  1. 最全综述 | 图像目标检测
  2. 文件上传漏洞及解决办法
  3. 基于matlab的卷积码实验报告,基于MATLAB的卷积码编译码设计仿真.doc
  4. Swift编码总结8
  5. 鸿蒙硬件HI3861开发环境搭建
  6. [LeetCode] Power of Four
  7. elementui tree获取父节点_elementUI 树状图 点击子节点获取父节点
  8. 马来西亚 IT 决策者正转向开源来最大化 IT 功能
  9. AWT_Swing_JPasswordField密码框(Java)
  10. Hive 基础及安装
  11. 报告表明混合云带来IT管理挑战
  12. Codeforces 208E. Blood Cousins
  13. 计算机辅助翻译入门编委,计算机辅助翻译入门简介,目录书摘
  14. 安装ie9提示未能完成安装_升级Internet Explorer未能完成安装四种解决措施
  15. 推荐阅读《未来世界的幸存者》
  16. adb 静默安装_Android静默安装与静默卸载(系统应用)
  17. 微信小游戏上传设置成体验版或者提交审核
  18. 计算机重装系统后无法重启,u盘安装系统之后重启电脑没反应怎么办
  19. QQ空间批量删除留言
  20. 高德地图E/libEGL: call to OpenGL ES API with no current context (logged once per thread)

热门文章

  1. Kotlin 视频教程系列 陈光剑
  2. 企业网页设计制作:打造品牌形象的突破口
  3. 找出数组中符合条件的数对的个数
  4. 报错“can‘t pickle onnxruntime.capi.onnxruntime_pybind11_state.InferenceSession objects“问题解决
  5. matlab roots 多项式实现 c语言,Matlab教学课apos;件教学教案.doc
  6. Unity打包iOS自动拷贝1024图标到xcode工程中(上架AppStore需要设置1024*1024图标)
  7. Qt开发基础(10)——定时器
  8. 怎么设置oracle变量环境变量,window中oracle环境变量设置方法分享
  9. a16z crypto合伙人:区块链的魅力与挑战 |链捕手
  10. 【别人的原创】上点、离点、内点