接收到数据报后,会调用tcp_event_data_recv(),不管是在慢速路径的tcp_data_queue中调用还是 在快速路径中处理接收数据后直接调用,注意(如果len <= tcp_header_len 则是没有载荷),不会调用tcp_event_date_recv处理。

tcp_event_data_recv()中设置ICSK_ACK_SCHED标志来表明有ACK需要发送。如果接收到了小包,说明对端很可能暂时没有数据需要发送了,此时会设置ICSK_ACK_PUSHED标志,如果处于快速路径中,就允许马上发送ACK。如果不止一次接收到小包,就设置ICSK_ACK_PUSHED2标志,不管是否处于快速路径中,都允许立即发送ACK,以强调发送ACK的紧急程度。

  1. enum inet_csk_ack_state_t {
  2. ICSK_ACK_SCHED = 1, /* 有ACK需要发送 */
  3. ICSK_ACK_TIMER = 2, /* 延迟确认定时器已经启动 */
  4. ICSK_ACK_PUSHED = 4, /* 如果处于快速发送模式,允许立即发送ACK */
  5. ICSK_ACK_PUSHED2 = 9 /* 无论是否处于快速发送模式,都可以立即发送ACK */
  6.  
  7. }
  1. /* There is something which you must keep in mind when you analyze the
  2. * behavior of the tp->ato delayed ack timeout interval. When a
  3. * connection starts up, we want to ack as quickly as possible. The
  4. * problem is that "good" TCP's do slow start at the beginning of data
  5. * transmission. The means that until we send the first few ACK's the
  6. * sender will sit on his end and only queue most of his data, because
  7. * he can only send snd_cwnd unacked packets at any given time. For
  8. * each ACK we send, he increments snd_cwnd and transmits more of his
  9. * queue. -DaveM
  10. struct inet_connection_sock {
  11. /* inet_sock has to be the first member! *//*
  12. struct inet_sock icsk_inet;
  13. struct request_sock_queue icsk_accept_queue;
  14. struct inet_bind_bucket *icsk_bind_hash;
  15. unsigned long icsk_timeout;
  16. struct timer_list icsk_retransmit_timer;
  17. struct timer_list icsk_delack_timer;
  18. __u32 icsk_rto;
  19. __u32 icsk_pmtu_cookie;
  20. const struct tcp_congestion_ops *icsk_ca_ops;
  21. const struct inet_connection_sock_af_ops *icsk_af_ops;
  22. unsigned int (*icsk_sync_mss)(struct sock *sk, u32 pmtu);
  23. __u8 icsk_ca_state:6,
  24. icsk_ca_setsockopt:1,
  25. icsk_ca_dst_locked:1;
  26. __u8 icsk_retransmits;
  27. __u8 icsk_pending;
  28. __u8 icsk_backoff;
  29. __u8 icsk_syn_retries;
  30. __u8 icsk_probes_out;
  31. __u16 icsk_ext_hdr_len;
  32. struct {
  33. /* ACK is pending.
  34. * ACK的发送状态标志,可以表示四种情况:
  35. * 1. ICSK_ACK_SCHED:目前有ACK需要发送
  36. * 2. ICSK_ACK_TIMER:延迟确认定时器已经启动
  37. * 3. ICSK_ACK_PUSHED:如果处于快速确认模式,允许立即发送ACK
  38. * 4. ICSK_ACK_PUSHED2:无论是否处于快速确认模式,都可以立即发送ACK
  39.  
  40. __u8 pending; /* ACK is pending
  41. //快速确认模式下,最多能够发送多少个ACK,额度用完以后就退出快速确认模式
  42. __u8 quick; /* Scheduled number of quick acks */
  43. /* 值为1时,为延迟确认模式;值为0时,为快速确认模式。
  44. * 注意这个标志是不是永久性的,而是动态变更的
  45. __u8 pingpong; /* The session is interactive
  46. /*
  47. * 如果延迟确认定时器触发时,发现socket被用户进程锁住,就把blocked置为1。
  48. * 之后在接收到新数据、或者将数据复制到用户空间之后、或者再次超时时,会马上发送ACK
  49.  
  50. __u8 blocked; /* Delayed ACK was blocked by socket lock
  51. /*
  52. * ACK的超时时间,是一个中间变量,根据接收到数据包的时间间隔来动态调整。
  53. * 用来计算延迟确认定时器的超时时间timeout。
  54.  
  55. __u32 ato; /* Predicted tick of soft clock
  56. //延迟确认定时器的超时时刻。
  57. unsigned long timeout; /* Currently scheduled timeout
  58. //最后一次收到带负荷的报文的时间点。
  59. __u32 lrcvtime; /* timestamp of last received data packet
  60. __u16 last_seg_size; /* Size of last incoming segment segment上次收到的数据段大小
  61. __u16 rcv_mss; /* MSS used for delayed ACK decisions
  62. } icsk_ack;
  63. ***/
    static inline void inet_csk_schedule_ack(struct sock *sk)
    {
        inet_csk(sk)->icsk_ack.pending |= ICSK_ACK_SCHED;
    }

  64. static void tcp_event_data_recv(struct sock *sk, struct sk_buff *skb)
  65. {
  66. struct tcp_sock *tp = tcp_sk(sk);
  67. struct inet_connection_sock *icsk = inet_csk(sk);
  68. u32 now;
  69.  
  70. inet_csk_schedule_ack(sk);/* 接收到了数据,设置ACK需调度标志*/
  71.  
  72. /* 通过接收到的数据段,来估算对端的MSS。
  73. * 如果接收到了小包,则设置ICSK_ACK_PUSHED标志。
  74. * 如果之前接收过小包,本次又接收到了小包,则设置ICSK_ACK_PUSHED2标志。
  75. */
  76. tcp_measure_rcv_mss(sk, skb);
  77.  
  78. /* 没有使用时间戳选项时的接收端RTT计算 */
  79. tcp_rcv_rtt_measure(tp);
  80.  
  81. now = tcp_time_stamp;
  82. /* 如果是第一次接收到带负荷的报文 */
  83. if (!icsk->icsk_ack.ato) {
  84. /* The _first_ data packet received, initialize
  85. * delayed ACK engine.
  86. */
  87. tcp_incr_quickack(sk);/* 设置在快速确认模式中可以发送的ACK数量 */
  88. icsk->icsk_ack.ato = TCP_ATO_MIN;/* ato的初始值,为40ms */
  89. } else {
  90. int m = now - icsk->icsk_ack.lrcvtime;/* 距离上次收到数据报的时间间隔 */
  91. /*
  92. 同时根据距离上次接收到数据报的时间间隔,来动态调整icsk->icsk_ack.ato:
  93. 1. delta <= TCP_ATO_MIN /2时,ato = ato / 2 + TCP_ATO_MIN / 2。
  94. 2.?TCP_ATO_MIN / 2 < delta <= ato时,ato = min(ato / 2 + delta, rto)。
  95. 3. delta > ato时,ato值不变。
  96. 如果接收到的数据包的时间间隔变小,ato也会相应的变小。
  97. 如果接收到的数据包的时间间隔变大,ato也会相应的变大。
  98. */
  99. if (m <= TCP_ATO_MIN / 2) {
  100. /* The fastest case is the first. */
  101. icsk->icsk_ack.ato = (icsk->icsk_ack.ato >> 1) + TCP_ATO_MIN / 2;
  102. } else if (m < icsk->icsk_ack.ato) {
  103. icsk->icsk_ack.ato = (icsk->icsk_ack.ato >> 1) + m;
  104. if (icsk->icsk_ack.ato > icsk->icsk_rto)
  105. icsk->icsk_ack.ato = icsk->icsk_rto;
  106. } else if (m > icsk->icsk_rto) {
  107. /* Too long gap. Apparently sender failed to
  108. * restart window, so that we send ACKs quickly.
  109. */
  110. tcp_incr_quickack(sk);
  111. sk_mem_reclaim(sk);
  112. }
  113. }
  114. icsk->icsk_ack.lrcvtime = now;/* 更新最后一次接收到数据报的时间 */
  115.  
  116. tcp_ecn_check_ce(tp, skb); /* 如果发现显示拥塞了,就进入快速确认模式 */
  117. /* 当报文段的负荷不小于128字节时,考虑增大接收窗口当前阈值 */
  118. if (skb->len >= 128)
  119. tcp_grow_window(sk, skb); /* 根据接收到的数据段的大小,来调整接收窗口的阈值rcv_ssthresh */
  120. }

