TCP 杂记

1. TCP_NODELAY 和 TCP_QUICKACK

1.1. Nagle's Algorithm

现代的 Linux kernel 的网络协议栈中默认启用的算法,也是粘包产生的原因之一。
根据算法文档,原版的 Nagle 算法可以简要表达为:

If a TCP has less than a full-sized packet to transmit,
and if any previous packet has not yet been acknowledged,
do not transmit a packet

伪码形式是:

if ((packet.size < Eff.snd.MSS) && (snd.nxt > snd.una)) {
    do not send the packet;
}

伪码中的 MSS 顾名思义,snd.nxt 指下一个需要发送的 TCP 报文的序列号;snd.una 指下一个需要ACK 的 TCP 报文序列号。如果 snd.nxt 等于 snd.una,则说明所有发出的报文都已经被 ACK 了。

从这个关系中可得知:

  • 握手完成后的第一个包,不论大小都会被立刻发送,因为并没有需要等待 ACK 的包;
  • 此后,所有的应用层数据,如果由于较大而出现拆包、且其中最后一个包小于 MSS 的话,则这最后一个包需要等待前面已经发出的所有包都被 ACK 以后,才会被发出。

此外还有:

  • 当尚存在未被 ACK 的前序包时,如果应用层连续发出许多小数据,则直到它们能够填满一个 MSS 之前,都会被放在缓冲区里缓存,并不会发出。

1.2 Nagle 算法的设计背景与初衷

TLDR:Nagle 算法的本意是尽量减少小包数量,从而减少带宽浪费、避免网络拥塞。

详细来讲,举个🌰:在通过 telnet 传输键盘操作的信令时,每次敲击键盘往往只会产生只有 1 个字节的应用层数据。如果没有 Nagle 算法,则当我们操作 telnet 时,会在短时间内大量生成 payload 只有 1 个字节的小包,并全都发送出去。

这会带来两个问题:

  1. 对于这些传输信令的包,光是 TCP header(20 字节) + 网络层 header(20/40 字节)就是其本身 payload 的 40 倍。占用带宽的绝大部分都用来传输 header 了,造成巨大的浪费。

  2. 对于早期互联网的低速网络,短时间内大量生成小包并全都发送出去的行为容易造成网络拥塞。

1.3 与延迟确认(Delayed ACK)的相互作用

延迟确认 (Delayed ACK) 技术是另一种 TCP 优化方案,其发明时间与在 Nagle 算法几乎相同时间。它设计的思路很简单:利用 ACK 的累计确认特性,适当地延后 ACK 报文的发送,从而能用一个累计的 ACK 替代多个单独的 ACK,减少浪费。

但是,二者同时被引入 TCP 协议后,就造成一个恶性的问题。如果下面三个条件同时满足:

  1. 发送方开启 Nagle 算法
  2. 接受方开启 Delayed ACK
  3. 发送的数据出现了拆包、且最后一个包小于 MSS

则会出现一个相当糟糕的情况:发送方在等待接收方发来上个包的 ACK ,在收到之前并不会发送最后的小包;但接收方又延迟了 ACK 的发送,这样一来,两边就同时陷入了停滞状态,直到 TCP Delayed ACK 的超时被触发(默认 40ms)。

1.4 解决方案

现代的 Linux 引入了两个 TCP options 来解决这些问题:

  • TCP_NODELAY 禁用 Nagle 算法
  • TCP_QUICKACK 禁用 Delayed ACK

当构建一个高性能 PROXY 的时候,我们应该同时启用这两个 Option ,从而排除这些算法可能引入的干扰,达到改善网络性能的目的。

1.5 ref

https://cloud.tencent.com/developer/article/1648761
https://medium.com/fcamels-notes/nagles-algorithm-%E5%92%8C-delayed-ack-%E4%BB%A5%E5%8F%8A-minshall-%E7%9A%84%E5%8A%A0%E5%BC%B7%E7%89%88-8fadcb84d96f
https://en.wikipedia.org/wiki/Nagle%27s_algorithm
https://en.wikipedia.org/wiki/TCP_delayed_acknowledgment

2. TCP keepalive 太长导致被容器网络 NAT 干掉

2.1 TCP keepalive 的三个参数

TCP KeepAlive机制主要涉及3个参数:

  • tcp_keepalive_time (integer; default: 7200; since Linux 2.2)
    The number of seconds a connection needs to be idle before TCP begins sending out keep-alive probes. Keep-alives are sent only when the SO_KEEPALIVE socket option is enabled. The default value is 7200 seconds (2 hours). An idle connection is terminated after approximately an additional 11 minutes (9 probes an interval of 75 seconds apart) when keep-alive is enabled.
    在 TCP 保活启用的情况下,从最后一次数据交换到 TCP 发送第一个保活探测包的间隔,即允许的持续空闲时长,或者说每次正常发送心跳的周期,默认值为7200s(2h)。

  • tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)
    The number of seconds between TCP keep-alive probes.
    tcp_keepalive_time 之后,没有接收到对方确认,继续发送保活探测包的发送频率,默认值为 75s。

  • tcp_keepalive_probes (integer; default: 9; since Linux 2.2)
    The maximum number of TCP keep-alive probes to send before giving up and killing the connection if no response is obtained from the other end.
    tcp_keepalive_time 之后,允许发送保活探测包的最大次数,到达此次数后直接放弃尝试并关闭连接,默认值为 9 次。

2.2 在容器网络 NAT 环境中的问题

默认的 2 小时太长了。可能还没来得及发出第一个保活包,容器网络 NAT 就认为这条 TCP 连接上长期没有数据交互,直接给干掉了。此后,当容器内应用再次尝试使用这个 Socket 时,会发现已经被异常关闭(EOF)。

2.3 ref

https://www.cnblogs.com/hueyxu/p/15759819.html

知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