为什么HTTP客户端在三次握手的ACK报文中发送空数据?

Why does HTTP client sends empty data in the ACK message of the three-way handshake?

我正在使用 Wireshark,我注意到我的浏览器连接了一个网站,它发送了一个空的 ACK 数据包(而不是带有 http 请求的 ACK 数据包)。 为什么会这样?在 TCP 和 HTTP RFC 中,不禁止发送带有数据的 ACK 数据包。这里可以实现延迟ACK吗?

另外,有没有办法enable/disable在套接字编程中发送带有数据的ACK?

注意:好像是ACK包可以和数据一起发送(见https://osqa-ask.wireshark.org/questions/36023/tcp-3-way-handshake-data-in-third-message)。不过,我想知道如何强制它。

发送一个空的 ACK 作为 TCP 握手的一部分实际上比在 TCP 握手的最后部分包含数据更常见。

典型的客户端代码首先调用 connect,只有在 return 秒后它才成功发送数据。 connect 只有在服务器用 SYN+ACK 响应时才会 return 成功。内核将为此自动发出 ACK 以向服务器发出握手现已完成的信号。内核此时没有任何应用程序数据要发送,因此它不能将这些数据包含到此 ACK 中。

试想一下,如果延迟最终 ACK 以等待来自客户端的更多潜在数据会发生什么:在最坏的情况下,客户端不会发送此类数据,因为它首先等待来自服务器的数据 - 这是SMTP、FTP、...等协议的典型代表。但是由于服务器没有从客户端获得 ACK,它不会认为 TCP 握手完成,因此不会发送任何数据。因此,这将引入不必要的延迟,直到客户端决定发送空 ACK 为止。

因此,为了优化握手,客户端必须告诉内核它将立即开始发送数据并且内核不应该已经发送它的 ACK。这至少可以在 Linux 中使用 TCP_QUICKACK 选项完成:

import socket
s = socket.socket()
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, False)
s.connect(("example.com",80))
s.send(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
print(s.recv(1024))

TCP_QUICKACK 明确设置为 False 的情况下,TCP 握手的 ACK 已经传输应用程序数据:

IP local-system.45664 > example.com.http: Flags [S], seq 1590101890, win 29200, options [mss 1460,sackOK,TS val 4226937632 ecr 0,nop,wscale 7], length 0
IP example.com.http > local-system.45664: Flags [S.], seq 3649111496, ack 1590101891, win 65535, options [mss 1452,sackOK,TS val 625701214 ecr 4226937632,nop,wscale 9], length 0
IP local-system.45664 > example.com.http: Flags [P.], seq 1:38, ack 1, win 229, options [nop,nop,TS val 4226937765 ecr 625701214], length 37: HTTP: GET / HTTP/1.0

在没有明确设置选项的情况下得到空 ACK:

IP local-system.45856 > example.com.http: Flags [S], seq 4147534093, win 29200, options [mss 1460,sackOK,TS val 4227186296 ecr 0,nop,wscale 7], length 0
IP example.com.http > local-system.45856: Flags [S.], seq 123501506, ack 4147534094, win 65535, options [mss 1452,sackOK,TS val 2369778695 ecr 4227186296,nop,wscale 9], length 0
IP local-system.45856 > example.com.http: Flags [.], ack 1, win 229, options [nop,nop,TS val 4227186421 ecr 2369778695], length 0
IP local-system.45856 > example.com.http: Flags [P.], seq 1:38, ack 1, win 229, options [nop,nop,TS val 4227186421 ecr 2369778695], length 37: HTTP: GET / HTTP/1.0