大家好,æˆ‘æ˜¯å°æž—,一个专为大家图解的工具人。

ä¸ç®¡é¢è¯• Java ã€C/C++ã€Python 等开å‘å²—ä½, TCP 的知识点å¯ä»¥è¯´æ˜¯å¿…问的了。

ä»» TCP è™æˆ‘åƒç™¾é,我ä»å¾… TCP å¦‚åˆæ‹ã€‚

过去ä¸ä¼šæ²¡å…³ç³»,ä»Šå¤©å°±è®©æˆ‘ä»¬æ¥æ¶ˆé™¤è¿™ä»½ææƒ§,微笑ç€å‹‡æ•¢çš„é¢å¯¹å®ƒå§!

æ‰€ä»¥å°æž—æ•´ç†äº†å…³äºŽ TCP çš„é¢è¯•题型,全文共 3 万字 + 100 张图,跟大家一起探讨探讨。

1〠TCP 基本认识

2ã€TCP 连接建立

3〠TCP 连接断开

4〠Socket 编程


5ã€TCP é‡ä¼ ã€æ»‘动窗å£ã€æµé‡æŽ§åˆ¶ã€æ‹¥å¡žæŽ§åˆ¶


01 TCP 基本认识

çž§çž§ TCP 头格å¼

我们先æ¥çœ‹çœ‹ TCP 头的格å¼,æ ‡æ³¨é¢œè‰²çš„è¡¨ç¤ºä¸Žæœ¬æ–‡å…³è”æ¯”较大的字段,其他字段ä¸åšè¯¦ç»†é˜è¿°ã€‚

åºåˆ—å·:在建立连接时由计算机生æˆçš„éšæœºæ•°ä½œä¸ºå…¶åˆå§‹å€¼,通过 SYN 包传给接收端主机,æ¯å‘é€ä¸€æ¬¡æ•°æ®,就「累加ã€ä¸€æ¬¡è¯¥ã€Œæ•°æ®å­—节数ã€çš„大å°ã€‚用æ¥è§£å†³ç½‘络包乱åºé—®é¢˜ã€‚

确认应答å·:æŒ‡ä¸‹ä¸€æ¬¡ã€ŒæœŸæœ›ã€æ”¶åˆ°çš„æ•°æ®çš„åºåˆ—å·,å‘é€ç«¯æ”¶åˆ°è¿™ä¸ªç¡®è®¤åº”答以åŽå¯ä»¥è®¤ä¸ºåœ¨è¿™ä¸ªåºå·ä»¥å‰çš„æ•°æ®éƒ½å·²ç»è¢«æ­£å¸¸æŽ¥æ”¶ã€‚用æ¥è§£å†³ä¸ä¸¢åŒ…的问题。

控制ä½:

  • ACK:该ä½ä¸º 1 æ—¶,「确认应答ã€çš„字段å˜ä¸ºæœ‰æ•ˆ,TCP 规定除了最åˆå»ºç«‹è¿žæŽ¥æ—¶çš„ SYN 包之外该ä½å¿…须设置为 1 。
  • RST:该ä½ä¸º 1 æ—¶,表示 TCP 连接中出现异常必须强制断开连接。
  • SYN:该ä½ä¸º 1 æ—¶,表示希望建立连接,并在其「åºåˆ—å·ã€çš„字段进行åºåˆ—å·åˆå§‹å€¼çš„设定。
  • FIN:该ä½ä¸º 1 æ—¶,表示今åŽä¸ä¼šå†æœ‰æ•°æ®å‘é€,希望断开连接。当通信结æŸå¸Œæœ›æ–­å¼€è¿žæŽ¥æ—¶,é€šä¿¡åŒæ–¹çš„主机之间就å¯ä»¥ç›¸äº’äº¤æ¢ FIN ä½ä¸º 1 çš„ TCP 段。

ä¸ºä»€ä¹ˆéœ€è¦ TCP åè®®? TCP 工作在哪一层?

IP 层是「ä¸å¯é ã€çš„,它ä¸ä¿è¯ç½‘络包的交付ã€ä¸ä¿è¯ç½‘络包的按åºäº¤ä»˜ã€ä¹Ÿä¸ä¿è¯ç½‘络包中的数æ®çš„完整性。

如果需è¦ä¿éšœç½‘络数æ®åŒ…çš„å¯é æ€§,那么就需è¦ç”±ä¸Šå±‚(传输层)çš„ TCP åè®®æ¥è´Ÿè´£ã€‚

因为 TCP 是一个工作在传输层的å¯é æ•°æ®ä¼ è¾“çš„æœåŠ¡,å®ƒèƒ½ç¡®ä¿æŽ¥æ”¶ç«¯æŽ¥æ”¶çš„ç½‘ç»œåŒ…æ˜¯æ— æŸåã€æ— é—´éš”ã€éžå†—余和按åºçš„。

什么是 TCP ?

TCP 是é¢å‘连接的ã€å¯é çš„ã€åŸºäºŽå­—节æµçš„传输层通信å议。

  • é¢å‘连接:ä¸€å®šæ˜¯ã€Œä¸€å¯¹ä¸€ã€æ‰èƒ½è¿žæŽ¥,ä¸èƒ½åƒ UDP åè®®å¯ä»¥ä¸€ä¸ªä¸»æœºåŒæ—¶å‘多个主机å‘逿¶ˆæ¯,也就是一对多是无法åšåˆ°çš„;

  • å¯é çš„:无论的网络链路中出现了怎样的链路å˜åŒ–,TCP 都å¯ä»¥ä¿è¯ä¸€ä¸ªæŠ¥æ–‡ä¸€å®šèƒ½å¤Ÿåˆ°è¾¾æŽ¥æ”¶ç«¯;

  • 字节æµ:æ¶ˆæ¯æ˜¯ã€Œæ²¡æœ‰è¾¹ç•Œã€çš„,æ‰€ä»¥æ— è®ºæˆ‘ä»¬æ¶ˆæ¯æœ‰å¤šå¤§éƒ½å¯ä»¥è¿›è¡Œä¼ è¾“ã€‚å¹¶ä¸”æ¶ˆæ¯æ˜¯ã€Œæœ‰åºçš„ã€,当「å‰ä¸€ä¸ªã€æ¶ˆæ¯æ²¡æœ‰æ”¶åˆ°çš„æ—¶å€™,å³ä½¿å®ƒå…ˆæ”¶åˆ°äº†åŽé¢çš„字节,那么也ä¸èƒ½æ‰”给应用层去处ç†,åŒæ—¶å¯¹ã€Œé‡å¤ã€çš„æŠ¥æ–‡ä¼šè‡ªåŠ¨ä¸¢å¼ƒã€‚

什么是 TCP 连接?

我们æ¥çœ‹çœ‹ RFC 793 是如何定义「连接ã€çš„:

Connections:
The reliability and flow control mechanisms described above require
that TCPs initialize and maintain certain status information for
each data stream. The combination of this information, including
sockets, sequence numbers, and window sizes, is called a connection.

ç®€å•æ¥è¯´å°±æ˜¯,用于ä¿è¯å¯é æ€§å’Œæµé‡æŽ§åˆ¶ç»´æŠ¤çš„æŸäº›çŠ¶æ€ä¿¡æ¯,这些信æ¯çš„组åˆ,包括Socketã€åºåˆ—å·å’Œçª—å£å¤§å°ç§°ä¸ºè¿žæŽ¥ã€‚

所以我们å¯ä»¥çŸ¥é“,建立一个 TCP 连接是需è¦å®¢æˆ·ç«¯ä¸ŽæœåŠ¡å™¨ç«¯è¾¾æˆä¸Šè¿°ä¸‰ä¸ªä¿¡æ¯çš„共识。

  • Socket:ç”± IP 地å€å’Œç«¯å£å·ç»„æˆ
  • åºåˆ—å·:用æ¥è§£å†³ä¹±åºé—®é¢˜ç­‰
  • 窗å£å¤§å°:用æ¥åšæµé‡æŽ§åˆ¶

如何唯一确定一个 TCP 连接呢?

TCP 四元组å¯ä»¥å”¯ä¸€çš„确定一个连接,四元组包括如下:

  • æºåœ°å€
  • æºç«¯å£
  • 目的地å€
  • 目的端å£

æºåœ°å€å’Œç›®çš„地å€çš„字段(32ä½)是在 IP 头部中,作用是通过 IP åè®®å‘é€æŠ¥æ–‡ç»™å¯¹æ–¹ä¸»æœºã€‚

æºç«¯å£å’Œç›®çš„端å£çš„字段(16ä½)是在 TCP 头部中,作用是告诉 TCP å议应该把报文å‘给哪个进程。

有一个 IP çš„æœåŠ¡å™¨ç›‘å¬äº†ä¸€ä¸ªç«¯å£,它的 TCP 的最大连接数是多少?

æœåŠ¡å™¨é€šå¸¸å›ºå®šåœ¨æŸä¸ªæœ¬åœ°ç«¯å£ä¸Šç›‘å¬,等待客户端的连接请求。

å› æ­¤,客户端 IP å’Œ ç«¯å£æ˜¯å¯å˜çš„,å…¶ç†è®ºå€¼è®¡ç®—å…¬å¼å¦‚下:

对 IPv4,客户端的 IP 数最多为 2 çš„ 32 次方,å®¢æˆ·ç«¯çš„ç«¯å£æ•°æœ€å¤šä¸º 2 çš„ 16 次方,也就是æœåŠ¡ç«¯å•æœºæœ€å¤§ TCP 连接数,约为 2 çš„ 48 次方。

当然,æœåŠ¡ç«¯æœ€å¤§å¹¶å‘ TCP 连接数远ä¸èƒ½è¾¾åˆ°ç†è®ºä¸Šé™ã€‚

  • é¦–å…ˆä¸»è¦æ˜¯æ–‡ä»¶æè¿°ç¬¦é™åˆ¶,Socket 都是文件,所以首先è¦é€šè¿‡ ulimit é…置文件æè¿°ç¬¦çš„æ•°ç›®;
  • å¦ä¸€ä¸ªæ˜¯å†…å­˜é™åˆ¶,æ¯ä¸ª TCP 连接都è¦å ç”¨ä¸€å®šå†…å­˜,æ“作系统的内存是有é™çš„。

UDP å’Œ TCP 有什么区别呢?分别的应用场景是?

UDP 䏿供夿‚的控制机制,利用 IP æä¾›é¢å‘「无连接ã€çš„通信æœåŠ¡ã€‚

UDP å议真的éžå¸¸ç®€,å¤´éƒ¨åªæœ‰ 8 个字节( 64 ä½),UDP 的头部格å¼å¦‚下:

  • 目标和æºç«¯å£:ä¸»è¦æ˜¯å‘Šè¯‰ UDP å议应该把报文å‘给哪个进程。
  • 包长度:该字段ä¿å­˜äº† UDP 首部的长度跟数æ®çš„长度之和。
  • 校验和:校验和是为了æä¾›å¯é çš„ UDP 首部和数æ®è€Œè®¾è®¡ã€‚

TCP å’Œ UDP 区别:

1. 连接

  • TCP 是é¢å‘连接的传输层åè®®,传输数æ®å‰å…ˆè¦å»ºç«‹è¿žæŽ¥ã€‚
  • UDP 是ä¸éœ€è¦è¿žæŽ¥,å³åˆ»ä¼ è¾“æ•°æ®ã€‚

2. æœåŠ¡å¯¹è±¡

  • TCP 是一对一的两点æœåŠ¡,å³ä¸€æ¡è¿žæŽ¥åªæœ‰ä¸¤ä¸ªç«¯ç‚¹ã€‚
  • UDP 支æŒä¸€å¯¹ä¸€ã€ä¸€å¯¹å¤šã€å¤šå¯¹å¤šçš„交互通信

3. å¯é æ€§

  • TCP 是å¯é äº¤ä»˜æ•°æ®çš„,æ•°æ®å¯ä»¥æ— å·®é”™ã€ä¸ä¸¢å¤±ã€ä¸é‡å¤ã€æŒ‰éœ€åˆ°è¾¾ã€‚
  • UDP 是尽最大努力交付,ä¸ä¿è¯å¯é äº¤ä»˜æ•°æ®ã€‚

4. æ‹¥å¡žæŽ§åˆ¶ã€æµé‡æŽ§åˆ¶

  • TCP 有拥塞控制和æµé‡æŽ§åˆ¶æœºåˆ¶,ä¿è¯æ•°æ®ä¼ è¾“的安全性。
  • UDP 则没有,å³ä½¿ç½‘络éžå¸¸æ‹¥å µäº†,也ä¸ä¼šå½±å“ UDP çš„å‘é€é€ŸçŽ‡ã€‚

5. 首部开销

  • TCP 首部长度较长,会有一定的开销,首部在没有使用「选项ã€å­—段时是 20 个字节,如果使用了「选项ã€å­—段则会å˜é•¿çš„。
  • UDP é¦–éƒ¨åªæœ‰ 8 个字节,并且是固定ä¸å˜çš„,开销较å°ã€‚

6. 传输方å¼

  • TCP 是æµå¼ä¼ è¾“,没有边界,但ä¿è¯é¡ºåºå’Œå¯é ã€‚
  • UDP 是一个包一个包的å‘é€,是有边界的,但å¯èƒ½ä¼šä¸¢åŒ…和乱åºã€‚

7. 分片ä¸åŒ

  • TCP 的数æ®å¤§å°å¦‚果大于 MSS 大å°,则会在传输层进行分片,目标主机收到åŽ,ä¹ŸåŒæ ·åœ¨ä¼ è¾“层组装 TCP æ•°æ®åŒ…,如果中途丢失了一个分片,åªéœ€è¦ä¼ è¾“丢失的这个分片。
  • UDP 的数æ®å¤§å°å¦‚果大于 MTU 大å°,则会在 IP 层进行分片,目标主机收到åŽ,在 IP 层组装完数æ®,接ç€å†ä¼ ç»™ä¼ è¾“层,但是如果中途丢了一个分片,则就需è¦é‡ä¼ æ‰€æœ‰çš„æ•°æ®åŒ…,这样传输效率éžå¸¸å·®,所以通常 UDP 的报文应该å°äºŽ MTU。

TCP å’Œ UDP 应用场景:

由于 TCP 是é¢å‘连接,能ä¿è¯æ•°æ®çš„å¯é æ€§äº¤ä»˜,å› æ­¤ç»å¸¸ç”¨äºŽ:

  • FTP 文件传输
  • HTTP / HTTPS

由于 UDP é¢å‘无连接,它å¯ä»¥éšæ—¶å‘逿•°æ®,å†åŠ ä¸ŠUDPæœ¬èº«çš„å¤„ç†æ—¢ç®€å•åˆé«˜æ•ˆ,å› æ­¤ç»å¸¸ç”¨äºŽ:

  • 包总é‡è¾ƒå°‘的通信,如 DNS ã€SNMP ç­‰
  • 视频ã€éŸ³é¢‘等多媒体通信
  • 广播通信

为什么 UDP 头部没有「首部长度ã€å­—段,而 TCP 头部有「首部长度ã€å­—段呢?

原因是 TCP 有å¯å˜é•¿çš„「选项ã€å­—段,而 UDP 头部长度则是ä¸ä¼šå˜åŒ–çš„,无需多一个字段去记录 UDP 的首部长度。

为什么 UDP 头部有「包长度ã€å­—段,而 TCP 头部则没有「包长度ã€å­—段呢?

先说说 TCP 是如何计算负载数æ®é•¿åº¦:

其中 IP 总长度 å’Œ IP 首部长度,在 IP é¦–éƒ¨æ ¼å¼æ˜¯å·²çŸ¥çš„。TCP 首部长度,则是在 TCP 首部格å¼å·²çŸ¥çš„,所以就å¯ä»¥æ±‚å¾— TCP æ•°æ®çš„长度。

大家这时就奇怪了问:“ UDP 也是基于 IP 层的呀,é‚£ UDP 的数æ®é•¿åº¦ä¹Ÿå¯ä»¥é€šè¿‡è¿™ä¸ªå…¬å¼è®¡ç®—å‘€? ä¸ºä½•è¿˜è¦æœ‰ã€ŒåŒ…长度ã€å‘¢?â€

这么一问,确实感觉 UDP ã€ŒåŒ…é•¿åº¦ã€æ˜¯å†—余的。

å› ä¸ºä¸ºäº†ç½‘ç»œè®¾å¤‡ç¡¬ä»¶è®¾è®¡å’Œå¤„ç†æ–¹ä¾¿,é¦–éƒ¨é•¿åº¦éœ€è¦æ˜¯ 4字节的整数å€ã€‚

