我们发送重传包时,重传包也可能丢失,如果没有检查重传包是否丢失的机制,那么只能依靠超时来恢复了。

37版本把检查重传包是否丢失的部分独立出来,这就是tcp_mark_lost_retrans()。

在处理SACK块的同时,会检测是否有出现乱序,如果有乱序,那么会计算乱序的长度并更新。

本文主要内容:检查重传包是否丢失,以及乱序的检测和更新。

Author:zhangskd @ csdn

检查重传包是否丢失

tcp_mark_lost_retrans()用于检查重传的包是否丢失,2.6.22内核在检查重传包是否丢失时是有Bug的,

具体可见:http://projects.itri.aist.go.jp/gnet/sack-bug.html

Q: 怎么检查重传包是否丢失呢?

A: 我们知道,要发送数据时,是先发送重传包,之后才发送新包的。

如果重传包顺利到达接收端,当新包到达时,服务器端会收到一个对新包的正常确认。

如果重传包丢失了,当新包到达时,服务器端会收到一个对新包的选择性确认。

基于这个事实:

当重传一个包时,我们记录当时要发送的下一新包的序列号(当时的tp->snd_nxt)。

当我们收到SACK时,就检查新包是被正常ACK,还是被SACK。如果新包被SACK,

但是重传包还没有,就说明当时重传的包已经丢失了。

重传一个包时,会记录当时要发送的下一个新包的序号,即tp->snd_nxt。

  1. int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb)
  2. {
  3. ...
  4.  
  5. /* 如果之前网络中没有重传包 */
  6. if (! tp->retrans_out)
  7. tp->lost_retrans_low = tp->snd_nxt;
  8.  
  9. TCP_SKB_CB(skb)->sacked |= TCPCB_RETRANS; /* 打上R标记 */
  10. tp->retrans_out += tcp_skb_pcount(skb); /* 更新retrans_out */
  11.  
  12. /* Save stamp of the first retransmit. */
  13. if (! tp->retrans_stamp)
  14. tp->retrans_stamp = TCP_SKB_CB(skb)->when;
  15.  
  16. tp->undo_retrans++;
  17.  
  18. /* snd_nxt is stored to detect loss of retransmitted segment,
  19. * see tcp_input.c tcp_sacktag_write_queue().
  20. * 就是在这里!把这时的snd_nxt保存到重传包的ack_seq。
  21. */
  22. TCP_SKB_CB(skb)->ack_seq = tp->snd_nxt;
  23.  
  24. ...
  25. }