MSS,最大报文段长度

在连接建立的时候,即在发送 SYN 段的时候,    同时会将 MSS 发送给对方(MSS 选项只能出现在 SYN 段中!!!),    告诉对端它期望接收的 TCP 报文段数据部分最大长度。

  • 建立MSS所基于的MTU的值基于路径MTU发现机制获取------------------------多好

那么MTU和MSS又有什么必然联系呢?

虽然MTU限制了IP层的报文大小, 但分层网络模型本来不就是为了对上层提供透明的服务么?即使一个很大的TCP报文传递给IP层, IP层也应该可以经过分段等手段成功传输报文才对。理论上来说是没错的,UDP中就不存在MSS,UDP生成任意大的UDP报文,然后包装成IP报文根据底层网络的MTU分段进行发送。MSS存在的本质原因就是TCP和UDP的根本不同:TCP提供稳定的连接。假设生成了很大的TCP报文,经过IP分段进行发送,而其中一个IP分段丢失了,则TCP协议需要重发整个TCP报文,造成了严重的网络性能浪费,而相对的由于UDP无保证的性质,即使丢失了IP分段也不会进行重发。所以说,MSS存在的核心作用,就是避免由于IP层对TCP报文进行分段而导致的性能下降。

  1. /* Adapt the MSS value used to make delayed ack decision to the
  2. * real world.
  3. */
  4. static void tcp_measure_rcv_mss(struct sock *sk, const struct sk_buff *skb)
  5. {
  6. struct inet_connection_sock *icsk = inet_csk(sk);
  7. const unsigned int lss = icsk->icsk_ack.last_seg_size;/* 上次收到的数据段大小 */
  8. unsigned int len;
  9.  
  10. icsk->icsk_ack.last_seg_size = 0;
  11.  
  12. /* skb->len may jitter because of SACKs, even if peer
  13. * sends good full-sized frames.
  14. */
  15. len = skb_shinfo(skb)->gso_size ? : skb->len;/// 本次接收到数据的长度
  16. //如果本次接收到数据的长度,大于当前发送方的MSS
  17. /*
  18. MSS 是软件层的概念,它是由软件控制的
  19. MTU 是硬件(比如网卡出口)的属性,是指二层链路层帧携带的数据最大大小。
  20.  
  21. */
  22. if (len >= icsk->icsk_ack.rcv_mss) {
  23. icsk->icsk_ack.rcv_mss = min_t(unsigned int, len,
  24. tcp_sk(sk)->advmss);
  25. /* Account for possibly-removed options */
  26. if (unlikely(len > icsk->icsk_ack.rcv_mss +
  27. MAX_TCP_OPTION_SPACE))
  28. tcp_gro_dev_warn(sk, skb, len);
  29. } else {
  30. /* Otherwise, we make more careful check taking into account,
  31. * that SACKs block is variable.
  32. *
  33. * "len" is invariant segment length, including TCP header.
  34. *///之前的len表示数据的长度,现在加上TCP首部的长度,这才是总的长度
  35. len += skb->data - skb_transport_header(skb);
  36. if (len >= TCP_MSS_DEFAULT + sizeof(struct tcphdr) ||
  37. /* If PSH is not set, packet should be
  38. * full sized, provided peer TCP is not badly broken.
  39. * This observation (if it is correct 8)) allows
  40. * to handle super-low mtu links fairly.
  41.  
  42. 满足以下条件时,说明接收到的数据段还是比较正常的,尝试更精确的计算MSS,
  43. * 排除SACK块的影响,更新last_seg_size和rcv_mss。
  44. */
  45. (len >= TCP_MIN_MSS + sizeof(struct tcphdr) &&
  46. !(tcp_flag_word(tcp_hdr(skb)) & TCP_REMNANT))) {
  47. /* Subtract also invariant (if peer is RFC compliant),
  48. * tcp header plus fixed timestamp option length.
  49. * Resulting "len" is MSS free of SACK jitter.
  50. *///减去报头和时间戳选项的长度,剩下的就是数据和SACK块(如果有的话)
  51. len -= tcp_sk(sk)->tcp_header_len;
  52. icsk->icsk_ack.last_seg_size = len;//更新最近一次接收到的数据段的长度
  53. if (len == lss) {//说明这次收到的还是full-sized,而不是小包
  54. icsk->icsk_ack.rcv_mss = len;
  55. return;
  56. }
  57. }//如果之前已经收到了小包,则进入更紧急的ACK发送模式,接下来无论是否处于快速确认模式,
  58. // 都可以马上发送ACK。
  59. if (icsk->icsk_ack.pending & ICSK_ACK_PUSHED)
  60. icsk->icsk_ack.pending |= ICSK_ACK_PUSHED2;
  61. icsk->icsk_ack.pending |= ICSK_ACK_PUSHED;
  62. }
  63. }