如果去掉 UDP 「包长度ã€å­—段,é‚£ UDP é¦–éƒ¨é•¿åº¦å°±ä¸æ˜¯ 4 字节的整数å€äº†,æ‰€ä»¥å°æž—觉得这å¯èƒ½æ˜¯ä¸ºäº†è¡¥å…¨ UDP 首部长度是 4 字节的整数å€,æ‰è¡¥å……了「包长度ã€å­—段。


02 TCP 连接建立

TCP ä¸‰æ¬¡æ¡æ‰‹è¿‡ç¨‹å’Œçжæ€å˜è¿

TCP 是é¢å‘连接的åè®®,所以使用 TCP å‰å¿…须先建立连接,è€Œå»ºç«‹è¿žæŽ¥æ˜¯é€šè¿‡ä¸‰æ¬¡æ¡æ‰‹æ¥è¿›è¡Œçš„。

  • 一开始,客户端和æœåŠ¡ç«¯éƒ½å¤„äºŽ CLOSED 状æ€ã€‚先是æœåŠ¡ç«¯ä¸»åŠ¨ç›‘å¬æŸä¸ªç«¯å£,处于 LISTEN 状æ€

  • å®¢æˆ·ç«¯ä¼šéšæœºåˆå§‹åŒ–åºå·(client_isn),将此åºå·ç½®äºŽ TCP 首部的「åºå·ã€å­—段中,åŒæ—¶æŠŠ SYN 标志ä½ç½®ä¸º 1 ,表示 SYN æŠ¥æ–‡ã€‚æŽ¥ç€æŠŠç¬¬ä¸€ä¸ª SYN 报文å‘é€ç»™æœåŠ¡ç«¯,è¡¨ç¤ºå‘æœåŠ¡ç«¯å‘起连接,该报文ä¸åŒ…å«åº”用层数æ®,之åŽå®¢æˆ·ç«¯å¤„于 SYN-SENT 状æ€ã€‚

  • æœåŠ¡ç«¯æ”¶åˆ°å®¢æˆ·ç«¯çš„ SYN 报文åŽ,首先æœåŠ¡ç«¯ä¹Ÿéšæœºåˆå§‹åŒ–自己的åºå·(server_isn),将此åºå·å¡«å…¥ TCP 首部的「åºå·ã€å­—段中,其次把 TCP 首部的「确认应答å·ã€å­—段填入 client_isn + 1, æŽ¥ç€æŠŠ SYN å’Œ ACK 标志ä½ç½®ä¸º 1ã€‚æœ€åŽæŠŠè¯¥æŠ¥æ–‡å‘给客户端,该报文也ä¸åŒ…å«åº”用层数æ®,ä¹‹åŽæœåŠ¡ç«¯å¤„äºŽ SYN-RCVD 状æ€ã€‚

  • 客户端收到æœåŠ¡ç«¯æŠ¥æ–‡åŽ,还è¦å‘æœåŠ¡ç«¯å›žåº”æœ€åŽä¸€ä¸ªåº”答报文,首先该应答报文 TCP 首部 ACK 标志ä½ç½®ä¸º 1 ,其次「确认应答å·ã€å­—段填入 server_isn + 1 ,æœ€åŽæŠŠæŠ¥æ–‡å‘é€ç»™æœåŠ¡ç«¯,这次报文å¯ä»¥æºå¸¦å®¢æˆ·åˆ°æœåŠ¡å™¨çš„æ•°æ®,之åŽå®¢æˆ·ç«¯å¤„于 ESTABLISHED 状æ€ã€‚

  • æœåŠ¡å™¨æ”¶åˆ°å®¢æˆ·ç«¯çš„åº”ç­”æŠ¥æ–‡åŽ,也进入 ESTABLISHED 状æ€ã€‚

从上é¢çš„过程å¯ä»¥å‘çŽ°ç¬¬ä¸‰æ¬¡æ¡æ‰‹æ˜¯å¯ä»¥æºå¸¦æ•°æ®çš„,å‰ä¸¤æ¬¡æ¡æ‰‹æ˜¯ä¸å¯ä»¥æºå¸¦æ•°æ®çš„,这也是é¢è¯•常问的题。

一旦完æˆä¸‰æ¬¡æ¡æ‰‹,åŒæ–¹éƒ½å¤„于 ESTABLISHED 状æ€,此时连接就已建立完æˆ,客户端和æœåŠ¡ç«¯å°±å¯ä»¥ç›¸äº’å‘逿•°æ®äº†ã€‚

如何在 Linux 系统中查看 TCP 状æ€?

TCP çš„è¿žæŽ¥çŠ¶æ€æŸ¥çœ‹,在 Linux å¯ä»¥é€šè¿‡ netstat -napt 命令查看。

ä¸ºä»€ä¹ˆæ˜¯ä¸‰æ¬¡æ¡æ‰‹?䏿˜¯ä¸¤æ¬¡ã€å››æ¬¡?

相信大家比较常回答的是:â€œå› ä¸ºä¸‰æ¬¡æ¡æ‰‹æ‰èƒ½ä¿è¯åŒæ–¹å…·æœ‰æŽ¥æ”¶å’Œå‘é€çš„能力。â€

这回答是没问题,但这回答是片é¢çš„,并没有说出主è¦çš„原因。

在å‰é¢æˆ‘们知é“了什么是 TCP 连接

  • 用于ä¿è¯å¯é æ€§å’Œæµé‡æŽ§åˆ¶ç»´æŠ¤çš„æŸäº›çŠ¶æ€ä¿¡æ¯,这些信æ¯çš„组åˆ,包括Socketã€åºåˆ—å·å’Œçª—å£å¤§å°ç§°ä¸ºè¿žæŽ¥ã€‚

所以,é‡è¦çš„æ˜¯ä¸ºä»€ä¹ˆä¸‰æ¬¡æ¡æ‰‹æ‰å¯ä»¥åˆå§‹åŒ–Socketã€åºåˆ—å·å’Œçª—å£å¤§å°å¹¶å»ºç«‹ TCP 连接。

接下æ¥ä»¥ä¸‰ä¸ªæ–¹é¢åˆ†æžä¸‰æ¬¡æ¡æ‰‹çš„原因:

  • ä¸‰æ¬¡æ¡æ‰‹æ‰å¯ä»¥é˜»æ­¢é‡å¤åކå²è¿žæŽ¥çš„åˆå§‹åŒ–(主è¦åŽŸå› )
  • ä¸‰æ¬¡æ¡æ‰‹æ‰å¯ä»¥åŒæ­¥åŒæ–¹çš„åˆå§‹åºåˆ—å·
  • ä¸‰æ¬¡æ¡æ‰‹æ‰å¯ä»¥é¿å…èµ„æºæµªè´¹

原因一:é¿å…历å²è¿žæŽ¥

我们æ¥çœ‹çœ‹ RFC 793 指出的 TCP è¿žæŽ¥ä½¿ç”¨ä¸‰æ¬¡æ¡æ‰‹çš„首è¦åŽŸå› 

The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.

ç®€å•æ¥è¯´,ä¸‰æ¬¡æ¡æ‰‹çš„首è¦åŽŸå› æ˜¯ä¸ºäº†é˜²æ­¢æ—§çš„é‡å¤è¿žæŽ¥åˆå§‹åŒ–é€ æˆæ··ä¹±ã€‚

ç½‘ç»œçŽ¯å¢ƒæ˜¯é”™ç»¼å¤æ‚çš„,往往并䏿˜¯å¦‚我们期望的一样,å…ˆå‘é€çš„æ•°æ®åŒ…,就先到达目标主机,å而它很骚,å¯èƒ½ä¼šç”±äºŽç½‘络拥堵等乱七八糟的原因,会使得旧的数æ®åŒ…,先到达目标主机,é‚£ä¹ˆè¿™ç§æƒ…况下 TCP ä¸‰æ¬¡æ¡æ‰‹æ˜¯å¦‚何é¿å…的呢?

客户端连续å‘é€å¤šæ¬¡ SYN 建立连接的报文,在网络拥堵情况下:

  • 一个「旧 SYN æŠ¥æ–‡ã€æ¯”「最新的 SYN 〠报文早到达了æœåŠ¡ç«¯;
  • 那么此时æœåŠ¡ç«¯å°±ä¼šå›žä¸€ä¸ª SYN + ACK 报文给客户端;
  • 客户端收到åŽå¯ä»¥æ ¹æ®è‡ªèº«çš„上下文,判断这是一个历å²è¿žæŽ¥(åºåˆ—å·è¿‡æœŸæˆ–è¶…æ—¶),那么客户端就会å‘é€ RST 报文给æœåŠ¡ç«¯,表示中止这一次连接。

å¦‚æžœæ˜¯ä¸¤æ¬¡æ¡æ‰‹è¿žæŽ¥,å°±ä¸èƒ½åˆ¤æ–­å½“å‰è¿žæŽ¥æ˜¯å¦æ˜¯åކå²è¿žæŽ¥,ä¸‰æ¬¡æ¡æ‰‹åˆ™å¯ä»¥åœ¨å®¢æˆ·ç«¯(å‘逿–¹)准备å‘é€ç¬¬ä¸‰æ¬¡æŠ¥æ–‡æ—¶,客户端因有足够的上下文æ¥åˆ¤æ–­å½“å‰è¿žæŽ¥æ˜¯å¦æ˜¯åކå²è¿žæŽ¥:

  • 如果是历å²è¿žæŽ¥(åºåˆ—å·è¿‡æœŸæˆ–è¶…æ—¶),åˆ™ç¬¬ä¸‰æ¬¡æ¡æ‰‹å‘é€çš„æŠ¥æ–‡æ˜¯ RST 报文,以此中止历å²è¿žæŽ¥;
  • å¦‚æžœä¸æ˜¯åކå²è¿žæŽ¥,则第三次å‘é€çš„æŠ¥æ–‡æ˜¯ ACK 报文,é€šä¿¡åŒæ–¹å°±ä¼šæˆåŠŸå»ºç«‹è¿žæŽ¥;

所以,TCP ä½¿ç”¨ä¸‰æ¬¡æ¡æ‰‹å»ºç«‹è¿žæŽ¥çš„æœ€ä¸»è¦åŽŸå› æ˜¯é˜²æ­¢åŽ†å²è¿žæŽ¥åˆå§‹åŒ–了连接。

原因二:åŒæ­¥åŒæ–¹åˆå§‹åºåˆ—å·

TCP åè®®çš„é€šä¿¡åŒæ–¹, 都必须维护一个「åºåˆ—å·ã€, åºåˆ—å·æ˜¯å¯é ä¼ è¾“的一个关键因素,它的作用:

  • 接收方å¯ä»¥å޻除é‡å¤çš„æ•°æ®;
  • 接收方å¯ä»¥æ ¹æ®æ•°æ®åŒ…çš„åºåˆ—å·æŒ‰åºæŽ¥æ”¶;
  • å¯ä»¥æ ‡è¯†å‘é€å‡ºåŽ»çš„æ•°æ®åŒ…中, 哪些是已ç»è¢«å¯¹æ–¹æ”¶åˆ°çš„;

å¯è§,åºåˆ—å·åœ¨ TCP è¿žæŽ¥ä¸­å æ®ç€éžå¸¸é‡è¦çš„作用,所以当客户端å‘逿ºå¸¦ã€Œåˆå§‹åºåˆ—å·ã€çš„ SYN 报文的时候,éœ€è¦æœåŠ¡ç«¯å›žä¸€ä¸ª ACK 应答报文,表示客户端的 SYN 报文已被æœåŠ¡ç«¯æˆåŠŸæŽ¥æ”¶,那当æœåŠ¡ç«¯å‘é€ã€Œåˆå§‹åºåˆ—å·ã€ç»™å®¢æˆ·ç«¯çš„æ—¶å€™,ä¾ç„¶ä¹Ÿè¦å¾—到客户端的应答回应,这样一æ¥ä¸€å›ž,æ‰èƒ½ç¡®ä¿åŒæ–¹çš„åˆå§‹åºåˆ—å·èƒ½è¢«å¯é çš„åŒæ­¥ã€‚

å››æ¬¡æ¡æ‰‹å…¶å®žä¹Ÿèƒ½å¤Ÿå¯é çš„åŒæ­¥åŒæ–¹çš„åˆå§‹åŒ–åºå·,但由于第二步和第三步å¯ä»¥ä¼˜åŒ–æˆä¸€æ­¥,所以就æˆäº†ã€Œä¸‰æ¬¡æ¡æ‰‹ã€ã€‚

è€Œä¸¤æ¬¡æ¡æ‰‹åªä¿è¯äº†ä¸€æ–¹çš„åˆå§‹åºåˆ—å·èƒ½è¢«å¯¹æ–¹æˆåŠŸæŽ¥æ”¶,没办法ä¿è¯åŒæ–¹çš„åˆå§‹åºåˆ—å·éƒ½èƒ½è¢«ç¡®è®¤æŽ¥æ”¶ã€‚

原因三:é¿å…èµ„æºæµªè´¹

å¦‚æžœåªæœ‰ã€Œä¸¤æ¬¡æ¡æ‰‹ã€,当客户端的 SYN 请求连接在网络中阻塞,客户端没有接收到 ACK 报文,å°±ä¼šé‡æ–°å‘é€ SYN ,ç”±äºŽæ²¡æœ‰ç¬¬ä¸‰æ¬¡æ¡æ‰‹,æœåС噍䏿¸…æ¥šå®¢æˆ·ç«¯æ˜¯å¦æ”¶åˆ°äº†è‡ªå·±å‘é€çš„建立连接的 ACK 确认信å·,æ‰€ä»¥æ¯æ”¶åˆ°ä¸€ä¸ª SYN å°±åªèƒ½å…ˆä¸»åŠ¨å»ºç«‹ä¸€ä¸ªè¿žæŽ¥,这会造æˆä»€ä¹ˆæƒ…况呢?

如果客户端的 SYN 阻塞了,é‡å¤å‘é€å¤šæ¬¡ SYN 报文,那么æœåŠ¡å™¨åœ¨æ”¶åˆ°è¯·æ±‚åŽå°±ä¼šå»ºç«‹å¤šä¸ªå†—余的无效链接,造æˆä¸å¿…è¦çš„èµ„æºæµªè´¹ã€‚

å³ä¸¤æ¬¡æ¡æ‰‹ä¼šé€ æˆæ¶ˆæ¯æ»žç•™æƒ…况下,æœåС噍é‡å¤æŽ¥å—无用的连接请求 SYN 报文,而造æˆé‡å¤åˆ†é…资æºã€‚

å°ç»“

TCP 建立连接时,é€šè¿‡ä¸‰æ¬¡æ¡æ‰‹èƒ½é˜²æ­¢åކå²è¿žæŽ¥çš„建立,能å‡å°‘åŒæ–¹ä¸å¿…è¦çš„资æºå¼€é”€,èƒ½å¸®åŠ©åŒæ–¹åŒæ­¥åˆå§‹åŒ–åºåˆ—å·ã€‚åºåˆ—å·èƒ½å¤Ÿä¿è¯æ•°æ®åŒ…ä¸é‡å¤ã€ä¸ä¸¢å¼ƒå’ŒæŒ‰åºä¼ è¾“。

ä¸ä½¿ç”¨ã€Œä¸¤æ¬¡æ¡æ‰‹ã€å’Œã€Œå››æ¬¡æ¡æ‰‹ã€çš„原因:

  • ã€Œä¸¤æ¬¡æ¡æ‰‹ã€:无法防止历å²è¿žæŽ¥çš„建立,会造æˆåŒæ–¹èµ„æºçš„æµªè´¹,也无法å¯é çš„åŒæ­¥åŒæ–¹åºåˆ—å·;
  • ã€Œå››æ¬¡æ¡æ‰‹ã€:ä¸‰æ¬¡æ¡æ‰‹å°±å·²ç»ç†è®ºä¸Šæœ€å°‘å¯é è¿žæŽ¥å»ºç«‹,所以ä¸éœ€è¦ä½¿ç”¨æ›´å¤šçš„通信次数。

为什么客户端和æœåŠ¡ç«¯çš„åˆå§‹åºåˆ—å· ISN 是ä¸ç›¸åŒçš„?

如果一个已ç»å¤±æ•ˆçš„连接被é‡ç”¨äº†,ä½†æ˜¯è¯¥æ—§è¿žæŽ¥çš„åŽ†å²æŠ¥æ–‡è¿˜æ®‹ç•™åœ¨ç½‘ç»œä¸­,如果åºåˆ—å·ç›¸åŒ,é‚£ä¹ˆå°±æ— æ³•åˆ†è¾¨å‡ºè¯¥æŠ¥æ–‡æ˜¯ä¸æ˜¯åŽ†å²æŠ¥æ–‡,å¦‚æžœåŽ†å²æŠ¥æ–‡è¢«æ–°çš„è¿žæŽ¥æŽ¥æ”¶äº†,则会产生数æ®é”™ä¹±ã€‚