检查重传包是否丢失,如果丢失了,重新打L标志。

  1. /* Check for lost retransmit. */
  2. static void tcp_mark_lost_retrans(struct sock *sk)
  3. {
  4. const struct inet_connection_sock *icsk = inet_csk(sk);
  5. struct tcp_sock *tp = tcp_sk(sk);
  6. struct sk_buff *skb;
  7. int cnt = 0;
  8. u32 new_low_seq = tp->snd_nxt; /* 下一个要发送的新包序列号 */
  9. u32 received_upto = tcp_highest_sack_seq(tp); /* 被SACK过的最大序列号 */
  10.  
  11. /* 使用这个方法的条件:
  12. * 使用FACK;有重传包;上次的最低snd_nxt被SACK;处于Recovery状态
  13. */
  14. if (! tcp_is_fack(tp) || ! tp->retrans_out || ! after(received_upto, tp->lost_retrans_low)
  15. || icsk->icsk_ca_state != TCP_CA_Recovery)
  16. return;
  17.  
  18. tcp_for_write_queue(skb, sk) {
  19. /* 注意了:对于重传包来说,ack_seq其实是当时的snd_nxt */
  20. u32 ack_seq = TCP_SKB_CB(skb)->ack_seq;
  21.  
  22. if (skb == tcp_send_head(sk)) /* 发送队列头了 */
  23. break;
  24.  
  25. /* 我们关注的是重传的包,如果遍历完了,就退出 */
  26. if (cnt == tp->retrans_out)
  27. break;
  28.  
  29. /* 不关心成功确认过的包 */
  30. if (! after(TCP_SKB_CB(skb)->end_seq, tp->snd_una))
  31. continue;
  32.  
  33. /* 只关注重传包,必须有R标志才处理 */
  34. if (! (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_RETRANS))
  35. continue;
  36.  
  37. /*
  38. * 如果重传包记录的snd_nxt被SACK了,那说明重传包丢了;否则应该在新包之前被确认才对。
  39. */
  40. if (after(received_upto, ack_seq)) {
  41. TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_RETRANS; /* 取消R标志 */
  42. tp->retrans_out -= tcp_skb_pcount(skb); /* 更新网络中重传包数量 */
  43. tcp_skb_mark_lost_uncond_verify(tp, skb); /* 给重传包打上LOST标志,并更新相关变量 */
  44. NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPLOSTRETRANSMIT);
  45.  
  46. } else { /* 如果重传包对应的snd_nxt在最高SACK序列号之后 */
  47. if (before(ack_seq, new_low_seq))
  48. new_low_seq = ack_seq; /* 更新未检测的重传包对应的最小snd_nxt */
  49. cnt += tcp_skb_pcount(skb); /* 用于判断重传包是否检查完了 */
  50. }
  51. }
  52.  
  53. /* 如果还有未检查完的重传包,那么更新未检测的重传包对应的最小snd_nxt */
  54. if (tp->retrans_out)
  55. tp->lost_retrans_low = new_low_seq;
  56.  
  57. }

给数据包打上LOST标志,更新相关变量。

  1. static void tcp_skb_mark_lost_uncond_verify(struct tcp_sock *tp, struct sk_buff *skb)
  2. {
  3. /* 更新重传过的包的最低、最高序号 */
  4. tcp_verfiy_retransmit_hint(tp, skb);
  5.  
  6. /* 如果这个包还未打上L标志,且没有S标志 */
  7. if (! (TCP_SKB_CB(skb)->sacked & (TCP_LOST | TCPCB_SACKED_ACKED))) {
  8. tp->lost_out += tcp_skb_pcount(skb); /* 更新网络中丢失包数量 */
  9. TCP_SKB_CB(skb)->sacked |= TCPCB_LOST; /* 打上L标志 */
  10. }
  11. }
  12.  
  13. /* This must be called before lost_out is incremented
  14. * 记录重传过的包的最低序号、最高序号。
  15. */
  16. static void tcp_verify_retransmit_hint(struct tcp_sock *tp, struct sk_buff *skb)
  17. {
  18. if ((tp->retransmit_skb_hint == NULL) || before(TCP_SKB_CB(skb)->seq,
  19. TCP_SKB_CB(tp->retransmit_skb_hint)->seq))
  20. tp->retransmit_skb_hint = skb;
  21.  
  22. if (! tp->lost_out || after(TCP_SKB_CB(skb)->end_seq, tp->retransmit_high))
  23. tp->retransmit_high = TCP_SKB_CB(skb)->end_seq;
  24. }

乱序处理

说明

Reordering metric is maximal distance, which a packet can be displaced in packet stream.

With SACKs we can estimate it:

1. SACK fills old hole and the corresponding segment was not ever retransmitted -> reordering.

Alas, we cannot use it when segment was retransmitted.

2. The last flaw it solved with D-SACK. D-SACK arrives for retransmitted and already SACKed segment

-> reordering..

Both of these heuristics are not used in Loss state, when we cannot account for retransmits accurately.

对于乱序,我们主要关注如何检测乱序,以及计算乱序的长度。

在tcp_sacktag_one()中有进行乱序的检测,那么在收到SACK或DSACK时怎么判断有乱序呢?

(1)skb的记分牌为S|R,然后它被DSACK。