此函数的原理:

我们知道发送端不可能在一个RTT期间发送大于一个通告窗口的数据量。那么接收端可以把接收一个确认窗口的数据量(rcv_wnd)所用的时间作为RTT。接收端收到一个数据段,然后发送确认(确认号为rcv_nxt,通告窗口为rcv_wnd),开始计时,RTT就是收到序号为rcv_nxt + rcv_wnd的数据段所用的时间。很显然,这种假设并不准确,测量所得的RTT会偏大一些。所以这种方法只有当没有采用时间戳选项时才使用,而内核默认是采用时间戳选项的(tcp_timestamps为1)。

  1. /*
  2. 此函数的原理:我们知道发送端不可能在一个RTT期间发送大于一个通告窗口的数据量。
  3. 那么接收端可以把接收一个确认窗口的数据量(rcv_wnd)所用的时间作为RTT。接收端收到一个数据段,
  4. 然后发送确认(确认号为rcv_nxt,通告窗口为rcv_wnd),开始计时,RTT就是收到序号为rcv_nxt + rcv_wnd的数据段所用的时间。
  5. 很显然,这种假设并不准确,测量所得的RTT会偏大一些。所以这种方法只有当没有采用时间戳选项时才使用,
  6. 而内核默认是采用时间戳选项的(tcp_timestamps为1)。
  7. 下面是一段对此方法的评价:
  8. If the sender is being throttled by the network, this estimate will be valid. However, if the sending application did nothave any data to send,
  9. the measured time could be much larger than the actual round-trip time. Thus this measurementacts only as an upper-bound on the round-trip time.
  10. ————————————————
  11.  
  12. */
  13. static inline void tcp_rcv_rtt_measure(struct tcp_sock *tp)
  14. {
  15. u32 delta_us;
  16. /* 第一次接收到数据时,需要对相关变量初始化*/
  17.  
  18. if (tp->rcv_rtt_est.time.v64 == 0)
  19. goto new_measure;
  20. /* 收到指定的序列号后,才能获取一个RTT测量样本*/
  21. if (before(tp->rcv_nxt, tp->rcv_rtt_est.seq))
  22. return;
  23. /* RTT的样本:jiffies - tp->rcv_rtt_est.time */
  24. delta_us = skb_mstamp_us_delta(&tp->tcp_mstamp, &tp->rcv_rtt_est.time);
  25. tcp_rcv_rtt_update(tp, delta_us, 1);
  26.  
  27. new_measure:
  28. tp->rcv_rtt_est.seq = tp->rcv_nxt + tp->rcv_wnd;
  29. tp->rcv_rtt_est.time = tp->tcp_mstamp;
  30. }
  1. static void __tcp_ecn_check_ce(struct tcp_sock *tp, const struct sk_buff *skb)
  2. {
  3. switch (TCP_SKB_CB(skb)->ip_dsfield & INET_ECN_MASK) {
  4. case INET_ECN_NOT_ECT:/* IP层不支持ECN */
  5. /* Funny extension: if ECT is not set on a segment,
  6. * and we already seen ECT on a previous segment,
  7. * it is probably a retransmit.
  8. */
  9. if (tp->ecn_flags & TCP_ECN_SEEN)
  10. tcp_enter_quickack_mode((struct sock *)tp);
  11. break;
  12. case INET_ECN_CE:/* 数据包携带拥塞标志 */
  13. if (tcp_ca_needs_ecn((struct sock *)tp))
  14. tcp_ca_event((struct sock *)tp, CA_EVENT_ECN_IS_CE);
  15.  
  16. if (!(tp->ecn_flags & TCP_ECN_DEMAND_CWR)) {
  17. /* Better not delay acks, sender can have a very low cwnd */
  18. tcp_enter_quickack_mode((struct sock *)tp);//进入快速确认模式
  19. tp->ecn_flags |= TCP_ECN_DEMAND_CWR;/* 用于让对端感知拥塞的标志 */
  20. }
  21. tp->ecn_flags |= TCP_ECN_SEEN;
  22. break;
  23. default:
  24. if (tcp_ca_needs_ecn((struct sock *)tp))
  25. tcp_ca_event((struct sock *)tp, CA_EVENT_ECN_NO_CE);
  26. tp->ecn_flags |= TCP_ECN_SEEN;
  27. break;
  28. }
  29. }
  30.  
  31. static void tcp_ecn_check_ce(struct tcp_sock *tp, const struct sk_buff *skb)
  32. {
  33. if (tp->ecn_flags & TCP_ECN_OK) /* 如果连接不支持ECN 则不处理*/
  34. __tcp_ecn_check_ce(tp, skb);
  35. }
  • #define TCP_MSS_DEFAULT 536U
  • #define TCP_MIN_MSS 88U /* Minimal accepted MSS. It is (60+60+8) - (20+20). */ 
     
     