所以,æ¯æ¬¡å»ºç«‹è¿žæŽ¥å‰é‡æ–°åˆå§‹åŒ–一个åºåˆ—å·ä¸»è¦æ˜¯ä¸ºäº†é€šä¿¡åŒæ–¹èƒ½å¤Ÿæ ¹æ®åºå·å°†ä¸å±žäºŽæœ¬è¿žæŽ¥çš„æŠ¥æ–‡æ®µä¸¢å¼ƒã€‚

å¦ä¸€æ–¹é¢æ˜¯ä¸ºäº†å®‰å…¨æ€§,防止黑客伪造的相åŒåºåˆ—å·çš„ TCP 报文被对方接收。

åˆå§‹åºåˆ—å· ISN æ˜¯å¦‚ä½•éšæœºäº§ç”Ÿçš„?

èµ·å§‹ ISN 是基于时钟的,æ¯ 4 毫秒 + 1,è½¬ä¸€åœˆè¦ 4.55 ä¸ªå°æ—¶ã€‚

RFC1948 中æå‡ºäº†ä¸€ä¸ªè¾ƒå¥½çš„åˆå§‹åŒ–åºåˆ—å· ISN éšæœºç”Ÿæˆç®—法。

ISN = M + F (localhost, localport, remotehost, remoteport)

  • M 是一个计时器,这个计时器æ¯éš” 4 毫秒加 1。
  • F 是一个 Hash 算法,æ ¹æ®æº IPã€ç›®çš„ IPã€æºç«¯å£ã€ç›®çš„端å£ç”Ÿæˆä¸€ä¸ªéšæœºæ•°å€¼ã€‚è¦ä¿è¯ Hash 算法ä¸èƒ½è¢«å¤–部轻易推算得出,用 MD5 算法是一个比较好的选择。

既然 IP 层会分片,为什么 TCP å±‚è¿˜éœ€è¦ MSS å‘¢?

我们先æ¥è®¤è¯†ä¸‹ MTU å’Œ MSS

  • MTU:一个网络包的最大长度,以太网中一般为 1500 字节;
  • MSS:除去 IP å’Œ TCP 头部之åŽ,一个网络包所能容纳的 TCP æ•°æ®çš„æœ€å¤§é•¿åº¦;

如果在 TCP 的整个报文(头部 + æ•°æ®)交给 IP 层进行分片,会有什么异常呢?

当 IP 层有一个超过 MTU 大å°çš„æ•°æ®(TCP 头部 + TCP æ•°æ®)è¦å‘é€,那么 IP 层就è¦è¿›è¡Œåˆ†ç‰‡,把数æ®åˆ†ç‰‡æˆè‹¥å¹²ç‰‡,ä¿è¯æ¯ä¸€ä¸ªåˆ†ç‰‡éƒ½å°äºŽ MTU。把一份 IP æ•°æ®æŠ¥è¿›è¡Œåˆ†ç‰‡ä»¥åŽ,由目标主机的 IP 层æ¥è¿›è¡Œé‡æ–°ç»„装åŽ,å†äº¤ç»™ä¸Šä¸€å±‚ TCP 传输层。

这看起æ¥äº•然有åº,ä½†è¿™å­˜åœ¨éšæ‚£çš„,那么当如果一个 IP 分片丢失,整个 IP 报文的所有分片都得é‡ä¼ ã€‚

因为 IP 层本身没有超时é‡ä¼ æœºåˆ¶,它由传输层的 TCP æ¥è´Ÿè´£è¶…æ—¶å’Œé‡ä¼ ã€‚

当接收方å‘现 TCP 报文(头部 + æ•°æ®)çš„æŸä¸€ç‰‡ä¸¢å¤±åŽ,则ä¸ä¼šå“应 ACK 给对方,那么å‘逿–¹çš„ TCP 在超时åŽ,就会é‡å‘「整个 TCP 报文(头部 + æ•°æ®)ã€ã€‚

å› æ­¤,å¯ä»¥å¾—知由 IP 层进行分片传输,是éžå¸¸æ²¡æœ‰æ•ˆçŽ‡çš„ã€‚

所以,为了达到最佳的传输效能 TCP å议在建立连接的时候通常è¦åå•†åŒæ–¹çš„ MSS 值,当 TCP 层å‘现数æ®è¶…过 MSS æ—¶,则就先会进行分片,当然由它形æˆçš„ IP 包的长度也就ä¸ä¼šå¤§äºŽ MTU ,自然也就ä¸ç”¨ IP 分片了。

ç»è¿‡ TCP 层分片åŽ,如果一个 TCP 分片丢失åŽ,进行é‡å‘时也是以 MSS 为å•ä½,而ä¸ç”¨é‡ä¼ æ‰€æœ‰çš„分片,大大增加了é‡ä¼ çš„æ•ˆçŽ‡ã€‚

什么是 SYN 攻击?如何é¿å… SYN 攻击?

SYN 攻击

æˆ‘ä»¬éƒ½çŸ¥é“ TCP 连接建立是需è¦ä¸‰æ¬¡æ¡æ‰‹,å‡è®¾æ”»å‡»è€…短时间伪造ä¸åŒ IP 地å€çš„ SYN 报文,æœåŠ¡ç«¯æ¯æŽ¥æ”¶åˆ°ä¸€ä¸ª SYN 报文,就进入SYN_RCVD 状æ€,但æœåŠ¡ç«¯å‘é€å‡ºåŽ»çš„ ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,ä¹…è€Œä¹…ä¹‹å°±ä¼šå æ»¡æœåŠ¡ç«¯çš„ SYN 接收队列(未连接队列),使得æœåС噍ä¸èƒ½ä¸ºæ­£å¸¸ç”¨æˆ·æœåŠ¡ã€‚

é¿å… SYN 攻击方å¼ä¸€

其中一ç§è§£å†³æ–¹å¼æ˜¯é€šè¿‡ä¿®æ”¹ Linux å†…æ ¸å‚æ•°,控制队列大å°å’Œå½“队列满时应åšä»€ä¹ˆå¤„ç†ã€‚

  • å½“ç½‘å¡æŽ¥æ”¶æ•°æ®åŒ…的速度大于内核处ç†çš„速度时,会有一个队列ä¿å­˜è¿™äº›æ•°æ®åŒ…ã€‚æŽ§åˆ¶è¯¥é˜Ÿåˆ—çš„æœ€å¤§å€¼å¦‚ä¸‹å‚æ•°:
net.coredev_max_backlog
  • SYN_RCVD 状æ€è¿žæŽ¥çš„æœ€å¤§ä¸ªæ•°:
net.ipv4.tcp_max_syn_backlog
  • 超出处ç†èƒ½æ—¶,对新的 SYN 直接回报 RST,丢弃连接:
net.ipv4.tcp_abort_on_overflow

é¿å… SYN 攻击方å¼äºŒ

我们先æ¥çœ‹ä¸‹ Linux 内核的 SYN (未完æˆè¿žæŽ¥å»ºç«‹)队列与 Accpet (已完æˆè¿žæŽ¥å»ºç«‹)队列是如何工作的?

正常æµç¨‹:

  • 当æœåŠ¡ç«¯æŽ¥æ”¶åˆ°å®¢æˆ·ç«¯çš„ SYN 报文时,会将其加入到内核的「 SYN 队列ã€;
  • 接ç€å‘é€ SYN + ACK 给客户端,等待客户端回应 ACK 报文;
  • æœåŠ¡ç«¯æŽ¥æ”¶åˆ° ACK 报文åŽ,从「 SYN 队列ã€ç§»é™¤æ”¾å…¥åˆ°ã€Œ Accept 队列ã€;
  • 应用通过调用 accpet() socket 接å£,从「 Accept 队列ã€å–出连接。

应用程åºè¿‡æ…¢:

  • 如果应用程åºè¿‡æ…¢æ—¶,就会导致「 Accept 队列ã€è¢«å æ»¡ã€‚

å—到 SYN 攻击:

  • å¦‚æžœä¸æ–­å—到 SYN 攻击,就会导致「 SYN 队列ã€è¢«å æ»¡ã€‚

tcp_syncookies 的方å¼å¯ä»¥åº”对 SYN 攻击的方法:

net.ipv4.tcp_syncookies = 1

  • 当 「 SYN é˜Ÿåˆ—ã€æ»¡ä¹‹åŽ,åŽç»­æœåŠ¡å™¨æ”¶åˆ° SYN 包,ä¸è¿›å…¥ã€Œ SYN 队列ã€;
  • 计算出一个 cookie 值,å†ä»¥ SYN + ACK 中的「åºåˆ—å·ã€è¿”回客户端,
  • æœåŠ¡ç«¯æŽ¥æ”¶åˆ°å®¢æˆ·ç«¯çš„åº”ç­”æŠ¥æ–‡æ—¶,æœåŠ¡å™¨ä¼šæ£€æŸ¥è¿™ä¸ª ACK åŒ…çš„åˆæ³•æ€§ã€‚å¦‚æžœåˆæ³•,直接放入到「 Accept 队列ã€ã€‚
  • 最åŽåº”用通过调用 accpet() socket 接å£,从「 Accept 队列ã€å–出的连接。

03 TCP 连接断开

TCP 四次挥手过程和状æ€å˜è¿

å¤©ä¸‹æ²¡æœ‰ä¸æ•£çš„宴席,对于 TCP 连接也是这样, TCP 断开连接是通过四次挥手方å¼ã€‚

åŒæ–¹éƒ½å¯ä»¥ä¸»åŠ¨æ–­å¼€è¿žæŽ¥,断开连接åŽä¸»æœºä¸­çš„「资æºã€å°†è¢«é‡Šæ”¾ã€‚

  • 客户端打算关闭连接,此时会å‘é€ä¸€ä¸ª TCP 首部 FIN 标志ä½è¢«ç½®ä¸º 1 的报文,ä¹Ÿå³ FIN 报文,之åŽå®¢æˆ·ç«¯è¿›å…¥ FIN_WAIT_1 状æ€ã€‚
  • æœåŠ¡ç«¯æ”¶åˆ°è¯¥æŠ¥æ–‡åŽ,å°±å‘客户端å‘é€ ACK 应答报文,æŽ¥ç€æœåŠ¡ç«¯è¿›å…¥ CLOSED_WAIT 状æ€ã€‚
  • 客户端收到æœåŠ¡ç«¯çš„ ACK 应答报文åŽ,之åŽè¿›å…¥ FIN_WAIT_2 状æ€ã€‚
  • 等待æœåŠ¡ç«¯å¤„ç†å®Œæ•°æ®åŽ,也å‘客户端å‘é€ FIN 报文,ä¹‹åŽæœåŠ¡ç«¯è¿›å…¥ LAST_ACK 状æ€ã€‚
  • 客户端收到æœåŠ¡ç«¯çš„ FIN 报文åŽ,回一个 ACK 应答报文,之åŽè¿›å…¥ TIME_WAIT 状æ€
  • æœåŠ¡å™¨æ”¶åˆ°äº† ACK 应答报文åŽ,就进入了 CLOSED 状æ€,至此æœåŠ¡ç«¯å·²ç»å®Œæˆè¿žæŽ¥çš„关闭。
  • 客户端在ç»è¿‡ 2MSL 一段时间åŽ,自动进入 CLOSED 状æ€,至此客户端也完æˆè¿žæŽ¥çš„关闭。

ä½ å¯ä»¥çœ‹åˆ°,æ¯ä¸ªæ–¹å‘都需è¦ä¸€ä¸ª FIN 和一个 ACK,因此通常被称为四次挥手。

è¿™é‡Œä¸€ç‚¹éœ€è¦æ³¨æ„是:主动关闭连接的,æ‰æœ‰ TIME_WAIT 状æ€ã€‚

为什么挥手需è¦å››æ¬¡?

冿¥å›žé¡¾ä¸‹å››æ¬¡æŒ¥æ‰‹åŒæ–¹å‘ FIN 包的过程,就能ç†è§£ä¸ºä»€ä¹ˆéœ€è¦å››æ¬¡äº†ã€‚

  • 关闭连接时,å®¢æˆ·ç«¯å‘æœåŠ¡ç«¯å‘é€ FIN æ—¶,仅仅表示客户端ä¸å†å‘逿•°æ®äº†ä½†æ˜¯è¿˜èƒ½æŽ¥æ”¶æ•°æ®ã€‚
  • æœåŠ¡å™¨æ”¶åˆ°å®¢æˆ·ç«¯çš„ FIN 报文时,先回一个 ACK 应答报文,而æœåŠ¡ç«¯å¯èƒ½è¿˜æœ‰æ•°æ®éœ€è¦å¤„ç†å’Œå‘é€,ç­‰æœåŠ¡ç«¯ä¸å†å‘逿•°æ®æ—¶,æ‰å‘é€ FIN 报文给客户端æ¥è¡¨ç¤ºåŒæ„现在关闭连接。

从上é¢è¿‡ç¨‹å¯çŸ¥,æœåŠ¡ç«¯é€šå¸¸éœ€è¦ç­‰å¾…å®Œæˆæ•°æ®çš„å‘é€å’Œå¤„ç†,所以æœåŠ¡ç«¯çš„ ACK å’Œ FIN 一般都会分开å‘é€,ä»Žè€Œæ¯”ä¸‰æ¬¡æ¡æ‰‹å¯¼è‡´å¤šäº†ä¸€æ¬¡ã€‚

为什么 TIME_WAIT 等待的时间是 2MSL?

MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP å议的,而 IP 头中有一个 TTL 字段,是 IP æ•°æ®æŠ¥å¯ä»¥ç»è¿‡çš„æœ€å¤§è·¯ç”±æ•°,æ¯ç»è¿‡ä¸€ä¸ªå¤„ç†ä»–çš„è·¯ç”±å™¨æ­¤å€¼å°±å‡ 1,当此值为 0 åˆ™æ•°æ®æŠ¥å°†è¢«ä¸¢å¼ƒ,åŒæ—¶å‘é€ ICMP 报文通知æºä¸»æœºã€‚

MSL 与 TTL 的区别: MSL çš„å•使˜¯æ—¶é—´,而 TTL 是ç»è¿‡è·¯ç”±è·³æ•°ã€‚所以 MSL 应该è¦å¤§äºŽç­‰äºŽ TTL 消耗为 0 的时间,ä»¥ç¡®ä¿æŠ¥æ–‡å·²è¢«è‡ªç„¶æ¶ˆäº¡ã€‚

TIME_WAIT 等待 2 å€çš„ MSL,比较åˆç†çš„解释是: 网络中å¯èƒ½å­˜åœ¨æ¥è‡ªå‘逿–¹çš„æ•°æ®åŒ…,当这些å‘逿–¹çš„æ•°æ®åŒ…被接收方处ç†åŽåˆä¼šå‘对方å‘é€å“应,所以一æ¥ä¸€å›žéœ€è¦ç­‰å¾… 2 å€çš„æ—¶é—´ã€‚

比如如果被动关闭方没有收到断开连接的最åŽçš„ ACK 报文,就会触å‘è¶…æ—¶é‡å‘ Fin 报文,å¦ä¸€æ–¹æŽ¥æ”¶åˆ° FIN åŽ,会é‡å‘ ACK 给被动关闭方, 一æ¥ä¸€åŽ»æ­£å¥½ 2 个 MSL。

2MSL 的时间是从客户端接收到 FIN åŽå‘é€ ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到æœåŠ¡ç«¯,å®¢æˆ·ç«¯åˆæŽ¥æ”¶åˆ°äº†æœåŠ¡ç«¯é‡å‘çš„ FIN 报文,那么 2MSL æ—¶é—´å°†é‡æ–°è®¡æ—¶ã€‚

在 Linux 系统里 2MSL 默认是 60 ç§’,那么一个 MSL 也就是 30 秒。Linux 系统åœç•™åœ¨ TIME_WAIT 的时间为固定的 60 秒。

其定义在 Linux 内核代ç é‡Œçš„å称为 TCP_TIMEWAIT_LEN:

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT 
                                    state, about 60 seconds  */

如果è¦ä¿®æ”¹ TIME_WAIT 的时间长度,åªèƒ½ä¿®æ”¹ Linux 内核代ç é‡Œ TCP_TIMEWAIT_LEN 的值,并釿–°ç¼–译 Linux 内核。

ä¸ºä»€ä¹ˆéœ€è¦ TIME_WAIT 状æ€?

主动å‘起关闭连接的一方,æ‰ä¼šæœ‰ TIME-WAIT 状æ€ã€‚

éœ€è¦ TIME-WAIT 状æ€,ä¸»è¦æ˜¯ä¸¤ä¸ªåŽŸå› :

  • 防止具有相åŒã€Œå››å…ƒç»„ã€çš„ã€Œæ—§ã€æ•°æ®åŒ…被收到;
  • ä¿è¯ã€Œè¢«åŠ¨å…³é—­è¿žæŽ¥ã€çš„一方能被正确的关闭,å³ä¿è¯æœ€åŽçš„ ACK 能让被动关闭方接收,从而帮助其正常关闭;

