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

Python UDP Socket发送大payload时丢包,约600报文后接收中断

问题描述

我正在实现一个逻辑数据二极管(数据仅单向传输,不允许ACK),因此选择UDP协议。协议核心流程如下:

  • 将负载(payload)拆分为小块
  • 为每个块分配序列号(sequence number)
  • 通过UDP向接收端传输该块

小负载传输时一切正常,但大负载在发送约600个数据报(datagram)后,报文似乎在传输中神秘丢失:发送端日志显示所有报文已发送,但接收端完全停止接收。

相关代码片段

发送循环代码:

for i in range(redundancy + 1):
    seq = 0
    sent_bytes = 0
    bytes_to_send = len(session.encrypted_data)
    while sent_bytes < bytes_to_send:
        logger.info("Sending payload chunk %d", seq)
        header = concat_bytes(str(seq).zfill(16).encode(), session.session_uuid.bytes_le)
        remaining_room = BUFFER_SIZE - len(header)
        data = session.encrypted_data[sent_bytes : sent_bytes + remaining_room]
        payload = concat_bytes(header, data)
        self._transmit_bytes(payload)
        sent_bytes += len(data)
        seq += 1

_transmit_bytes方法:

def _transmit_bytes(self, message: bytes):
    self.server_socket.sendto(message, self.addr)
    time.sleep(MESSAGE_DELAY)

server_socket初始化代码:

self.server_socket: LDDSocket = LDDSocket(listen=False)
so_linger_options = struct.pack("ii", 1, CLOSE_TIMEOUT)
send_buffer_size = 1024 * 1024 * 100  # 100 MB
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, so_linger_options)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, send_buffer_size)

LDDSocket是socket.socket的极简封装(listen=False仅表示不执行bind操作)。

已尝试的优化方案(无效)

  • 设置SO_LINGER选项(CLOSE_TIMEOUT为10秒)
  • 设置SO_SNDBUF选项(100MB)
  • 关闭socket前等待10秒,但接收端远在该超时及后续socket.close()调用前就已停止接收

发送器类的__exit__方法(上下文管理器):

def __exit__(self, exc_type, exc_val, exc_tb):
    cleanup_grace_period = 10  # seconds
    logger.info("Waiting %d seconds for cleanup...", cleanup_grace_period)
    time.sleep(cleanup_grace_period)
    logger.info("Cleanup presumed to be complete; closing socket.")
    self.close()
    logger.info("Socket closed.")

请问,导致socket发送一定量数据后停止传输的原因可能是什么?(中断位置大致固定,但并非完全一致)


可能的原因分析

1. 接收端接收缓冲区溢出

UDP是无连接协议,若接收端未及时读取缓冲区数据,系统接收缓冲区会被填满,后续到达的报文会直接被内核丢弃。发送端持续发送但接收端处理速度跟不上(如单报文处理耗时过长、接收线程阻塞)时,就会出现“接收端停止接收”的现象——实际是后续报文被丢弃,接收端无法感知。

2. 发送端SO_SNDBUF设置未实际生效

你设置了100MB的SO_SNDBUF,但系统存在缓冲区大小上限,实际生效值可能远低于设定值。当发送速度超过内核缓冲区处理能力时,sendto返回成功仅代表数据进入用户态缓冲区,未真正发往网络。若缓冲区被填满,阻塞模式下sendto会阻塞,非阻塞模式下会返回错误,但你的代码未检查sendto返回值,可能掩盖了问题。

建议:添加返回值校验,确认实际发送字节数与报文长度一致:

sent_len = self.server_socket.sendto(message, self.addr)
if sent_len != len(message):
    logger.warning("Partial send: sent %d bytes out of %d", sent_len, len(message))

3. 网络层面的流量管控或分片丢包

  • 中间设备限速:路由器、交换机等可能对UDP流量有速率限制,短时间发送大量报文会触发丢包逻辑。即便设置了MESSAGE_DELAY,若延迟过小仍可能触发管控。
  • UDP报文过大导致分片丢包:若BUFFER_SIZE设置过大,单个UDP报文超过MTU(通常1500字节,减去IP/UDP头后约1472字节)会被分片传输。一旦某分片丢失,整个报文无法重组,接收端无法识别。若接收端依赖序列号排序,会卡在丢失报文的位置,表现为停止接收。

4. 接收端序列号处理逻辑异常

协议依赖序列号重组数据,若接收端处理某序列号报文时出现解析错误、崩溃或死循环,会导致后续报文无法被正确处理或读取,看起来像是停止接收。

5. SO_LINGER选项的副作用

开启SO_LINGER并设置超时,会在socket关闭时等待未发送数据完成,但该选项可能改变内核缓冲区处理逻辑,间接影响发送过程。可暂时移除该选项,验证问题是否消失。


内容的提问来源于stack exchange,提问作者404usernamenotfound

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 02:38:20