关于QUICK计数器

Linux为TCP的非Delay ACK维护了一个计数器QUICK,该计数器表示在非Delay ACK模式下,发送的即时ACK的数量,也就是即时ACK的配额,在pingpong为0的前提下,只有QUICK持有配额(不为0),该即时ACK才可以被发送出去,否则该ACK会被Delay!

QUICK计数器值在什么情况下会增减

  1. 在连接初始化后,当第一次收到数据的时候

    此时会将QUICK配额增加到最多16个段。配备16个段的配额是为了照顾慢启动,保证发送端在慢启动阶段时,接收端有足够的QUIICK配额发送即时的ACK。
  2. 当自从上一次接收到数据到现在又一次收到数据的时间间隔太久的时候

    此时会将QUICK配额增加到最多16个段。此时配备足量的QUICK配额是为了发送即时的ACK以促使发送端尽快发送数据。
  3. 当接收端窗口缩减的时候

    此时会将QUIICK配额清零。这种情况下,内存可以已经吃紧,尽量延缓接收数据是有益的,所以要减缓TCP时钟,延迟ACK的发送。
  4. 当接收端窗口小于接收端缓存一半的时候

    此时会将QUIICK配额清零。

tcpack--3快速确认模式的更多相关文章

  1. tcpack--3快速确认模式- ack状态发送&清除

    ACK发送状态的转换图 ACK的发送状态清除 当成功发送ACK时,会删除延迟确认定时器,同时清零ACK的发送状态标志icsk->icsk_ack.pending ACK发送事件主要做了:更新快速 ...

  2. TCP的ACK确认系列 — 快速确认

    主要内容:TCP的快速确认.TCP_QUICKACK选项的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 快速确认模式 (1) 进入快速确认模式 ...

  3. 消息中间件系列三:使用RabbitMq原生Java客户端进行消息通信(消费者(接收方)自动确认模式、消费者(接收方)自行确认模式、生产者(发送方)确认模式)

    准备工作: 1)安装RabbitMQ,参考文章:消息中间件系列二:RabbitMQ入门(基本概念.RabbitMQ的安装和运行) 2.)分别新建名为OriginalRabbitMQProducer和O ...

  4. C#下控制台程序窗口下启用快速编辑模式运行线程会阻止线程运行

    最近做一个小的功能,使用C#控制台程序开启一个线程进行无限循环没5秒处理一次程序,发现控制台窗口在开启快速编辑模式情况下,进行选择程序打印 出来的文字后发现线程不走了,将快速编辑模式去除后,线程就不会 ...

  5. 在C#中,Windows Console控制台 设置控制台标题、禁用关闭按钮、关闭快速编辑模式、插入模式

    设置控制台标题 禁用关闭按钮 关闭快速编辑模式 关闭插入模式 设置控制台标题.禁用关闭按钮 #region 设置控制台标题 禁用关闭按钮 [DllImport("user32.dll&quo ...

  6. AVR 定时器快速PWM模式使用

    PWM很常用,AVR自带内部PWM功能,分为快速PWM模式和相位修正PWM模式.   我们这里选择方式15 ,由OCR1A保存上限值,由OCR1B保存匹配值,所以输出管脚 OCR1A不能输PWM,只能 ...

  7. rabbitmq 持久化 事务 发送确认模式

    部分内容来自:http://blog.csdn.net/hzw19920329/article/details/54315940 http://blog.csdn.net/hzw19920329/ar ...

  8. Rabbitmq之高级特性——百分百投递消息&消息确认模式&消息返回模式实现

    rabbitmq的高级特性: 如何保障消息的百分之百成功? 要满足4个条件:生产方发送出去,消费方接受到消息,发送方接收到消费者的确认信息,完善的消费补偿机制 解决方案,1)消息落库,进行消息状态打标 ...

  9. asp.net core系列 76 Apollo 快速安装模式下填坑和ASP.NetCore结合使用

    前言:由于公司占时没有运维,出于微服务的需要,Apollo只能先装在windows 阿里云上跑起来,由于环境及网络等问题,在安装过程中遇到很多坑,算是一个个坑填完后,最终实现. 一. java jdk ...