我们想象一下,一个数据包乱序了,它滞留在网络的某个角落里。我们收到后续包的SACK,认为这个包丢失了,

进行重传。之后原始包到达接收端了,这个数据包被SACK了。最后重传包也到达接收端了,这个包被DSACK了。

(2)如果一个包落在highest_sack之前,它既没被SACK过,也不是重传的,那么它肯定是乱序了,到现在才被SACK。

如果检测到了乱序,那么乱序队列的长队为:tp->fackets_out - state.reord。

  1. static void tcp_update_reordering(struct sock *sk, const int metric,
  2. const int ts)
  3. {
  4. struct tcp_sock *tp = tcp_sk(sk);
  5.  
  6. if (metric > tp->reordering) {
  7. int mib_idx;
  8. /* 更新reordering的值,取其小者*/
  9. tp->reordering = min(TCP_MAX_REORDERING, metric);
  10.  
  11. if (ts)
  12. mib_idx = LINUX_MIB_TCPTSREORDER;
  13. else if (tcp_is_reno(tp))
  14. mib_idx = LINUX_MIB_TCPRENOREORDER;
  15. else if (tcp_is_fack(tp))
  16. mib_idx = LINUX_MIB_TCPFACKREORDER;
  17. else
  18. mib_idx = LINUX_MIB_TCPSACKREORDER;
  19.  
  20. NET_INC_STATS_BH(sock_net(sk), mib_idx);
  21. #if FASTRETRANS_DEBUG > 1
  22. printk(KERN_DEBUG "Disorder%d %d %u f%u s%u rr%d\n",
  23. tp->rx_opt.sack_ok, inet_csk(sk)->icsk_ca_state,
  24. tp->reordering, tp->fackets_out, tp->sacked_out,
  25. tp->undo_marker ? tp->undo_retrans : 0);
  26. #endif
  27. tcp_disable_fack(tp); /* 出现了reorder,再用fack就太激进了*/
  28. }
  29. }
  1. /* Packet counting of FACK is based on in-order assumptions, therefore
  2. * TCP disables it when reordering is detected.
  3. */
  4. static void tcp_disable_fack(struct tcp_sock *tp)
  5. {
  6. /* RFC3517 uses different metric in lost marker => reset on change */
  7. if (tcp_is_fack(tp))
  8. tp->lost_skb_hint = NULL;
  9. tp->rx_opt.sack_ok &= ~2; /* 取消FACK选项*/
  10. }

