You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux下Python UDP传输优化求助:已实现Go-Back-N仍速度迟缓

UDP文件传输工具优化问题

我需要为Linux创建一个Python UDP工具,目前能正常传输文件,但速度非常慢,且无法针对丢包场景进行优化。我已经实现了Go-Back-N算法和动态超时机制,但传输速度仍未达标。以下是我的代码:

客户端代码

import socket
import struct
import time
import sys

# Constants
SERVER_ADDRESS = ('localhost', 12345)
CHUNK_SIZE = 1024
HEADER_FORMAT = "I"
INITIAL_TIMEOUT = 0.5
WINDOW_SIZE = 4
MIN_TIMEOUT = 0.1
MAX_TIMEOUT = 2.0

def calculate_new_timeout(estimated_rtt, sample_rtt, alpha=0.125, beta=0.25):
    if estimated_rtt is None:
        estimated_rtt = sample_rtt
    else:
        estimated_rtt = (1 - alpha) * estimated_rtt + alpha * sample_rtt
    adjusted_timeout = estimated_rtt + beta * estimated_rtt
    return max(MIN_TIMEOUT, min(adjusted_timeout, MAX_TIMEOUT))

def send_chunk(s, chunk, seq_num):
    try:
        packet = struct.pack(HEADER_FORMAT, seq_num) + chunk
        s.sendto(packet, SERVER_ADDRESS)
    except socket.error as err:
        print(f"Error sending packet {seq_num}: {err}")

def send_file(filename):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.settimeout(INITIAL_TIMEOUT)
    estimated_rtt = None
    packets_in_flight = {}

    start_time = time.time()

    try:
        with open(filename, 'rb') as f:
            eof = False
            seq_num = 0
            while not eof:
                while len(packets_in_flight) < WINDOW_SIZE and not eof:
                    chunk = f.read(CHUNK_SIZE)
                    if chunk == b'':
                        eof = True
                        chunk = b'EOF'
                    send_chunk(s, chunk, seq_num)
                    packets_in_flight[seq_num] = (chunk, time.time())
                    seq_num += 1

                while packets_in_flight:
                    try:
                        ack, _ = s.recvfrom(CHUNK_SIZE)
                        ack_num, = struct.unpack(HEADER_FORMAT, ack)
                        if ack_num in packets_in_flight:
                            sample_rtt = time.time() - packets_in_flight[ack_num][1]
                            del packets_in_flight[ack_num]
                            estimated_rtt = calculate_new_timeout(estimated_rtt, sample_rtt)
                            s.settimeout(estimated_rtt)
                    except socket.timeout:
                        for seq_num, (chunk, _) in packets_in_flight.items():
                            send_chunk(s, chunk, seq_num)
    finally:
        duration = time.time() - start_time
        print(f'File sent successfully in {duration:.2f} seconds.')
        s.close()

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python client.py <filename>")
        sys.exit(1)
    send_file(sys.argv[1])

服务端代码

import socket
import struct
import sys

# Constants
LISTEN_ADDRESS = ('localhost', 12345)
BUFFER_SIZE = 2048
HEADER_FORMAT = "I"

def receive_file(save_as):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.bind(LISTEN_ADDRESS)
    print("Server started. Waiting for data...")

    expected_seq_num = 0

    try:
        with open(save_as, 'wb') as f:
            while True:
                packet, client_address = s.recvfrom(BUFFER_SIZE)
                seq_num, = struct.unpack(HEADER_FORMAT, packet[:struct.calcsize(HEADER_FORMAT)])
                data = packet[struct.calcsize(HEADER_FORMAT):]

                if seq_num == expected_seq_num:
                    if data == b'EOF':
                        print("EOF received. Ending transmission.")
                        ack_packet = struct.pack(HEADER_FORMAT, expected_seq_num)
                        s.sendto(ack_packet, client_address)
                        break
                    f.write(data)
                    expected_seq_num += 1

                ack_packet = struct.pack(HEADER_FORMAT, expected_seq_num - 1)
                s.sendto(ack_packet, client_address)

    finally:
        s.close()
        print(f"File received successfully and saved as {save_as}.")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python server.py <save_as_filename>")
        sys.exit(1)
    receive_file(sys.argv[1])
可行的优化方法

1. 调整窗口与数据包尺寸

  • 增大WINDOW_SIZE:当前值为4过小,可根据带宽延迟乘积(BDP)计算最优值,公式为BDP = 带宽(Mbps) * 延迟(ms) / 8,得到字节数后除以单包大小得到窗口数量,初始可尝试32或64。
  • 优化CHUNK_SIZE:Linux下UDP单包建议接近MTU(1472字节,MTU1500减去IP头20+UDP头8),减少数据包数量,降低协议开销。

2. 改进重传逻辑

  • 超时后仅重传最早未确认的包,而非所有在途包,减少不必要的流量消耗;同时为每个包单独设置定时器,精准触发重传。
  • 实现快速重传:当收到3个重复ACK时,立即重传对应缺失的包,无需等待超时,降低延迟。

3. 异步IO与多线程优化

  • 使用selectors模块实现异步IO,同时处理发送、接收和超时事件,避免阻塞导致窗口无法及时填充。
  • 分离文件读取和网络发送线程:文件读取线程提前将数据块读入缓冲区,网络线程直接从缓冲区取数据发送,避免磁盘IO阻塞传输。

4. 优化ACK机制

  • 服务端支持选择性ACK(SACK):收到乱序包时,在ACK中附带已接收的乱序包序号,让客户端仅重传缺失的包。
  • 延迟ACK:服务端合并多个ACK为一个发送(比如每收到N个包或延迟10ms发送一次),减少反向流量开销。

5. 系统层面优化

  • 增大Linux UDP缓冲区:通过sysctl命令调整参数,避免缓冲区不足导致丢包:
    sysctl -w net.core.rmem_max=16777216
    sysctl -w net.core.wmem_max=16777216
    
  • 在代码中设置socket缓冲区大小:调用s.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 16777216)和对应接收缓冲区参数。

6. 细节优化

  • 将packets_in_flight改为有序数据结构(如列表),快速定位最早未确认的包,提升重传效率。
  • 提前计算结构体头长度,避免重复调用struct.calcsize。
  • 增加数据包校验:在头部加入CRC校验码,服务端验证数据完整性,避免因数据损坏导致无效重传。

内容的提问来源于stack exchange,提问作者Dr.Labradoor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 00:49:55