随机推荐

  1. MeteoInfoLab脚本示例:格点数据散点图

    绘制格点数据的散点图,用scaterm函数. 脚本程序: f = addfile('D:/Temp/GrADS/model.ctl') ps = f['PS'][0,(10,60),(60,140)] ...

  2. day09 Pyhton学习

    一.昨日内容回顾 文件操作 open(文件路径,mode="模式",encoding="编码") 文件路径: 1.绝对路径 从磁盘根目录寻找 2.相对路径 相对 ...

  3. 资源管理神器Clover

    开开心心地上班,这时你得打开我的电脑,点进D盘,打开某个项目;然后还得打开XX文档,还有.... 最后的最后,你的桌面便成了这个样子 每天你都得天打开多个文件夹,切换时找文件找的晕头转向而烦恼. 每天 ...

  4. 为什么大部分的程序员学编程,都会选择从C语言开始?

    软件行业经过几十年的发展,编程语言的种类已经越来越多了,而且很多新的编程语言已经在这个领域从开始的默默无闻到如今风风火火,整个编程语言朝着集成化方向发展,这样会导致很多的初学者选择上不像以前那么单一了 ...

  5. 【UR #2】猪猪侠再战括号序列

    UOJ小清新题表 题目摘要 UOJ链接 有一个由 \(n\) 个左括号 "(" 和 \(n\) 个右括号 ")" 组成的序列.每次操作时可以选定两个数 \(l, ...

  6. Linux下批量kill掉进程

    ps -ef|grep java|grep -v grep|cut -c 9-15|xargs kill -9 管道符"|"用来隔开两个命令,管道符左边命令的输出会作为管道符右边命 ...

  7. 第十三章 Linux三剑客之老二—sed

    一.sed #擅长增删改查 替换 选项: -n #取消默认输出 -r #支持扩展正则使用 -i #改变文件内容 -e #允许多项编辑 内部指令: p #print 打印 d    # 删除 排除 a ...

  8. 手撸了一个HTTP框架:支持Sprng MVC、IOC、AOP,拦截器,配置文件读取...

    https://github.com/Snailclimb/jsoncat :仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架 距离上一次给小伙伴们汇报简易 ...

  9. 蓝桥杯2020 E:七段码

    题解 正规解法是 dfs + 并查集,首先用 dfs 将其所有的情况枚举出来,再用并查集来判断是否在一个连通块上. 许多小伙伴计算的答案为76,主要是判断连通块这方面有问题,倘若不用并查集,直接枚举一 ...

  10. docker容器学习资料

    现在说起docker容器,你应该不会太陌生了吧?如果你是真的不懂或者是太久没有用到已经忘记的差不多了,那么你需要这一波的干货了,这波的干货刚刚好可以满足你的需要! 话不多说,直接上干货