TCP的核心系列 — SACK和DSACK的实现(七)的更多相关文章

  1. TCP的核心系列 — SACK和DSACK的实现(一)

    TCP的实现中,SACK和DSACK是比较重要的一部分. SACK和DSACK的处理部分由Ilpo Järvinen (ilpo.jarvinen@helsinki.fi) 维护. tcp_ack() ...

  2. TCP的核心系列 — SACK和DSACK的实现(二)

    和18版本相比,37版本的SACK和DSACK的实现做了很多改进,最明显的就是需要遍历的次数少了, 减少了CPU的消耗.37版的性能提升了,代码有大幅度的改动,逻辑也更加复杂了. 本文主要内容:37版 ...

  3. TCP的核心系列 — SACK和DSACK的实现(三)

    不论是18版,还是37版,一开始都会从TCP的控制块中取出SACK选项的起始地址. SACK选项的起始地址是保存在tcp_skb_cb结构的sacked项中的,那么这是在什么时候做的呢? SACK块并 ...

  4. TCP的核心系列 — SACK和DSACK的实现(六)

    上篇文章中我们主要说明如何skip到一个SACK块对应的开始段,如何walk这个SACK块包含的段,而没有涉及到 如何标志一个段的记分牌.37版本把给一个段打标志的内容独立出来,这就是tcp_sack ...

  5. TCP的核心系列 — SACK和DSACK的实现(五)

    18版本对于每个SACK块,都是从重传队列头开始遍历.37版本则可以选择性的遍历重传队列的某一部分,忽略 SACK块间的间隙.或者已经cache过的部分.这主要是通过tcp_sacktag_skip( ...

  6. TCP的核心系列 — SACK和DSACK的实现(四)

    和18版本不同,37版本把DSACK的检测部分独立出来,可读性更好. 37版本在DSACK的处理中也做了一些优化,对DSACK的两种情况分别进行处理. 本文主要内容:DSACK的检测.DSACK的处理 ...

  7. TCP的核心系列 — ACK的处理(二)

    本文主要内容:tcp_ack()中的一些细节,如发送窗口的更新.持续定时器等. 内核版本:3.2.12 Author:zhangskd @ csdn 发送窗口的更新 什么时候需要更新发送窗口呢? (1 ...

  8. TCP的核心系列 — ACK的处理(一)

    TCP发送数据包后,会收到对端的ACK.通过处理ACK,TCP可以进行拥塞控制和流控制,所以 ACK的处理是TCP的一个重要内容.tcp_ack()用于处理接收到的ACK. 本文主要内容:TCP接收A ...

  9. TCP的核心系列 — 重传队列的更新和时延的采样(二)

    在tcp_clean_rtx_queue()中,并非对每个ACK都进行时延采样.是否进行时延采样,跟这个ACK是否为 重复的ACK.这个ACK是否确认了重传包,以及是否使用时间戳选项都有关系. 本文主 ...

随机推荐

  1. JavaWeb 文件 上传 下载

    文件上传下载对于一个网站来说,重要性不言而喻.今天来分享一个JavaWeb方式实现的文件上传下载的小例子. 项目依赖 项目目录 工作流程 文件上传 表单处的设置 服务器端 上传功能的实现 upload ...

  2. Google Dremel数据模型详解(下)

    "神秘"的r和d 单从数据结构来看的话,我们可以这样解释r和d的含义.r代表着当前字段与前一字段的关系,是在哪一层合并的,即公共的父结点在哪?举例来说,假如我们重建到了Code=' ...

  3. android ndk之hello world

    前言:Android NDK r7及以上的版本已经集成了Cygwin编译环境,也就是说,我们完全可以抛弃庞大的Cygwin了. r6及以下版本,也可以抛弃几个G的完整版,使用精简过的Mini-Cygw ...

  4. sql中InnoDB和MyISAM的区别

    InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型 1,MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快,但是不提供事务支持等高级处理,往往被认为只适合小项目:而 ...

  5. Objc生成搜索引擎查询字符串

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 拿baidu为例,百度的搜索url为: http://www. ...

  6. Error running app: Instant Run requires 'Tools | Android | Enable ADB integration' to be enabled.

    废了半天劲才解决... 就三步:菜单栏,Tools -> Adnroid -> enable ADB integration

  7. J2EE学习从菜鸟变大鸟之九 深入浅出理解 Servlet-----实例解析

    关于Servlet的基础内容在前面已经和大家分享过了,参考J2EE学习从菜鸟变大鸟之七 Servlet,现在结合到DRP中学习,深刻的体会Servlet起到了枢纽中转的作用,控制逻辑(到MVC中更像是 ...

  8. Uva - 210 - Concurrency Simulator

    自己写个双端队列,或者直接用deque,这个也比较好用 AC代码: #include <iostream> #include <cstdio> #include <cst ...

  9. Chapter 2 User Authentication, Authorization, and Security(6):服务器权限授予粒度

    原文出处:http://blog.csdn.net/dba_huangzj/article/details/38867489,专题目录:http://blog.csdn.net/dba_huangzj ...

  10. (二)php的常量和变量

    [php在命令行下接收参数] 如果在命令行调试php,传入的参数通过$argv获取,注意其中包含了文件名这一个元素,数组中元素的个数通过$argc获取. [可变变量] 指的是变量的名称可变,变量的标识 ...