📜  TCP 中的选择性确认 (SACK)(1)

📅  最后修改于: 2023-12-03 15:05:30.341000             🧑  作者: Mango

TCP 中的选择性确认 (SACK)

简介

TCP 中的选择性确认 (SACK) 是一种先进的重传机制,在数据包丢失时可提高网络的吞吐量和延迟。传统的 TCP 重传机制只是简单地重传丢失的数据包,但是它没有考虑丢失的数据包之后已经被传输的数据。

SACK 的基本思想是让接收方能够通知发送方哪些数据已经成功接收,而发送方不需要重传这些数据。这样可以有效地减少不必要的重传和网络拥塞,提高网络性能。

SACK 的工作原理

SACK 可以在 TCP 报文头里添加选项字段,用来通知发送方哪些数据已经成功接收。具体来说,每次接收到 TCP 报文时,接收方会根据报文头中的 SACK 选项确定哪些数据已经成功接收。如果发现有数据丢失,接收方就可以把这些丢失的数据的序列号通过 SACK 选项发送给发送方,告诉它哪些数据需要重传。

SACK 报文头中包含了一组序列号范围,表示已成功接收的数据范围。这个范围可以被发送方用来确定哪些数据需要重传。SACK 可以包含多组序列号范围,这是因为一次收到报文不一定完整地包含了所有的数据,所以有可能收到多份数据,每份可能涵盖不同的序列号范围,需要分别进行确认。

SACK 的优缺点
优点
  • SACK 可以大大提高网络的性能,减少不必要的重传和网络拥塞。
  • SACK 可以更准确地确定丢失的数据,避免了传统 TCP 重传机制中由于连锁反应引起的重传过载。
  • SACK 可以避免误认为丢失的数据已经到达发送方,从而避免了无效的重传。
缺点
  • SACK 要求网络设备支持 TCP 的选项字段,不受支持的设备将无法识别并忽略 SACK 选项。
  • SACK 将增加 TCP 报文的长度,对于某些小的传输协议可能会造成一定程度的浪费。
代码示例

以下示例演示了如何使用 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)