原因一:防止旧连接的数æ®åŒ…

å‡è®¾ TIME-WAIT 没有等待时间或时间过短,被延迟的数æ®åŒ…抵达åŽä¼šå‘生什么呢?

  • 如上图黄色框框æœåŠ¡ç«¯åœ¨å…³é—­è¿žæŽ¥ä¹‹å‰å‘é€çš„ SEQ = 301 报文,被网络延迟了。
  • 这时有相åŒç«¯å£çš„ TCP 连接被å¤ç”¨åŽ,被延迟的 SEQ = 301 抵达了客户端,那么客户端是有å¯èƒ½æ­£å¸¸æŽ¥æ”¶è¿™ä¸ªè¿‡æœŸçš„æŠ¥æ–‡,这就会产生数æ®é”™ä¹±ç­‰ä¸¥é‡çš„问题。

所以,TCP 就设计出了这么一个机制,ç»è¿‡ 2MSL 这个时间,足以让两个方å‘上的数æ®åŒ…都被丢弃,使得原æ¥è¿žæŽ¥çš„æ•°æ®åŒ…在网络中都自然消失,å†å‡ºçŽ°çš„æ•°æ®åŒ…一定都是新建立连接所产生的。

原因二:ä¿è¯è¿žæŽ¥æ­£ç¡®å…³é—­

在 RFC 793 指出 TIME-WAIT å¦ä¸€ä¸ªé‡è¦çš„作用是:

TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.

也就是说,TIME-WAIT ä½œç”¨æ˜¯ç­‰å¾…è¶³å¤Ÿçš„æ—¶é—´ä»¥ç¡®ä¿æœ€åŽçš„ ACK 能让被动关闭方接收,从而帮助其正常关闭。

å‡è®¾ TIME-WAIT 没有等待时间或时间过短,断开连接会造æˆä»€ä¹ˆé—®é¢˜å‘¢?

  • 如上图红色框框客户端四次挥手的最åŽä¸€ä¸ª ACK 报文如果在网络中被丢失了,此时如果客户端 TIME-WAIT 过短或没有,则就直接进入了 CLOSED 状æ€äº†,那么æœåŠ¡ç«¯åˆ™ä¼šä¸€ç›´å¤„åœ¨ LASE_ACK 状æ€ã€‚
  • 当客户端å‘起建立连接的 SYN 请求报文åŽ,æœåŠ¡ç«¯ä¼šå‘é€ RST 报文给客户端,连接建立的过程就会被终止。

如果 TIME-WAIT 等待足够长的情况就会é‡åˆ°ä¸¤ç§æƒ…况:

  • æœåŠ¡ç«¯æ­£å¸¸æ”¶åˆ°å››æ¬¡æŒ¥æ‰‹çš„æœ€åŽä¸€ä¸ª ACK 报文,则æœåŠ¡ç«¯æ­£å¸¸å…³é—­è¿žæŽ¥ã€‚
  • æœåŠ¡ç«¯æ²¡æœ‰æ”¶åˆ°å››æ¬¡æŒ¥æ‰‹çš„æœ€åŽä¸€ä¸ª ACK 报文时,则会é‡å‘ FIN 关闭连接报文并等待新的 ACK 报文。

所以客户端在 TIME-WAIT 状æ€ç­‰å¾… 2MSL æ—¶é—´åŽ,å°±å¯ä»¥ä¿è¯åŒæ–¹çš„连接都å¯ä»¥æ­£å¸¸çš„关闭。

TIME_WAIT 过多有什么å±å®³?

如果æœåŠ¡å™¨æœ‰å¤„äºŽ TIME-WAIT 状æ€çš„ TCP,则说明是由æœåŠ¡å™¨æ–¹ä¸»åŠ¨å‘起的断开请求。

过多的 TIME-WAIT 状æ€ä¸»è¦çš„å±å®³æœ‰ä¸¤ç§:

  • 第一是内存资æºå ç”¨;
  • 第二是对端å£èµ„æºçš„å ç”¨,一个 TCP 连接至少消耗一个本地端å£;

第二个å±å®³æ˜¯ä¼šé€ æˆä¸¥é‡çš„åŽæžœçš„,è¦çŸ¥é“,端å£èµ„æºä¹Ÿæ˜¯æœ‰é™çš„,一般å¯ä»¥å¼€å¯çš„端å£ä¸º 32768~61000,也å¯ä»¥é€šè¿‡å¦‚䏋傿•°è®¾ç½®æŒ‡å®š

net.ipv4.ip_local_port_range

如果å‘起连接一方的 TIME_WAIT 状æ€è¿‡å¤š,å æ»¡äº†æ‰€æœ‰ç«¯å£èµ„æº,则会导致无法创建新连接。

客户端å—端å£èµ„æºé™åˆ¶:

  • 客户端TIME_WAIT过多,就会导致端å£èµ„æºè¢«å ç”¨,因为端å£å°±65536个,è¢«å æ»¡å°±ä¼šå¯¼è‡´æ— æ³•创建新的连接。

æœåŠ¡ç«¯å—系统资æºé™åˆ¶:

  • 由于一个四元组表示 TCP 连接,ç†è®ºä¸ŠæœåŠ¡ç«¯å¯ä»¥å»ºç«‹å¾ˆå¤šè¿žæŽ¥,æœåŠ¡ç«¯ç¡®å®žåªç›‘å¬ä¸€ä¸ªç«¯å£ 但是会把连接扔给处ç†çº¿ç¨‹,所以ç†è®ºä¸Šç›‘å¬çš„端å£å¯ä»¥ç»§ç»­ç›‘å¬ã€‚但是线程池处ç†ä¸äº†é‚£ä¹ˆå¤šä¸€ç›´ä¸æ–­çš„连接了。所以当æœåŠ¡ç«¯å‡ºçŽ°å¤§é‡ TIME_WAIT æ—¶,系统资æºè¢«å æ»¡æ—¶,会导致处ç†ä¸è¿‡æ¥æ–°çš„连接。

如何优化 TIME_WAIT?

这里给出优化 TIME-WAIT 的几个方å¼,都是有利有弊:

  • 打开 net.ipv4.tcp_tw_reuse å’Œ net.ipv4.tcp_timestamps 选项;
  • net.ipv4.tcp_max_tw_buckets
  • 程åºä¸­ä½¿ç”¨ SO_LINGER ,应用强制使用 RST 关闭。

æ–¹å¼ä¸€:net.ipv4.tcp_tw_reuse å’Œ tcp_timestamps

如下的 Linux å†…æ ¸å‚æ•°å¼€å¯åŽ,则å¯ä»¥å¤ç”¨å¤„于 TIME_WAIT çš„ socket 为新的连接所用。

æœ‰ä¸€ç‚¹éœ€è¦æ³¨æ„的是,tcp_tw_reuse 功能åªèƒ½ç”¨å®¢æˆ·ç«¯(连接å‘èµ·æ–¹),因为开å¯äº†è¯¥åŠŸèƒ½,在调用 connect() 函数时,å†…æ ¸ä¼šéšæœºæ‰¾ä¸€ä¸ª time_wait 状æ€è¶…过 1 秒的连接给新的连接å¤ç”¨ã€‚

net.ipv4.tcp_tw_reuse = 1

使用这个选项,è¿˜æœ‰ä¸€ä¸ªå‰æ,éœ€è¦æ‰“开对 TCP 时间戳的支æŒ,å³

net.ipv4.tcp_timestamps=1(默认å³ä¸º 1)

这个时间戳的字段是在 TCP 头部的「选项ã€é‡Œ,用于记录 TCP å‘逿–¹çš„当剿—¶é—´æˆ³å’Œä»Žå¯¹ç«¯æŽ¥æ”¶åˆ°çš„æœ€æ–°æ—¶é—´æˆ³ã€‚

由于引入了时间戳,我们在å‰é¢æåˆ°çš„ 2MSL 问题就ä¸å¤å­˜åœ¨äº†,因为é‡å¤çš„æ•°æ®åŒ…会因为时间戳过期被自然丢弃。

æ–¹å¼äºŒ:net.ipv4.tcp_max_tw_buckets

这个值默认为 18000,当系统中处于 TIME_WAIT 的连接一旦超过这个值时,系统就会将åŽé¢çš„ TIME_WAIT 连接状æ€é‡ç½®ã€‚

这个方法过于暴力,è€Œä¸”æ²»æ ‡ä¸æ²»æœ¬,带æ¥çš„问题远比解决的问题多,䏿ލè使用。

æ–¹å¼ä¸‰:程åºä¸­ä½¿ç”¨ SO_LINGER

我们å¯ä»¥é€šè¿‡è®¾ç½® socket 选项,æ¥è®¾ç½®è°ƒç”¨ close 关闭连接行为。

struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger,sizeof(so_linger));

如果l_onoffä¸ºéž 0, 且l_linger值为 0,那么调用closeåŽ,会立该å‘é€ä¸€ä¸ªRST标志给对端,该 TCP 连接将跳过四次挥手,也就跳过了TIME_WAIT状æ€,直接关闭。

但这为跨越TIME_WAITçŠ¶æ€æä¾›äº†ä¸€ä¸ªå¯èƒ½,ä¸è¿‡æ˜¯ä¸€ä¸ªéžå¸¸å±é™©çš„行为,ä¸å€¼å¾—æå€¡ã€‚

如果已ç»å»ºç«‹äº†è¿žæŽ¥,但是客户端çªç„¶å‡ºçŽ°æ•…éšœäº†æ€Žä¹ˆåŠž?

TCP æœ‰ä¸€ä¸ªæœºåˆ¶æ˜¯ä¿æ´»æœºåˆ¶ã€‚è¿™ä¸ªæœºåˆ¶çš„åŽŸç†æ˜¯è¿™æ ·çš„:

定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP ä¿æ´»æœºåˆ¶ä¼šå¼€å§‹ä½œç”¨,æ¯éš”一个时间间隔,å‘é€ä¸€ä¸ªæŽ¢æµ‹æŠ¥æ–‡,该探测报文包å«çš„æ•°æ®éžå¸¸å°‘,如果连续几个探测报文都没有得到å“应,则认为当å‰çš„ TCP è¿žæŽ¥å·²ç»æ­»äº¡,系统内核将错误信æ¯é€šçŸ¥ç»™ä¸Šå±‚应用程åºã€‚

在 Linux 内核å¯ä»¥æœ‰å¯¹åº”çš„å‚æ•°å¯ä»¥è®¾ç½®ä¿æ´»æ—¶é—´ã€ä¿æ´»æŽ¢æµ‹çš„æ¬¡æ•°ã€ä¿æ´»æŽ¢æµ‹çš„æ—¶é—´é—´éš”,以下都为默认值:

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75  
net.ipv4.tcp_keepalive_probes=9
  • tcp_keepalive_time=7200:è¡¨ç¤ºä¿æ´»æ—¶é—´æ˜¯ 7200 ç§’(2å°æ—¶),也就 2 å°æ—¶å†…如果没有任何连接相关的活动,则会å¯åŠ¨ä¿æ´»æœºåˆ¶
  • tcp_keepalive_intvl=75:è¡¨ç¤ºæ¯æ¬¡æ£€æµ‹é—´éš” 75 ç§’;
  • tcp_keepalive_probes=9:表示检测 9 次无å“应,认为对方是ä¸å¯è¾¾çš„,从而中断本次的连接。

也就是说在 Linux 系统中,最少需è¦ç»è¿‡ 2 å°æ—¶ 11 分 15 ç§’æ‰å¯ä»¥å‘现一个「死亡ã€è¿žæŽ¥ã€‚

这个时间是有点长的,我们也å¯ä»¥æ ¹æ®å®žé™…的需求,å¯¹ä»¥ä¸Šçš„ä¿æ´»ç›¸å…³çš„傿•°è¿›è¡Œè®¾ç½®ã€‚

如果开å¯äº† TCP ä¿æ´»,需è¦è€ƒè™‘ä»¥ä¸‹å‡ ç§æƒ…况:

第一ç§,å¯¹ç«¯ç¨‹åºæ˜¯æ­£å¸¸å·¥ä½œçš„。当 TCP ä¿æ´»çš„æŽ¢æµ‹æŠ¥æ–‡å‘é€ç»™å¯¹ç«¯, 对端会正常å“应,这样 TCP ä¿æ´»æ—¶é—´ä¼šè¢«é‡ç½®,等待下一个 TCP ä¿æ´»æ—¶é—´çš„到æ¥ã€‚

第二ç§,对端程åºå´©æºƒå¹¶é‡å¯ã€‚当 TCP ä¿æ´»çš„æŽ¢æµ‹æŠ¥æ–‡å‘é€ç»™å¯¹ç«¯åŽ,对端是å¯ä»¥å“应的,但由于没有该连接的有效信æ¯,会产生一个 RST 报文,这样很快就会å‘现 TCP 连接已ç»è¢«é‡ç½®ã€‚

第三ç§,是对端程åºå´©æºƒ,或对端由于其他原因导致报文ä¸å¯è¾¾ã€‚当 TCP ä¿æ´»çš„æŽ¢æµ‹æŠ¥æ–‡å‘é€ç»™å¯¹ç«¯åŽ,石沉大海,没有å“应,连续几次,è¾¾åˆ°ä¿æ´»æŽ¢æµ‹æ¬¡æ•°åŽ,TCP 会报告该 TCP è¿žæŽ¥å·²ç»æ­»äº¡ã€‚


04 Socket 编程

针对 TCP 应该如何 Socket 编程?

  • æœåŠ¡ç«¯å’Œå®¢æˆ·ç«¯åˆå§‹åŒ– socket,得到文件æè¿°ç¬¦;
  • æœåŠ¡ç«¯è°ƒç”¨ bind,将绑定在 IP 地å€å’Œç«¯å£;
  • æœåŠ¡ç«¯è°ƒç”¨ listen,进行监å¬;
  • æœåŠ¡ç«¯è°ƒç”¨ accept,等待客户端连接;
  • 客户端调用 connect,呿œåŠ¡å™¨ç«¯çš„åœ°å€å’Œç«¯å£å‘起连接请求;
  • æœåŠ¡ç«¯ accept 返回用于传输的 socket 的文件æè¿°ç¬¦;
  • 客户端调用 write 写入数æ®;æœåŠ¡ç«¯è°ƒç”¨ read è¯»å–æ•°æ®;
  • 客户端断开连接时,会调用 close,那么æœåŠ¡ç«¯ read è¯»å–æ•°æ®çš„æ—¶å€™,就会读å–到了 EOF,待处ç†å®Œæ•°æ®åŽ,æœåŠ¡ç«¯è°ƒç”¨ close,表示连接关闭。

è¿™é‡Œéœ€è¦æ³¨æ„的是,æœåŠ¡ç«¯è°ƒç”¨ accept æ—¶,连接æˆåŠŸäº†ä¼šè¿”å›žä¸€ä¸ªå·²å®Œæˆè¿žæŽ¥çš„ socket,åŽç»­ç”¨æ¥ä¼ è¾“æ•°æ®ã€‚

所以,监å¬çš„ socket 和真正用æ¥ä¼ é€æ•°æ®çš„ socket,是「两个〠socket,一个å«ä½œç›‘å¬ socket,一个å«ä½œå·²å®Œæˆè¿žæŽ¥ socket。

æˆåŠŸè¿žæŽ¥å»ºç«‹ä¹‹åŽ,åŒæ–¹å¼€å§‹é€šè¿‡ read å’Œ write 函数æ¥è¯»å†™æ•°æ®,å°±åƒå¾€ä¸€ä¸ªæ–‡ä»¶æµé‡Œé¢å†™ä¸œè¥¿ä¸€æ ·ã€‚

listen æ—¶å€™å‚æ•° backlog çš„æ„义?

Linux内核中会维护两个队列:

  • 未完æˆè¿žæŽ¥é˜Ÿåˆ—(SYN 队列):接收到一个 SYN 建立连接请求,处于 SYN_RCVD 状æ€;
  • 已完æˆè¿žæŽ¥é˜Ÿåˆ—(Accpet 队列):å·²å®Œæˆ TCP ä¸‰æ¬¡æ¡æ‰‹è¿‡ç¨‹,处于 ESTABLISHED 状æ€;

int listen (int socketfd, int backlog)
  • 傿•°ä¸€ socketfd 为 socketfd 文件æè¿°ç¬¦
  • 傿•°äºŒ backlog,è¿™å‚æ•°åœ¨åކå²ç‰ˆæœ¬æœ‰ä¸€å®šçš„å˜åŒ–

