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

use*_*894 4 sockets wireshark

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

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

注意:似乎 ACK 数据包可以与数据一起发送(请参阅https://osqa-ask.wireshark.org/questions/36023/tcp-3-way-handshake-data-in-third-message) 。不过,我想知道如何强制它。 在此输入图像描述

Ste*_*ich 7

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

典型的客户端代码首先调用connect,只有在成功返回后才发送数据。connect仅当服务器以 SYN+ACK 响应时才会成功返回。内核将自动发出 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))
Run Code Online (Sandbox Code Playgroud)

显式设置为 False时TCP_QUICKACK,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
Run Code Online (Sandbox Code Playgroud)

如果没有显式设置该选项,就会得到空的 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
Run Code Online (Sandbox Code Playgroud)

  • @user3563894:请参阅更新的答案 - 设置 `TCP_QUICKACK` 可以在 Linux 上优化握手 (2认同)