📅  最后修改于: 2023-12-03 15:05:30.341000             🧑  作者: Mango
TCP 中的选择性确认 (SACK) 是一种先进的重传机制,在数据包丢失时可提高网络的吞吐量和延迟。传统的 TCP 重传机制只是简单地重传丢失的数据包,但是它没有考虑丢失的数据包之后已经被传输的数据。
SACK 的基本思想是让接收方能够通知发送方哪些数据已经成功接收,而发送方不需要重传这些数据。这样可以有效地减少不必要的重传和网络拥塞,提高网络性能。
SACK 可以在 TCP 报文头里添加选项字段,用来通知发送方哪些数据已经成功接收。具体来说,每次接收到 TCP 报文时,接收方会根据报文头中的 SACK 选项确定哪些数据已经成功接收。如果发现有数据丢失,接收方就可以把这些丢失的数据的序列号通过 SACK 选项发送给发送方,告诉它哪些数据需要重传。
SACK 报文头中包含了一组序列号范围,表示已成功接收的数据范围。这个范围可以被发送方用来确定哪些数据需要重传。SACK 可以包含多组序列号范围,这是因为一次收到报文不一定完整地包含了所有的数据,所以有可能收到多份数据,每份可能涵盖不同的序列号范围,需要分别进行确认。
以下示例演示了如何使用 Python 套接字库实现 SACK 选项。首先需要创建一个 TCP 解析器,用来解析 TCP 报文头。然后通过设置 TCP 选项字段来启用 SACK,最后可以在发送和接收报文时获取 SACK 选项并进行处理。
import socket
import struct
# 创建 TCP 解析器
class TCPSegment:
def __init__(self, raw=None):
header_len = 20
if raw is None:
self.raw = bytearray(header_len)
self.raw[0] = 0x45
struct.pack_into(">BBH", self.raw, 6, 0x40, 6, 0) # TTL = 64, TCP proto = 6
else:
self.raw = bytearray(raw)
self.src = socket.inet_ntoa(self.raw[12:16])
self.dst = socket.inet_ntoa(self.raw[16:20])
self.sport = struct.unpack(">H", self.raw[20:22])[0]
self.dport = struct.unpack(">H", self.raw[22:24])[0]
self.seq_num = struct.unpack(">L", self.raw[24:28])[0]
self.ack_num = struct.unpack(">L", self.raw[28:32])[0]
self.flags = struct.unpack(">H", self.raw[32:34])[0] & 0b111111
# SACK 选项字段
self.sack = []
# 设置 TCP 选项字段
def set_option(self, kind, data):
pos = (self.get_option_len() >> 2) + 1
self.raw[pos] = kind
self.raw[pos + 1] = len(data) + 2
self.raw[pos + 2:pos + 2 + len(data)] = data
self.raw[pos + len(data) + 2] = 0
# 获取 TCP 选项长度
def get_option_len(self):
header_len = (self.flags >> 12) << 2
return header_len - 20
# 启用 SACK 选项
def enable_sack(s):
# 创建 TCP 解析器
tcp = TCPSegment()
# 设置 TCP 选项
option_data = bytearray(b'\x05\x02\x00\x00')
tcp.set_option(3, option_data)
# 发送 SACK 选项
s.sendall(tcp.raw)
# 获取 SACK 选项
def get_sack(tcp):
if tcp.get_option_len() == 0:
return []
if tcp.raw[20] & 0b01000000 == 0:
return []
sack_pos = (tcp.get_option_len() >> 2) + 1
sack_len = tcp.raw[sack_pos + 1] - 2
num_blocks = sack_len // 8
sack = []
for i in range(num_blocks):
start = struct.unpack(">I", tcp.raw[sack_pos + 2 + i * 8:sack_pos + 6 + i * 8])[0]
end = struct.unpack(">I", tcp.raw[sack_pos + 6 + i * 8:sack_pos + 10 + i * 8])[0]
sack.append((start, end))
return sack
# 例子:发送和接收 SACK 报文
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# 连接服务器
s.connect(('www.example.com', 80))
# 启用 SACK 选项
enable_sack(s)
# 发送请求
s.sendall(b'GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n')
# 接收响应
sack_list = []
while True:
data = s.recv(1024)
if not data:
break
# 解析 TCP 报文头
tcp = TCPSegment(data)
# 获取 SACK 选项
sack = get_sack(tcp)
for (start, end) in sack:
sack_list.append((start, end))
print("SACK list: ", sack_list)