在早期 Linux 内核 backlog 是 SYN 队列大å°,也就是未完æˆçš„队列大å°ã€‚

在 Linux 内核 2.2 之åŽ,backlog å˜æˆ accept 队列,也就是已完æˆè¿žæŽ¥å»ºç«‹çš„队列长度,所以现在通常认为 backlog 是 accept 队列。

但是上é™å€¼æ˜¯å†…æ ¸å‚æ•° somaxconn 的大å°,也就说 accpet 队列长度 = min(backlog, somaxconn)。

accept å‘ç”Ÿåœ¨ä¸‰æ¬¡æ¡æ‰‹çš„哪一步?

我们先看看客户端连接æœåŠ¡ç«¯æ—¶,å‘é€äº†ä»€ä¹ˆ?

  • 客户端的åè®®æ ˆå‘æœåŠ¡å™¨ç«¯å‘é€äº† SYN 包,并告诉æœåŠ¡å™¨ç«¯å½“å‰å‘é€åºåˆ—å· client_isn,客户端进入 SYN_SENT 状æ€;
  • æœåŠ¡å™¨ç«¯çš„å议栈收到这个包之åŽ,和客户端进行 ACK 应答,应答的值为 client_isn+1,表示对 SYN 包 client_isn 的确认,åŒæ—¶æœåŠ¡å™¨ä¹Ÿå‘é€ä¸€ä¸ª SYN 包,å‘Šè¯‰å®¢æˆ·ç«¯å½“å‰æˆ‘çš„å‘é€åºåˆ—å·ä¸º server_isn,æœåŠ¡å™¨ç«¯è¿›å…¥ SYN_RCVD 状æ€;
  • 客户端å议栈收到 ACK 之åŽ,使得应用程åºä»Ž connect 调用返回,表示客户端到æœåŠ¡å™¨ç«¯çš„å•å‘连接建立æˆåŠŸ,客户端的状æ€ä¸º ESTABLISHED,åŒæ—¶å®¢æˆ·ç«¯å议栈也会对æœåŠ¡å™¨ç«¯çš„ SYN 包进行应答,应答数æ®ä¸º server_isn+1;
  • 应答包到达æœåŠ¡å™¨ç«¯åŽ,æœåŠ¡å™¨ç«¯å议栈使得 accept 阻塞调用返回,这个时候æœåŠ¡å™¨ç«¯åˆ°å®¢æˆ·ç«¯çš„å•å‘连接也建立æˆåŠŸ,æœåŠ¡å™¨ç«¯ä¹Ÿè¿›å…¥ ESTABLISHED 状æ€ã€‚

从上é¢çš„æè¿°è¿‡ç¨‹,我们å¯ä»¥å¾—知客户端 connect æˆåŠŸè¿”å›žæ˜¯åœ¨ç¬¬äºŒæ¬¡æ¡æ‰‹,æœåŠ¡ç«¯ accept æˆåŠŸè¿”å›žæ˜¯åœ¨ä¸‰æ¬¡æ¡æ‰‹æˆåŠŸä¹‹åŽã€‚

客户端调用 close 了,连接是断开的æµç¨‹æ˜¯ä»€ä¹ˆ?

我们看看客户端主动调用了 close,会å‘生什么?

  • 客户端调用 close,表明客户端没有数æ®éœ€è¦å‘é€äº†,åˆ™æ­¤æ—¶ä¼šå‘æœåŠ¡ç«¯å‘é€ FIN 报文,进入 FIN_WAIT_1 状æ€;
  • æœåŠ¡ç«¯æŽ¥æ”¶åˆ°äº† FIN 报文,TCP å议栈会为 FIN 包æ’入一个文件结æŸç¬¦ EOF 到接收缓冲区中,应用程åºå¯ä»¥é€šè¿‡ read è°ƒç”¨æ¥æ„ŸçŸ¥è¿™ä¸ª FIN 包。这个 EOF 会被放在已排队等候的其他已接收的数æ®ä¹‹åŽ,这就æ„å‘³ç€æœåŠ¡ç«¯éœ€è¦å¤„ç†è¿™ç§å¼‚常情况,因为 EOF è¡¨ç¤ºåœ¨è¯¥è¿žæŽ¥ä¸Šå†æ— é¢å¤–æ•°æ®åˆ°è¾¾ã€‚此时,æœåŠ¡ç«¯è¿›å…¥ CLOSE_WAIT 状æ€;
  • 接ç€,当处ç†å®Œæ•°æ®åŽ,自然就会读到 EOF,于是也调用 close 关闭它的套接字,这会使得客户端会å‘出一个 FIN 包,之åŽå¤„于 LAST_ACK 状æ€;
  • 客户端接收到æœåŠ¡ç«¯çš„ FIN 包,å¹¶å‘é€ ACK 确认包给æœåŠ¡ç«¯,此时客户端将进入 TIME_WAIT 状æ€;
  • æœåŠ¡ç«¯æ”¶åˆ° ACK 确认包åŽ,就进入了最åŽçš„ CLOSE 状æ€;
  • 客户端ç»è¿‡ 2MSL 时间之åŽ,也进入 CLOSE 状æ€;

05 é‡ä¼ æœºåˆ¶

TCP 实现å¯é ä¼ è¾“的方å¼ä¹‹ä¸€,是通过åºåˆ—å·ä¸Žç¡®è®¤åº”答。

在 TCP 中,当å‘é€ç«¯çš„æ•°æ®åˆ°è¾¾æŽ¥æ”¶ä¸»æœºæ—¶,接收端主机会返回一个确认应答消æ¯,表示已收到消æ¯ã€‚

ä½†åœ¨é”™ç»¼å¤æ‚的网络,å¹¶ä¸ä¸€å®šèƒ½å¦‚上图那么顺利能正常的数æ®ä¼ è¾“,万一数æ®åœ¨ä¼ è¾“过程中丢失了呢?

所以 TCP 针对数æ®åŒ…丢失的情况,会用é‡ä¼ æœºåˆ¶è§£å†³ã€‚

接下æ¥è¯´è¯´å¸¸è§çš„é‡ä¼ æœºåˆ¶:

  • è¶…æ—¶é‡ä¼ 
  • 快速é‡ä¼ 
  • SACK
  • D-SACK

5.1 è¶…æ—¶é‡ä¼ 

é‡ä¼ æœºåˆ¶çš„其中一个方å¼,就是在å‘逿•°æ®æ—¶,设定一个定时器,当超过指定的时间åŽ,没有收到对方的 ACK 确认应答报文,就会é‡å‘该数æ®,也就是我们常说的超时é‡ä¼ ã€‚

TCP ä¼šåœ¨ä»¥ä¸‹ä¸¤ç§æƒ…况å‘生超时é‡ä¼ :

  • æ•°æ®åŒ…丢失
  • 确认应答丢失

超时时间应该设置为多少呢?

我们先æ¥äº†è§£ä¸€ä¸‹ä»€ä¹ˆæ˜¯ RTT(Round-Trip Time 往返时延),从下图我们就å¯ä»¥çŸ¥é“:

RTT 就是数æ®ä»Žç½‘络一端传é€åˆ°å¦ä¸€ç«¯æ‰€éœ€çš„æ—¶é—´,也就是包的往返时间。

è¶…æ—¶é‡ä¼ æ—¶é—´æ˜¯ä»¥ RTO (Retransmission Timeout è¶…æ—¶é‡ä¼ æ—¶é—´)表示。

å‡è®¾åœ¨é‡ä¼ çš„æƒ…况下,è¶…æ—¶æ—¶é—´ RTO ã€Œè¾ƒé•¿æˆ–è¾ƒçŸ­ã€æ—¶,会å‘生什么事情呢?

上图中有两ç§è¶…æ—¶æ—¶é—´ä¸åŒçš„æƒ…况:

  • 当超时时间 RTO 较大时,é‡å‘就慢,丢了è€åŠå¤©æ‰é‡å‘,没有效率,性能差;
  • 当超时时间 RTO è¾ƒå°æ—¶,会导致å¯èƒ½å¹¶æ²¡æœ‰ä¸¢å°±é‡å‘,于是é‡å‘的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的é‡å‘。

精确的测é‡è¶…æ—¶æ—¶é—´ RTO 的值是éžå¸¸é‡è¦çš„,è¿™å¯è®©æˆ‘们的é‡ä¼ æœºåˆ¶æ›´é«˜æ•ˆã€‚

æ ¹æ®ä¸Šè¿°çš„ä¸¤ç§æƒ…况,我们å¯ä»¥å¾—知,è¶…æ—¶é‡ä¼ æ—¶é—´ RTO 的值应该略大于报文往返 RTT 的值。

至此,å¯èƒ½å¤§å®¶è§‰å¾—è¶…æ—¶é‡ä¼ æ—¶é—´ RTO 的值计算,ä¹Ÿä¸æ˜¯å¾ˆå¤æ‚嘛。

好åƒå°±æ˜¯åœ¨å‘é€ç«¯å‘包时记下 t0 ,ç„¶åŽæŽ¥æ”¶ç«¯å†æŠŠè¿™ä¸ª ack å›žæ¥æ—¶å†è®°ä¸€ä¸ª t1,于是 RTT = t1 – t0。没那么简å•,è¿™åªæ˜¯ä¸€ä¸ªé‡‡æ ·,ä¸èƒ½ä»£è¡¨æ™®é情况。

实际上「报文往返 RTT çš„å€¼ã€æ˜¯ç»å¸¸å˜åŒ–çš„,因为我们的网络也是时常å˜åŒ–的。也就因为「报文往返 RTT 的值〠是ç»å¸¸æ³¢åЍå˜åŒ–çš„,所以「超时é‡ä¼ æ—¶é—´ RTO 的值ã€åº”该是一个动æ€å˜åŒ–的值。

我们æ¥çœ‹çœ‹ Linux 是如何计算 RTO 的呢?

估计往返时间,通常需è¦é‡‡æ ·ä»¥ä¸‹ä¸¤ä¸ª:

  • éœ€è¦ TCP 通过采样 RTT 的时间,ç„¶åŽè¿›è¡ŒåŠ æƒå¹³å‡,算出一个平滑 RTT 的值,而且这个值还是è¦ä¸æ–­å˜åŒ–çš„,å› ä¸ºç½‘ç»œçŠ¶å†µä¸æ–­åœ°å˜åŒ–。
  • 除了采样 RTT,还è¦é‡‡æ · RTT 的波动范围,这样就é¿å…如果 RTT 有一个大的波动的è¯,很难被å‘现的情况。

RFC6289 建议使用以下的公å¼è®¡ç®— RTO:

其中 SRTT 是计算平滑的RTT ,DevRTR 是计算平滑的RTT 与 最新 RTT 的差è·ã€‚

在 Linux 下,α = 0.125,β = 0.25, μ = 1,∂ = 4。别问怎么æ¥çš„,问就是大é‡å®žéªŒä¸­è°ƒå‡ºæ¥çš„。

如果超时é‡å‘的数æ®,冿¬¡è¶…时的时候,åˆéœ€è¦é‡ä¼ çš„æ—¶å€™,TCP 的策略是超时间隔加å€ã€‚

也就是æ¯å½“é‡åˆ°ä¸€æ¬¡è¶…æ—¶é‡ä¼ çš„æ—¶å€™,都会将下一次超时时间间隔设为先å‰å€¼çš„两å€ã€‚两次超时,就说明网络环境差,ä¸å®œé¢‘ç¹åå¤å‘é€ã€‚

超时触å‘é‡ä¼ å­˜åœ¨çš„问题是,超时周期å¯èƒ½ç›¸å¯¹è¾ƒé•¿ã€‚é‚£æ˜¯ä¸æ˜¯å¯ä»¥æœ‰æ›´å¿«çš„æ–¹å¼å‘¢?

于是就å¯ä»¥ç”¨ã€Œå¿«é€Ÿé‡ä¼ ã€æœºåˆ¶æ¥è§£å†³è¶…æ—¶é‡å‘的时间等待。

5.2 快速é‡ä¼ 

TCP 还有å¦å¤–一ç§å¿«é€Ÿé‡ä¼ (Fast Retransmit)机制,它ä¸ä»¥æ—¶é—´ä¸ºé©±åЍ,而是以数æ®é©±åЍé‡ä¼ ã€‚

快速é‡ä¼ æœºåˆ¶,是如何工作的呢?其实很简å•,一图胜åƒè¨€ã€‚

在上图,å‘逿–¹å‘出了 1,2,3,4,5 份数æ®:

  • 第一份 Seq1 å…ˆé€åˆ°äº†,于是就 Ack 回 2;
  • 结果 Seq2 因为æŸäº›åŽŸå› æ²¡æ”¶åˆ°,Seq3 到达了,于是还是 Ack 回 2;
  • åŽé¢çš„ Seq4 å’Œ Seq5 都到了,但还是 Ack 回 2,因为 Seq2 还是没有收到;
  • å‘é€ç«¯æ”¶åˆ°äº†ä¸‰ä¸ª Ack = 2 的确认,知é“了 Seq2 还没有收到,就会在定时器过期之å‰,é‡ä¼ ä¸¢å¤±çš„ Seq2。
  • 最åŽ,收到了 Seq2,此时因为 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6 。

所以,快速é‡ä¼ çš„å·¥ä½œæ–¹å¼æ˜¯å½“收到三个相åŒçš„ ACK 报文时,会在定时器过期之å‰,é‡ä¼ ä¸¢å¤±çš„æŠ¥æ–‡æ®µã€‚

快速é‡ä¼ æœºåˆ¶åªè§£å†³äº†ä¸€ä¸ªé—®é¢˜,就是超时时间的问题,但是它ä¾ç„¶é¢ä¸´ç€å¦å¤–一个问题。就是é‡ä¼ çš„æ—¶å€™,是é‡ä¼ ä¹‹å‰çš„一个,还是é‡ä¼ æ‰€æœ‰çš„问题。

比如对于上é¢çš„例å­,是é‡ä¼  Seq2 å‘¢?还是é‡ä¼  Seq2ã€Seq3ã€Seq4ã€Seq5 å‘¢?因为å‘é€ç«¯å¹¶ä¸æ¸…楚这连续的三个 Ack 2 是è°ä¼ å›žæ¥çš„。

æ ¹æ® TCP ä¸åŒçš„实现,ä»¥ä¸Šä¸¤ç§æƒ…况都是有å¯èƒ½çš„。å¯è§,这是一把åŒåˆƒå‰‘。

为了解决ä¸çŸ¥é“该é‡ä¼ å“ªäº› TCP 报文,于是就有 SACK 方法。

5.3 SACK 方法

还有一ç§å®žçްé‡ä¼ æœºåˆ¶çš„æ–¹å¼å«:SACK( Selective Acknowledgment 选择性确认)。

è¿™ç§æ–¹å¼éœ€è¦åœ¨ TCP 头部「选项ã€å­—段里加一个 SACK 的东西,它å¯ä»¥å°†ç¼“存的地图å‘é€ç»™å‘逿–¹,这样å‘逿–¹å°±å¯ä»¥çŸ¥é“å“ªäº›æ•°æ®æ”¶åˆ°äº†,å“ªäº›æ•°æ®æ²¡æ”¶åˆ°,知é“了这些信æ¯,å°±å¯ä»¥åªé‡ä¼ ä¸¢å¤±çš„æ•°æ®ã€‚

如下图,å‘逿–¹æ”¶åˆ°äº†ä¸‰æ¬¡åŒæ ·çš„ ACK 确认报文,于是就会触å‘快速é‡å‘机制,通过 SACK ä¿¡æ¯å‘çŽ°åªæœ‰ 200~299 这段数æ®ä¸¢å¤±,则é‡å‘æ—¶,å°±åªé€‰æ‹©äº†è¿™ä¸ª TCP 段进行é‡å¤ã€‚

å¦‚æžœè¦æ”¯æŒ SACK,å¿…é¡»åŒæ–¹éƒ½è¦æ”¯æŒã€‚在 Linux 下,å¯ä»¥é€šè¿‡ net.ipv4.tcp_sack 傿•°æ‰“开这个功能(Linux 2.4 åŽé»˜è®¤æ‰“å¼€)。

5.4 Duplicate SACK

Duplicate SACK åˆç§° D-SACK,其主è¦ä½¿ç”¨äº† SACK æ¥å‘Šè¯‰ã€Œå‘逿–¹ã€æœ‰å“ªäº›æ•°æ®è¢«é‡å¤æŽ¥æ”¶äº†ã€‚

下é¢ä¸¾ä¾‹ä¸¤ä¸ªæ —å­,æ¥è¯´æ˜Ž D-SACK 的作用。

