Почему HTTP-клиент отправляет пустые данные в сообщении ACK трехстороннего рукопожатия? - PullRequest
2 голосов
/ 12 января 2020

Я использую Wireshark и отмечаю, что мой браузер подключается к веб-сайту, он отправляет пустой ACK-пакет (а не ACK-пакет с http-запросом). Почему это происходит? В TCP и HTTP RFC запрещается отправлять ACK-пакеты с данными. Может ли здесь быть реализован отложенный ACK?

Кроме того, есть ли способ включить / отключить отправку ACK с данными в программировании сокетов?

Примечание: похоже, что пакет ACK можно отправить с данными (см. https://osqa-ask.wireshark.org/questions/36023/tcp-3-way-handshake-data-in-third-message). И все же мне интересно, как это можно форсировать. enter image description here

1 Ответ

3 голосов
/ 12 января 2020

Отправка пустого ACK как части TCP-рукопожатия фактически более распространена, чем включение данных в последнюю часть 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))

Если для TCP_QUICKACK явно задано значение False, ACK рукопожатия TCP уже транспортирует данные приложения:

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
...