æ —å­ä¸€å·:ACK 丢包

  • 「接收方ã€å‘给「å‘逿–¹ã€çš„两个 ACK 确认应答都丢失了,所以å‘逿–¹è¶…æ—¶åŽ,é‡ä¼ ç¬¬ä¸€ä¸ªæ•°æ®åŒ…(3000 ~ 3499)
  • 于是「接收方ã€å‘çŽ°æ•°æ®æ˜¯é‡å¤æ”¶åˆ°çš„,于是回了一个 SACK = 3000~3500,告诉「å‘逿–¹ã€ 3000~3500 çš„æ•°æ®æ—©å·²è¢«æŽ¥æ”¶äº†,因为 ACK 都到了 4000 了,å·²ç»æ„å‘³ç€ 4000 之å‰çš„æ‰€æœ‰æ•°æ®éƒ½å·²æ”¶åˆ°,所以这个 SACK å°±ä»£è¡¨ç€ D-SACK。
  • 这样「å‘逿–¹ã€å°±çŸ¥é“了,æ•°æ®æ²¡æœ‰ä¸¢,是「接收方ã€çš„ ACK 确认报文丢了。

æ —å­äºŒå·:网络延时

  • æ•°æ®åŒ…(1000~1499) 被网络延迟了,导致「å‘逿–¹ã€æ²¡æœ‰æ”¶åˆ° Ack 1500 的确认报文。
  • 而åŽé¢æŠ¥æ–‡åˆ°è¾¾çš„三个相åŒçš„ ACK 确认报文,就触å‘了快速é‡ä¼ æœºåˆ¶,但是在é‡ä¼ åŽ,被延迟的数æ®åŒ…(1000~1499)åˆåˆ°äº†ã€ŒæŽ¥æ”¶æ–¹ã€;
  • 所以「接收方ã€å›žäº†ä¸€ä¸ª SACK=1000~1500,因为 ACK å·²ç»åˆ°äº† 3000,所以这个 SACK 是 D-SACK,表示收到了é‡å¤çš„包。
  • 这样å‘逿–¹å°±çŸ¥é“快速é‡ä¼ è§¦å‘çš„åŽŸå› ä¸æ˜¯å‘出去的包丢了,ä¹Ÿä¸æ˜¯å› ä¸ºå›žåº”çš„ ACK 包丢了,而是因为网络延迟了。

å¯è§,D-SACK 有这么几个好处:

  1. å¯ä»¥è®©ã€Œå‘逿–¹ã€çŸ¥é“,是å‘出去的包丢了,还是接收方回应的 ACK 包丢了;
  2. å¯ä»¥çŸ¥é“æ˜¯ä¸æ˜¯ã€Œå‘逿–¹ã€çš„æ•°æ®åŒ…被网络延迟了;
  3. å¯ä»¥çŸ¥é“ç½‘ç»œä¸­æ˜¯ä¸æ˜¯æŠŠã€Œå‘逿–¹ã€çš„æ•°æ®åŒ…ç»™å¤åˆ¶äº†;

在 Linux 下å¯ä»¥é€šè¿‡ net.ipv4.tcp_dsack 傿•°å¼€å¯/关闭这个功能(Linux 2.4 åŽé»˜è®¤æ‰“å¼€)。


06 滑动窗å£

å¼•å…¥çª—å£æ¦‚念的原因

æˆ‘ä»¬éƒ½çŸ¥é“ TCP 是æ¯å‘é€ä¸€ä¸ªæ•°æ®,都è¦è¿›è¡Œä¸€æ¬¡ç¡®è®¤åº”答。当上一个数æ®åŒ…收到了应答了, å†å‘é€ä¸‹ä¸€ä¸ªã€‚

这个模å¼å°±æœ‰ç‚¹åƒæˆ‘和你é¢å¯¹é¢èŠå¤©,你䏀奿ˆ‘一å¥ã€‚ä½†è¿™ç§æ–¹å¼çš„缺点是效率比较低的。

如果你说完一å¥è¯,我在处ç†å…¶ä»–事情,æ²¡æœ‰åŠæ—¶å›žå¤ä½ ,那你䏿˜¯è¦å¹²ç­‰ç€æˆ‘åšå®Œå…¶ä»–事情åŽ,我回å¤ä½ ,ä½ æ‰èƒ½è¯´ä¸‹ä¸€å¥è¯,很显然这ä¸çŽ°å®žã€‚

所以,è¿™æ ·çš„ä¼ è¾“æ–¹å¼æœ‰ä¸€ä¸ªç¼ºç‚¹:æ•°æ®åŒ…的往返时间越长,通信的效率就越低。

为解决这个问题,TCP 引入了窗å£è¿™ä¸ªæ¦‚念。å³ä½¿åœ¨å¾€è¿”时间较长的情况下,它也ä¸ä¼šé™ä½Žç½‘络通信的效率。

那么有了窗å£,å°±å¯ä»¥æŒ‡å®šçª—å£å¤§å°,窗å£å¤§å°å°±æ˜¯æŒ‡æ— éœ€ç­‰å¾…确认应答,而å¯ä»¥ç»§ç»­å‘逿•°æ®çš„æœ€å¤§å€¼ã€‚

窗å£çš„实现实际上是æ“作系统开辟的一个缓存空间,å‘逿–¹ä¸»æœºåœ¨ç­‰åˆ°ç¡®è®¤åº”答返回之å‰,必须在缓冲区中ä¿ç•™å·²å‘é€çš„æ•°æ®ã€‚如果按期收到确认应答,此时数æ®å°±å¯ä»¥ä»Žç¼“存区清除。

å‡è®¾çª—å£å¤§å°ä¸º 3 个 TCP 段,那么å‘逿–¹å°±å¯ä»¥ã€Œè¿žç»­å‘é€ã€ 3 个 TCP 段,并且中途若有 ACK 丢失,å¯ä»¥é€šè¿‡ã€Œä¸‹ä¸€ä¸ªç¡®è®¤åº”答进行确认ã€ã€‚如下图:

图中的 ACK 600 确认应答报文丢失,也没关系,因为å¯ä»¥é€šè¿‡ä¸‹ä¸€ä¸ªç¡®è®¤åº”答进行确认,åªè¦å‘逿–¹æ”¶åˆ°äº† ACK 700 确认应答,å°±æ„å‘³ç€ 700 之å‰çš„æ‰€æœ‰æ•°æ®ã€ŒæŽ¥æ”¶æ–¹ã€éƒ½æ”¶åˆ°äº†ã€‚这个模å¼å°±å«ç´¯è®¡ç¡®è®¤æˆ–者累计应答。

窗å£å¤§å°ç”±å“ªä¸€æ–¹å†³å®š?

TCP å¤´é‡Œæœ‰ä¸€ä¸ªå­—æ®µå« Window,也就是窗å£å¤§å°ã€‚

这个字段是接收端告诉å‘é€ç«¯è‡ªå·±è¿˜æœ‰å¤šå°‘缓冲区å¯ä»¥æŽ¥æ”¶æ•°æ®ã€‚于是å‘é€ç«¯å°±å¯ä»¥æ ¹æ®è¿™ä¸ªæŽ¥æ”¶ç«¯çš„处ç†èƒ½åŠ›æ¥å‘逿•°æ®,而ä¸ä¼šå¯¼è‡´æŽ¥æ”¶ç«¯å¤„ç†ä¸è¿‡æ¥ã€‚

所以,通常窗å£çš„大尿˜¯ç”±æŽ¥æ”¶æ–¹çš„窗å£å¤§å°æ¥å†³å®šçš„。

å‘逿–¹å‘é€çš„æ•°æ®å¤§å°ä¸èƒ½è¶…过接收方的窗å£å¤§å°,å¦åˆ™æŽ¥æ”¶æ–¹å°±æ— æ³•正常接收到数æ®ã€‚

å‘逿–¹çš„æ»‘动窗å£

我们先æ¥çœ‹çœ‹å‘逿–¹çš„窗å£,下图就是å‘逿–¹ç¼“存的数æ®,æ ¹æ®å¤„ç†çš„æƒ…况分æˆå››ä¸ªéƒ¨åˆ†,其中深è“色方框是å‘é€çª—å£,紫色方框是å¯ç”¨çª—å£:

  • #1 是已å‘é€å¹¶æ”¶åˆ° ACK确认的数æ®:1~31 字节
  • #2 是已å‘é€ä½†æœªæ”¶åˆ° ACK确认的数æ®:32~45 字节
  • #3 是未å‘é€ä½†æ€»å¤§å°åœ¨æŽ¥æ”¶æ–¹å¤„ç†èŒƒå›´å†…(接收方还有空间):46~51字节
  • #4 是未å‘é€ä½†æ€»å¤§å°è¶…过接收方处ç†èŒƒå›´(接收方没有空间):52字节以åŽ

在下图,当å‘逿–¹æŠŠæ•°æ®ã€Œå…¨éƒ¨ã€éƒ½ä¸€ä¸‹å‘é€å‡ºåŽ»åŽ,å¯ç”¨çª—å£çš„大å°å°±ä¸º 0 了,表明å¯ç”¨çª—å£è€—å°½,在没收到 ACK ç¡®è®¤ä¹‹å‰æ˜¯æ— æ³•ç»§ç»­å‘逿•°æ®äº†ã€‚

在下图,当收到之å‰å‘é€çš„æ•°æ® 32~36 字节的 ACK 确认应答åŽ,如果å‘é€çª—å£çš„大尿²¡æœ‰å˜åŒ–,则滑动窗å£å¾€å³è¾¹ç§»åЍ 5 个字节,因为有 5 个字节的数æ®è¢«åº”答确认,æŽ¥ä¸‹æ¥ 52~56 字节åˆå˜æˆäº†å¯ç”¨çª—å£,那么åŽç»­ä¹Ÿå°±å¯ä»¥å‘é€ 52~56 è¿™ 5 个字节的数æ®äº†ã€‚

ç¨‹åºæ˜¯å¦‚何表示å‘逿–¹çš„四个部分的呢?

TCP æ»‘åŠ¨çª—å£æ–¹æ¡ˆä½¿ç”¨ä¸‰ä¸ªæŒ‡é’ˆæ¥è·Ÿè¸ªåœ¨å››ä¸ªä¼ è¾“类别中的æ¯ä¸€ä¸ªç±»åˆ«ä¸­çš„字节。其中两个指针是ç»å¯¹æŒ‡é’ˆ(指特定的åºåˆ—å·),一个是相对指针(需è¦åšåç§»)。

  • SND.WND:表示å‘é€çª—å£çš„大å°(大尿˜¯ç”±æŽ¥æ”¶æ–¹æŒ‡å®šçš„);

  • SND.UNA:是一个ç»å¯¹æŒ‡é’ˆ,它指å‘的是已å‘é€ä½†æœªæ”¶åˆ°ç¡®è®¤çš„第一个字节的åºåˆ—å·,也就是 #2 的第一个字节。

  • SND.NXT:也是一个ç»å¯¹æŒ‡é’ˆ,å®ƒæŒ‡å‘æœªå‘é€ä½†å¯å‘é€èŒƒå›´çš„第一个字节的åºåˆ—å·,也就是 #3 的第一个字节。

  • æŒ‡å‘ #4 的第一个字节是个相对指针,å®ƒéœ€è¦ SND.UNA 指针加上 SND.WND 大å°çš„åç§»é‡,å°±å¯ä»¥æŒ‡å‘ #4 的第一个字节了。

那么å¯ç”¨çª—å£å¤§å°çš„计算就å¯ä»¥æ˜¯:

å¯ç”¨çª—å£å¤§ = SND.WND -(SND.NXT - SND.UNA)

接收方的滑动窗å£

æŽ¥ä¸‹æ¥æˆ‘们看看接收方的窗å£,接收窗å£ç›¸å¯¹ç®€å•一些,æ ¹æ®å¤„ç†çš„æƒ…况划分æˆä¸‰ä¸ªéƒ¨åˆ†:

  • #1 + #2 是已æˆåŠŸæŽ¥æ”¶å¹¶ç¡®è®¤çš„æ•°æ®(等待应用进程读å–);
  • #3 是未收到数æ®ä½†å¯ä»¥æŽ¥æ”¶çš„æ•°æ®;
  • #4 未收到数æ®å¹¶ä¸å¯ä»¥æŽ¥æ”¶çš„æ•°æ®;

其中三个接收部分,使用两个指针进行划分:

  • RCV.WND:表示接收窗å£çš„大å°,它会通告给å‘逿–¹ã€‚
  • RCV.NXT:是一个指针,å®ƒæŒ‡å‘æœŸæœ›ä»Žå‘逿–¹å‘逿¥çš„下一个数æ®å­—节的åºåˆ—å·,也就是 #3 的第一个字节。
  • æŒ‡å‘ #4 的第一个字节是个相对指针,å®ƒéœ€è¦ RCV.NXT 指针加上 RCV.WND 大å°çš„åç§»é‡,å°±å¯ä»¥æŒ‡å‘ #4 的第一个字节了。

接收窗å£å’Œå‘é€çª—å£çš„大尿˜¯ç›¸ç­‰çš„å—?

并䏿˜¯å®Œå…¨ç›¸ç­‰,接收窗å£çš„大尿˜¯çº¦ç­‰äºŽå‘é€çª—å£çš„大å°çš„。

因为滑动窗å£å¹¶ä¸æ˜¯ä¸€æˆä¸å˜çš„。比如,å½“æŽ¥æ”¶æ–¹çš„åº”ç”¨è¿›ç¨‹è¯»å–æ•°æ®çš„速度éžå¸¸å¿«çš„è¯,è¿™æ ·çš„è¯æŽ¥æ”¶çª—å£å¯ä»¥å¾ˆå¿«çš„就空缺出æ¥ã€‚那么新的接收窗å£å¤§å°,是通过 TCP 报文中的 Windows 字段æ¥å‘Šè¯‰å‘逿–¹ã€‚那么这个传输过程是存在时延的,所以接收窗å£å’Œå‘é€çª—壿˜¯çº¦ç­‰äºŽçš„关系。


07 æµé‡æŽ§åˆ¶

å‘逿–¹ä¸èƒ½æ— è„‘çš„å‘æ•°æ®ç»™æŽ¥æ”¶æ–¹,è¦è€ƒè™‘接收方处ç†èƒ½åŠ›ã€‚

å¦‚æžœä¸€ç›´æ— è„‘çš„å‘æ•°æ®ç»™å¯¹æ–¹,但对方处ç†ä¸è¿‡æ¥,那么就会导致触å‘é‡å‘机制,从而导致网络æµé‡çš„æ— ç«¯çš„æµªè´¹ã€‚

为了解决这ç§çŽ°è±¡å‘生,TCP æä¾›ä¸€ç§æœºåˆ¶å¯ä»¥è®©ã€Œå‘逿–¹ã€æ ¹æ®ã€ŒæŽ¥æ”¶æ–¹ã€çš„实际接收能力控制å‘é€çš„æ•°æ®é‡,这就是所谓的æµé‡æŽ§åˆ¶ã€‚

下é¢ä¸¾ä¸ªæ —å­,为了简å•èµ·è§,å‡è®¾ä»¥ä¸‹åœºæ™¯:

  • 客户端是接收方,æœåŠ¡ç«¯æ˜¯å‘逿–¹
  • å‡è®¾æŽ¥æ”¶çª—å£å’Œå‘é€çª—å£ç›¸åŒ,都为 200
  • å‡è®¾ä¸¤ä¸ªè®¾å¤‡åœ¨æ•´ä¸ªä¼ è¾“è¿‡ç¨‹ä¸­éƒ½ä¿æŒç›¸åŒçš„窗å£å¤§å°,ä¸å—外界影å“

æ ¹æ®ä¸Šå›¾çš„æµé‡æŽ§åˆ¶,说明下æ¯ä¸ªè¿‡ç¨‹:

  1. å®¢æˆ·ç«¯å‘æœåŠ¡ç«¯å‘é€è¯·æ±‚æ•°æ®æŠ¥æ–‡ã€‚è¿™é‡Œè¦è¯´æ˜Žä¸‹,æœ¬æ¬¡ä¾‹å­æ˜¯æŠŠæœåŠ¡ç«¯ä½œä¸ºå‘逿–¹,所以没有画出æœåŠ¡ç«¯çš„æŽ¥æ”¶çª—å£ã€‚
  2. æœåŠ¡ç«¯æ”¶åˆ°è¯·æ±‚æŠ¥æ–‡åŽ,å‘é€ç¡®è®¤æŠ¥æ–‡å’Œ 80 字节的数æ®,于是å¯ç”¨çª—å£ Usable å‡å°‘为 120 字节,åŒæ—¶ SND.NXT 指针也å‘å³åç§» 80 字节åŽ,æŒ‡å‘ 321,è¿™æ„味ç€ä¸‹æ¬¡å‘逿•°æ®çš„æ—¶å€™,åºåˆ—å·æ˜¯ 321。
  3. 客户端收到 80 字节数æ®åŽ,于是接收窗å£å¾€å³ç§»åЍ 80 字节,RCV.NXT ä¹Ÿå°±æŒ‡å‘ 321,è¿™æ„味ç€å®¢æˆ·ç«¯æœŸæœ›çš„下一个报文的åºåˆ—å·æ˜¯ 321,接ç€å‘é€ç¡®è®¤æŠ¥æ–‡ç»™æœåŠ¡ç«¯ã€‚
  4. æœåŠ¡ç«¯å†æ¬¡å‘é€äº† 120 字节数æ®,于是å¯ç”¨çª—å£è€—尽为 0,æœåŠ¡ç«¯æ— æ³•å†ç»§ç»­å‘逿•°æ®ã€‚
  5. 客户端收到 120 字节的数æ®åŽ,于是接收窗å£å¾€å³ç§»åЍ 120 字节,RCV.NXT ä¹Ÿå°±æŒ‡å‘ 441,接ç€å‘é€ç¡®è®¤æŠ¥æ–‡ç»™æœåŠ¡ç«¯ã€‚
  6. æœåŠ¡ç«¯æ”¶åˆ°å¯¹ 80 字节数æ®çš„确认报文åŽ,SND.UNA 指针往å³åç§»åŽæŒ‡å‘ 321,于是å¯ç”¨çª—å£ Usable 增大到 80。
  7. æœåŠ¡ç«¯æ”¶åˆ°å¯¹ 120 字节数æ®çš„确认报文åŽ,SND.UNA 指针往å³åç§»åŽæŒ‡å‘ 441,于是å¯ç”¨çª—å£ Usable 增大到 200。
  8. æœåŠ¡ç«¯å¯ä»¥ç»§ç»­å‘é€äº†,于是å‘é€äº† 160 字节的数æ®åŽ,SND.NXT æŒ‡å‘ 601,于是å¯ç”¨çª—å£ Usable å‡å°‘到 40。
  9. 客户端收到 160 字节åŽ,接收窗å£å¾€å³ç§»åŠ¨äº† 160 字节,RCV.NXT 也就是指å‘了 601,接ç€å‘é€ç¡®è®¤æŠ¥æ–‡ç»™æœåŠ¡ç«¯ã€‚
  10. æœåŠ¡ç«¯æ”¶åˆ°å¯¹ 160 字节数æ®çš„确认报文åŽ,å‘é€çª—å£å¾€å³ç§»åŠ¨äº† 160 字节,于是 SND.UNA 指针å移了 160 åŽæŒ‡å‘ 601,å¯ç”¨çª—å£ Usable 也就增大至了 200。

7.1 æ“作系统缓冲区与滑动窗å£çš„关系

å‰é¢çš„æµé‡æŽ§åˆ¶ä¾‹å­,我们å‡å®šäº†å‘é€çª—å£å’ŒæŽ¥æ”¶çª—壿˜¯ä¸å˜çš„,但是实际上,å‘é€çª—å£å’ŒæŽ¥æ”¶çª—å£ä¸­æ‰€å­˜æ”¾çš„字节数,都是放在æ“作系统内存缓冲区中的,而æ“作系统的缓冲区,会被æ“作系统调整。

å½“åº”ç”¨è¿›ç¨‹æ²¡åŠžæ³•åŠæ—¶è¯»å–缓冲区的内容时,也会对我们的缓冲区造æˆå½±å“。

é‚£æ“心系统的缓冲区,是如何影å“å‘é€çª—å£å’ŒæŽ¥æ”¶çª—å£çš„å‘¢?

我们先æ¥çœ‹çœ‹ç¬¬ä¸€ä¸ªä¾‹å­ã€‚

å½“åº”ç”¨ç¨‹åºæ²¡æœ‰åŠæ—¶è¯»å–缓存时,å‘é€çª—å£å’ŒæŽ¥æ”¶çª—å£çš„å˜åŒ–。

考虑以下场景:

  • 客户端作为å‘逿–¹,æœåŠ¡ç«¯ä½œä¸ºæŽ¥æ”¶æ–¹,å‘é€çª—å£å’ŒæŽ¥æ”¶çª—å£åˆå§‹å¤§å°ä¸º 360
  • æœåŠ¡ç«¯éžå¸¸çš„ç¹å¿™,å½“æ”¶åˆ°å®¢æˆ·ç«¯çš„æ•°æ®æ—¶,应用层ä¸èƒ½åŠæ—¶è¯»å–æ•°æ®ã€‚

æ ¹æ®ä¸Šå›¾çš„æµé‡æŽ§åˆ¶,说明下æ¯ä¸ªè¿‡ç¨‹:

  1. 客户端å‘é€ 140 字节数æ®åŽ,å¯ç”¨çª—å£å˜ä¸º 220 (360 - 140)。
  2. æœåŠ¡ç«¯æ”¶åˆ° 140 字节数æ®,但是æœåŠ¡ç«¯éžå¸¸ç¹å¿™,应用进程åªè¯»å–了 40 个字节,还有 100 字节å ç”¨ç€ç¼“冲区,äºŽæ˜¯æŽ¥æ”¶çª—å£æ”¶ç¼©åˆ°äº† 260 (360 - 100),最åŽå‘é€ç¡®è®¤ä¿¡æ¯æ—¶,将窗å£å¤§å°é€šå‘Šç»™å®¢æˆ·ç«¯ã€‚
  3. 客户端收到确认和窗å£é€šå‘ŠæŠ¥æ–‡åŽ,å‘é€çª—å£å‡å°‘为 260。
  4. 客户端å‘é€ 180 字节数æ®,此时å¯ç”¨çª—å£å‡å°‘到 80。
  5. æœåŠ¡ç«¯æ”¶åˆ° 180 字节数æ®,ä½†æ˜¯åº”ç”¨ç¨‹åºæ²¡æœ‰è¯»å–任何数æ®,è¿™ 180 字节直接就留在了缓冲区,äºŽæ˜¯æŽ¥æ”¶çª—å£æ”¶ç¼©åˆ°äº† 80 (260 - 180),并在å‘é€ç¡®è®¤ä¿¡æ¯æ—¶,通过窗å£å¤§å°ç»™å®¢æˆ·ç«¯ã€‚
  6. 客户端收到确认和窗å£é€šå‘ŠæŠ¥æ–‡åŽ,å‘é€çª—å£å‡å°‘为 80。
  7. 客户端å‘é€ 80 字节数æ®åŽ,å¯ç”¨çª—å£è€—尽。
  8. æœåŠ¡ç«¯æ”¶åˆ° 80 字节数æ®,但是应用程åºä¾ç„¶æ²¡æœ‰è¯»å–任何数æ®,è¿™ 80 字节留在了缓冲区,äºŽæ˜¯æŽ¥æ”¶çª—å£æ”¶ç¼©åˆ°äº† 0,并在å‘é€ç¡®è®¤ä¿¡æ¯æ—¶,通过窗å£å¤§å°ç»™å®¢æˆ·ç«¯ã€‚
  9. 客户端收到确认和窗å£é€šå‘ŠæŠ¥æ–‡åŽ,å‘é€çª—å£å‡å°‘为 0。

å¯è§æœ€åŽçª—å£éƒ½æ”¶ç¼©ä¸º 0 了,也就是å‘生了窗å£å…³é—­ã€‚当å‘逿–¹å¯ç”¨çª—å£å˜ä¸º 0 æ—¶,å‘逿–¹å®žé™…上会定时å‘é€çª—å£æŽ¢æµ‹æŠ¥æ–‡,ä»¥ä¾¿çŸ¥é“æŽ¥æ”¶æ–¹çš„çª—å£æ˜¯å¦å‘生了改å˜,这个内容åŽé¢ä¼šè¯´,è¿™é‡Œå…ˆç®€å•æä¸€ä¸‹ã€‚

我们先æ¥çœ‹çœ‹ç¬¬äºŒä¸ªä¾‹å­ã€‚

当æœåŠ¡ç«¯ç³»ç»Ÿèµ„æºéžå¸¸ç´§å¼ çš„æ—¶å€™,æ“心系统å¯èƒ½ä¼šç›´æŽ¥å‡å°‘了接收缓冲区大å°,这时应用程åºåˆæ— æ³•åŠæ—¶è¯»å–缓存数æ®,那么这时候就有严é‡çš„事情å‘生了,会出现数æ®åŒ…丢失的现象。

说明下æ¯ä¸ªè¿‡ç¨‹:

  1. 客户端å‘é€ 140 字节的数æ®,于是å¯ç”¨çª—å£å‡å°‘到了 220。
  2. æœåŠ¡ç«¯å› ä¸ºçŽ°åœ¨éžå¸¸çš„ç¹å¿™,æ“作系统于是就把接收缓存å‡å°‘了 120 字节,当收到 140 字节数æ®åŽ,åˆå› ä¸ºåº”ç”¨ç¨‹åºæ²¡æœ‰è¯»å–任何数æ®,所以 140 字节留在了缓冲区中,于是接收窗å£å¤§å°ä»Ž 360 收缩æˆäº† 100,最åŽå‘é€ç¡®è®¤ä¿¡æ¯æ—¶,通告窗å£å¤§å°ç»™å¯¹æ–¹ã€‚
  3. 此时客户端因为还没有收到æœåŠ¡ç«¯çš„é€šå‘Šçª—å£æŠ¥æ–‡,所以ä¸çŸ¥é“æ­¤æ—¶æŽ¥æ”¶çª—å£æ”¶ç¼©æˆäº† 100,客户端åªä¼šçœ‹è‡ªå·±çš„å¯ç”¨çª—å£è¿˜æœ‰ 220,所以客户端就å‘é€äº† 180 字节数æ®,于是å¯ç”¨çª—å£å‡å°‘到 40。
  4. æœåŠ¡ç«¯æ”¶åˆ°äº† 180 å­—èŠ‚æ•°æ®æ—¶,å‘现数æ®å¤§å°è¶…过了接收窗å£çš„大å°,于是就把数æ®åŒ…丢失了。
  5. 客户端收到第 2 步时,æœåŠ¡ç«¯å‘é€çš„ç¡®è®¤æŠ¥æ–‡å’Œé€šå‘Šçª—å£æŠ¥æ–‡,å°è¯•å‡å°‘å‘é€çª—å£åˆ° 100,把窗å£çš„å³ç«¯å‘左收缩了 80,此时å¯ç”¨çª—å£çš„大å°å°±ä¼šå‡ºçŽ°è¯¡å¼‚çš„è´Ÿå€¼ã€‚

所以,如果å‘生了先å‡å°‘缓存,冿”¶ç¼©çª—å£,就会出现丢包的现象。

ä¸ºäº†é˜²æ­¢è¿™ç§æƒ…况å‘生,TCP 规定是ä¸å…è®¸åŒæ—¶å‡å°‘ç¼“å­˜åˆæ”¶ç¼©çª—å£çš„,而是采用先收缩窗å£,过段时间å†å‡å°‘缓存,这样就å¯ä»¥é¿å…了丢包情况。

7.2 窗å£å…³é—­

在å‰é¢æˆ‘们都看到了,TCP 通过让接收方指明希望从å‘逿–¹æŽ¥æ”¶çš„æ•°æ®å¤§å°(窗å£å¤§å°)æ¥è¿›è¡Œæµé‡æŽ§åˆ¶ã€‚

如果窗å£å¤§å°ä¸º 0 æ—¶,就会阻止å‘逿–¹ç»™æŽ¥æ”¶æ–¹ä¼ é€’æ•°æ®,直到窗å£å˜ä¸ºéž 0 为止,这就是窗å£å…³é—­ã€‚

窗å£å…³é—­æ½œåœ¨çš„å±é™©

接收方å‘å‘逿–¹é€šå‘Šçª—å£å¤§å°æ—¶,是通过 ACK 报文æ¥é€šå‘Šçš„。

那么,当å‘生窗å£å…³é—­æ—¶,接收方处ç†å®Œæ•°æ®åŽ,会å‘å‘逿–¹é€šå‘Šä¸€ä¸ªçª—å£éž 0 çš„ ACK 报文,如果这个通告窗å£çš„ ACK 报文在网络中丢失了,那麻烦就大了。

这会导致å‘逿–¹ä¸€ç›´ç­‰å¾…æŽ¥æ”¶æ–¹çš„éž 0 窗å£é€šçŸ¥,接收方也一直等待å‘逿–¹çš„æ•°æ®,如ä¸é‡‡å–措施,è¿™ç§ç›¸äº’等待的过程,会造æˆäº†æ­»é”的现象。

TCP 是如何解决窗å£å…³é—­æ—¶,潜在的死é”现象呢?

为了解决这个问题,TCP 为æ¯ä¸ªè¿žæŽ¥è®¾æœ‰ä¸€ä¸ªæŒç»­å®šæ—¶å™¨,åªè¦ TCP 连接一方收到对方的零窗å£é€šçŸ¥,å°±å¯åЍæŒç»­è®¡æ—¶å™¨ã€‚

如果æŒç»­è®¡æ—¶å™¨è¶…æ—¶,就会å‘é€çª—å£æŽ¢æµ‹ ( Window
probe ) 报文
,而对方在确认这个探测报文时,给出自己现在的接收窗å£å¤§å°ã€‚

  • 如果接收窗å£ä»ç„¶ä¸º 0,é‚£ä¹ˆæ”¶åˆ°è¿™ä¸ªæŠ¥æ–‡çš„ä¸€æ–¹å°±ä¼šé‡æ–°å¯åЍæŒç»­è®¡æ—¶å™¨;
  • 如果接收窗å£ä¸æ˜¯ 0,那么死é”的局é¢å°±å¯ä»¥è¢«æ‰“破了。

çª—å£æŽ¢æµ‹çš„æ¬¡æ•°ä¸€èˆ¬ä¸º 3 次,æ¯æ¬¡å¤§çº¦ 30-60 ç§’(ä¸åŒçš„实现å¯èƒ½ä¼šä¸ä¸€æ ·)。如果 3 æ¬¡è¿‡åŽæŽ¥æ”¶çª—å£è¿˜æ˜¯ 0 çš„è¯,有的 TCP å®žçŽ°å°±ä¼šå‘ RST 报文æ¥ä¸­æ–­è¿žæŽ¥ã€‚

7.3 糊涂窗å£ç»¼åˆç—‡

如果接收方太忙了,æ¥ä¸åŠå–走接收窗å£é‡Œçš„æ•°æ®,那么就会导致å‘逿–¹çš„å‘é€çª—å£è¶Šæ¥è¶Šå°ã€‚

到最åŽ,如果接收方腾出几个字节并告诉å‘逿–¹çŽ°åœ¨æœ‰å‡ ä¸ªå­—èŠ‚çš„çª—å£,而å‘逿–¹ä¼šä¹‰æ— å顾地å‘é€è¿™å‡ ä¸ªå­—节,这就是糊涂窗å£ç»¼åˆç—‡ã€‚

è¦çŸ¥é“,我们的 TCP + IP 头有 40 个字节,为了传输那几个字节的数æ®,è¦è¾¾ä¸Šè¿™ä¹ˆå¤§çš„开销,这太ä¸ç»æµŽäº†ã€‚

就好åƒä¸€ä¸ªå¯ä»¥æ‰¿è½½ 50 人的大巴车,æ¯æ¬¡æ¥äº†ä¸€ä¸¤ä¸ªäºº,就直接å‘车。除éžå®¶é‡Œæœ‰çŸ¿çš„大巴叿œº,æ‰æ•¢è¿™æ ·çŽ©,ä¸ç„¶è¿Ÿæ—©ç ´äº§ã€‚è¦è§£å†³è¿™ä¸ªé—®é¢˜ä¹Ÿä¸éš¾,大巴叿œºç­‰ä¹˜å®¢æ•°é‡è¶…过了 25 个,æ‰è®¤å®šå¯ä»¥å‘车。

现举个糊涂窗å£ç»¼åˆç—‡çš„æ —å­,考虑以下场景:

接收方的窗å£å¤§å°æ˜¯ 360 字节,但接收方由于æŸäº›åŽŸå› é™·å…¥å›°å¢ƒ,å‡è®¾æŽ¥æ”¶æ–¹çš„应用层读å–的能力如下:

  • æŽ¥æ”¶æ–¹æ¯æŽ¥æ”¶ 3 个字节,应用程åºå°±åªèƒ½ä»Žç¼“å†²åŒºä¸­è¯»å– 1 个字节的数æ®;
  • 在下一个å‘逿–¹çš„ TCP 段到达之å‰,应用程åºè¿˜ä»Žç¼“冲区中读å–了 40 个é¢å¤–的字节;

æ¯ä¸ªè¿‡ç¨‹çš„窗å£å¤§å°çš„å˜åŒ–,在图中都æè¿°çš„很清楚了,å¯ä»¥å‘现窗å£ä¸æ–­å‡å°‘了,并且å‘é€çš„æ•°æ®éƒ½æ˜¯æ¯”较å°çš„了。

所以,糊涂窗å£ç»¼åˆç—‡çš„现象是å¯ä»¥å‘生在å‘逿–¹å’ŒæŽ¥æ”¶æ–¹:

  • 接收方å¯ä»¥é€šå‘Šä¸€ä¸ªå°çš„窗å£
  • 而å‘逿–¹å¯ä»¥å‘é€å°æ•°æ®

于是,è¦è§£å†³ç³Šæ¶‚窗å£ç»¼åˆç—‡,就解决上é¢ä¸¤ä¸ªé—®é¢˜å°±å¯ä»¥äº†

  • 让接收方ä¸é€šå‘Šå°çª—å£ç»™å‘逿–¹
  • 让å‘逿–¹é¿å…å‘é€å°æ•°æ®

怎么让接收方ä¸é€šå‘Šå°çª—å£å‘¢?

接收方通常的策略如下:

当「窗å£å¤§å°ã€å°äºŽ min( MSS,缓存空间/2 ) ,也就是å°äºŽ MSS 与 1/2 缓存大å°ä¸­çš„æœ€å°å€¼æ—¶,就会å‘å‘逿–¹é€šå‘Šçª—å£ä¸º 0,也就阻止了å‘逿–¹å†å‘æ•°æ®è¿‡æ¥ã€‚

等到接收方处ç†äº†ä¸€äº›æ•°æ®åŽ,窗å£å¤§å° >= MSS,或者接收方缓存空间有一åŠå¯ä»¥ä½¿ç”¨,å°±å¯ä»¥æŠŠçª—壿‰“开让å‘逿–¹å‘逿•°æ®è¿‡æ¥ã€‚

怎么让å‘逿–¹é¿å…å‘é€å°æ•°æ®å‘¢?

å‘逿–¹é€šå¸¸çš„ç­–ç•¥:

使用 Nagle 算法,该算法的æ€è·¯æ˜¯å»¶æ—¶å¤„ç†,它满足以下两个æ¡ä»¶ä¸­çš„ä¸€æ¡æ‰å¯ä»¥å‘逿•°æ®:

  • è¦ç­‰åˆ°çª—å£å¤§å° >= MSS 或是 æ•°æ®å¤§å° >= MSS
  • 收到之å‰å‘逿•°æ®çš„ ack 回包

åªè¦æ²¡æ»¡è¶³ä¸Šé¢æ¡ä»¶ä¸­çš„一æ¡,å‘逿–¹ä¸€ç›´åœ¨å›¤ç§¯æ•°æ®,直到满足上é¢çš„å‘逿¡ä»¶ã€‚

å¦å¤–,Nagle 算法默认是打开的,如果对于一些需è¦å°æ•°æ®åŒ…交互的场景的程åº,比如,telnet 或 ssh 这样的交互性比较强的程åº,则需è¦å…³é—­ Nagle 算法。

å¯ä»¥åœ¨ Socket 设置 TCP_NODELAY 选项æ¥å…³é—­è¿™ä¸ªç®—法(关闭 Nagle ç®—æ³•æ²¡æœ‰å…¨å±€å‚æ•°,éœ€è¦æ ¹æ®æ¯ä¸ªåº”用自己的特点æ¥å…³é—­)

setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&value, sizeof(int));

08 拥塞控制

ä¸ºä»€ä¹ˆè¦æœ‰æ‹¥å¡žæŽ§åˆ¶å‘€,䏿˜¯æœ‰æµé‡æŽ§åˆ¶äº†å—?

å‰é¢çš„æµé‡æŽ§åˆ¶æ˜¯é¿å…「å‘逿–¹ã€çš„æ•°æ®å¡«æ»¡ã€ŒæŽ¥æ”¶æ–¹ã€çš„缓存,但是并ä¸çŸ¥é“网络的中å‘生了什么。

一般æ¥è¯´,计算机网络都处在一个共享的环境。因此也有å¯èƒ½ä¼šå› ä¸ºå…¶ä»–主机之间的通信使得网络拥堵。

在网络出现拥堵时,如果继续å‘é€å¤§é‡æ•°æ®åŒ…,å¯èƒ½ä¼šå¯¼è‡´æ•°æ®åŒ…æ—¶å»¶ã€ä¸¢å¤±ç­‰,这时 TCP 就会é‡ä¼ æ•°æ®,但是一é‡ä¼ å°±ä¼šå¯¼è‡´ç½‘络的负担更é‡,äºŽæ˜¯ä¼šå¯¼è‡´æ›´å¤§çš„å»¶è¿Ÿä»¥åŠæ›´å¤šçš„丢包,è¿™ä¸ªæƒ…å†µå°±ä¼šè¿›å…¥æ¶æ€§å¾ªçŽ¯è¢«ä¸æ–­åœ°æ”¾å¤§â€¦

所以,TCP ä¸èƒ½å¿½ç•¥ç½‘络上å‘生的事,它被设计æˆä¸€ä¸ªæ— ç§çš„åè®®,当网络å‘逿‹¥å¡žæ—¶,TCP 会自我牺牲,é™ä½Žå‘é€çš„æ•°æ®é‡ã€‚

于是,就有了拥塞控制,控制的目的就是é¿å…「å‘逿–¹ã€çš„æ•°æ®å¡«æ»¡æ•´ä¸ªç½‘络。

为了在「å‘逿–¹ã€è°ƒèŠ‚æ‰€è¦å‘逿•°æ®çš„é‡,定义了一个å«åšã€Œæ‹¥å¡žçª—å£ã€çš„æ¦‚念。

什么是拥塞窗å£?å’Œå‘é€çª—壿œ‰ä»€ä¹ˆå…³ç³»å‘¢?

æ‹¥å¡žçª—å£ cwnd是å‘逿–¹ç»´æŠ¤çš„一个的状æ€å˜é‡,它会根æ®ç½‘络的拥塞程度动æ€å˜åŒ–的。

我们在å‰é¢æåˆ°è¿‡å‘é€çª—å£ swnd å’ŒæŽ¥æ”¶çª—å£ rwnd 是约等于的关系,那么由于加入了拥塞窗å£çš„æ¦‚念åŽ,此时å‘é€çª—å£çš„值是swnd = min(cwnd, rwnd),也就是拥塞窗å£å’ŒæŽ¥æ”¶çª—å£ä¸­çš„æœ€å°å€¼ã€‚

æ‹¥å¡žçª—å£ cwnd å˜åŒ–的规则:

  • åªè¦ç½‘络中没有出现拥塞,cwnd 就会增大;
  • 但网络中出现了拥塞,cwnd å°±å‡å°‘;

那么怎么知é“当å‰ç½‘络是å¦å‡ºçŽ°äº†æ‹¥å¡žå‘¢?

其实åªè¦ã€Œå‘逿–¹ã€æ²¡æœ‰åœ¨è§„定时间内接收到 ACK 应答报文,也就是å‘生了超时é‡ä¼ ,就会认为网络出现了用拥塞。

拥塞控制有哪些控制算法?

æ‹¥å¡žæŽ§åˆ¶ä¸»è¦æ˜¯å››ä¸ªç®—法:

  • æ…¢å¯åЍ
  • 拥塞é¿å…
  • 拥塞å‘生
  • 快速æ¢å¤

8.1 æ…¢å¯åЍ

TCP 在刚建立连接完æˆåŽ,首先是有个慢å¯åŠ¨çš„è¿‡ç¨‹,这个慢å¯åŠ¨çš„æ„æ€å°±æ˜¯ä¸€ç‚¹ä¸€ç‚¹çš„æé«˜å‘逿•°æ®åŒ…的数é‡,如果一上æ¥å°±å‘大é‡çš„æ•°æ®,è¿™ä¸æ˜¯ç»™ç½‘络添堵å—?

æ…¢å¯åŠ¨çš„ç®—æ³•è®°ä½ä¸€ä¸ªè§„则就行:当å‘逿–¹æ¯æ”¶åˆ°ä¸€ä¸ª ACK,æ‹¥å¡žçª—å£ cwnd 的大å°å°±ä¼šåŠ  1。

这里å‡å®šæ‹¥å¡žçª—å£ cwnd å’Œå‘é€çª—å£ swnd 相等,下é¢ä¸¾ä¸ªæ —å­:

  • 连接建立完æˆåŽ,一开始åˆå§‹åŒ– cwnd = 1,表示å¯ä»¥ä¼ ä¸€ä¸ª MSS 大å°çš„æ•°æ®ã€‚
  • 当收到一个 ACK 确认应答åŽ,cwnd 增加 1,于是一次能够å‘é€ 2 个
  • 当收到 2 个的 ACK 确认应答åŽ, cwnd 增加 2,于是就å¯ä»¥æ¯”之å‰å¤šå‘2 个,所以这一次能够å‘é€ 4 个
  • 当这 4 个的 ACK 确认到æ¥çš„æ—¶å€™,æ¯ä¸ªç¡®è®¤ cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就å¯ä»¥æ¯”之å‰å¤šå‘ 4 个,所以这一次能够å‘é€ 8 个。

å¯ä»¥çœ‹å‡ºæ…¢å¯åŠ¨ç®—æ³•,å‘包的个数是指数性的增长。

那慢å¯åŠ¨æ¶¨åˆ°ä»€ä¹ˆæ—¶å€™æ˜¯ä¸ªå¤´å‘¢?

æœ‰ä¸€ä¸ªå«æ…¢å¯åŠ¨é—¨é™ ssthresh (slow start threshold)状æ€å˜é‡ã€‚

  • 当 cwnd < ssthresh æ—¶,使用慢å¯åŠ¨ç®—æ³•ã€‚
  • 当 cwnd >= ssthresh æ—¶,就会使用「拥塞é¿å…算法ã€ã€‚

8.2 拥塞é¿å…算法

å‰é¢è¯´é“,å½“æ‹¥å¡žçª—å£ cwnd ã€Œè¶…è¿‡ã€æ…¢å¯åŠ¨é—¨é™ ssthresh 就会进入拥塞é¿å…算法。

一般æ¥è¯´ ssthresh çš„å¤§å°æ˜¯ 65535 字节。

那么进入拥塞é¿å…算法åŽ,它的规则是:æ¯å½“收到一个 ACK æ—¶,cwnd 增加 1/cwnd。

接上å‰é¢çš„æ…¢å¯åŠ¨çš„æ —å­,现å‡å®š ssthresh 为 8

  • 当 8 个 ACK åº”ç­”ç¡®è®¤åˆ°æ¥æ—¶,æ¯ä¸ªç¡®è®¤å¢žåŠ  1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够å‘é€ 9 个 MSS 大å°çš„æ•°æ®,å˜æˆäº†çº¿æ€§å¢žé•¿ã€‚

所以,我们å¯ä»¥å‘现,拥塞é¿å…算法就是将原本慢å¯åŠ¨ç®—æ³•çš„æŒ‡æ•°å¢žé•¿å˜æˆäº†çº¿æ€§å¢žé•¿,还是增长阶段,但是增长速度缓慢了一些。

就这么一直增长ç€åŽ,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需è¦å¯¹ä¸¢å¤±çš„æ•°æ®åŒ…进行é‡ä¼ ã€‚

当触å‘了é‡ä¼ æœºåˆ¶,也就进入了「拥塞å‘生算法ã€ã€‚

8.3 拥塞å‘生

当网络出现拥塞,也就是会å‘生数æ®åŒ…é‡ä¼ ,é‡ä¼ æœºåˆ¶ä¸»è¦æœ‰ä¸¤ç§:

  • è¶…æ—¶é‡ä¼ 
  • 快速é‡ä¼ 

这两ç§ä½¿ç”¨çš„æ‹¥å¡žå‘é€ç®—法是ä¸åŒçš„,接下æ¥åˆ†åˆ«æ¥è¯´è¯´ã€‚

å‘生超时é‡ä¼ çš„æ‹¥å¡žå‘生算法

当å‘生了「超时é‡ä¼ ã€,则就会使用拥塞å‘生算法。

这个时候,ssthresh å’Œ cwnd 的值会å‘生å˜åŒ–:

  • ssthresh 设为 cwnd/2
  • cwnd é‡ç½®ä¸º 1

接ç€,就釿–°å¼€å§‹æ…¢å¯åЍ,æ…¢å¯åŠ¨æ˜¯ä¼šçªç„¶å‡å°‘æ•°æ®æµçš„。这真是一旦「超时é‡ä¼ ã€,马上回到解放å‰ã€‚ä½†æ˜¯è¿™ç§æ–¹å¼å¤ªæ¿€è¿›äº†,å应也很强烈,会造æˆç½‘络å¡é¡¿ã€‚

å°±å¥½åƒæœ¬æ¥åœ¨ç§‹å山高速漂移ç€,çªç„¶æ¥ä¸ªç´§æ€¥åˆ¹è½¦,轮胎å—得了å—。。。

å‘生快速é‡ä¼ çš„æ‹¥å¡žå‘生算法

还有更好的方å¼,å‰é¢æˆ‘们讲过「快速é‡ä¼ ç®—法ã€ã€‚当接收方å‘现丢了一个中间包的时候,å‘é€ä¸‰æ¬¡å‰ä¸€ä¸ªåŒ…çš„ ACK,于是å‘é€ç«¯å°±ä¼šå¿«é€Ÿåœ°é‡ä¼ ,ä¸å¿…等待超时å†é‡ä¼ ã€‚

TCP è®¤ä¸ºè¿™ç§æƒ…况ä¸ä¸¥é‡,因为大部分没丢,åªä¸¢äº†ä¸€å°éƒ¨åˆ†,则 ssthresh å’Œ cwnd å˜åŒ–如下:

  • cwnd = cwnd/2 ,也就是设置为原æ¥çš„一åŠ;
  • ssthresh = cwnd;
  • 进入快速æ¢å¤ç®—法

8.4 快速æ¢å¤

快速é‡ä¼ å’Œå¿«é€Ÿæ¢å¤ç®—æ³•ä¸€èˆ¬åŒæ—¶ä½¿ç”¨,快速æ¢å¤ç®—法是认为,你还能收到 3 个é‡å¤ ACK 说明网络也ä¸é‚£ä¹ˆç³Ÿç³•,所以没有必è¦åƒ RTO 超时那么强烈。

正如å‰é¢æ‰€è¯´,进入快速æ¢å¤ä¹‹å‰,cwnd å’Œ ssthresh 已被更新了:

  • cwnd = cwnd/2 ,也就是设置为原æ¥çš„一åŠ;
  • ssthresh = cwnd;

ç„¶åŽ,进入快速æ¢å¤ç®—法如下:

  • æ‹¥å¡žçª—å£ cwnd = ssthresh + 3 ( 3 çš„æ„æ€æ˜¯ç¡®è®¤æœ‰ 3 个数æ®åŒ…被收到了);
  • é‡ä¼ ä¸¢å¤±çš„æ•°æ®åŒ…;
  • å¦‚æžœå†æ”¶åˆ°é‡å¤çš„ ACK,那么 cwnd 增加 1;
  • 如果收到新数æ®çš„ ACK åŽ,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数æ®,说明从 duplicated ACK 时的数æ®éƒ½å·²æ”¶åˆ°,该æ¢å¤è¿‡ç¨‹å·²ç»ç»“æŸ,å¯ä»¥å›žåˆ°æ¢å¤ä¹‹å‰çš„状æ€äº†,也å³å†æ¬¡è¿›å…¥æ‹¥å¡žé¿å…状æ€;

也就是没有åƒã€Œè¶…æ—¶é‡ä¼ ã€ä¸€å¤œå›žåˆ°è§£æ”¾å‰,而是还在比较高的值,åŽç»­å‘ˆçº¿æ€§å¢žé•¿ã€‚

8.5 拥塞算法示æ„图

好了,以上就是拥塞控制的全部内容了,看完åŽ,你冿¥çœ‹ä¸‹é¢è¿™å¼ å›¾ç‰‡,æ¯ä¸ªè¿‡ç¨‹æˆ‘相信你都能明白:


结尾

ç å­—䏿˜“,ç”»å›¾æ›´ä¸æ˜“,å¤§å®¶ç»™å°æž—个一键三连å§,这对我éžå¸¸é‡è¦,感谢大家!


作者简介

作者简介:大家好,æˆ‘æ˜¯å°æž—,一个专为大家图解的工具人,微信æœç´¢ã€Œå°æž—codingã€,关注专注于图解计算机基础的我,写了很多原创的图解系列文章,如图解网络ã€å›¾è§£ç³»ç»Ÿã€å›¾è§£æ•°æ®åº“等等,这么å–力的图解,当然是希望大家能学起æ¥ä¸ä¼šæž¯ç‡¥,å°æž—期待你的关注,和我一起热气腾腾的æˆé•¿ã€‚