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

多线程Python UDP客户端收回复仍抛超时异常的原因排查

问题:UDP Ping客户端出现已收到回复却触发超时异常的情况

客户端代码

from socket import *
import threading
import time


def ping_thread(num, clientSocket):
    try:
        clientSocket.settimeout(1)
        t1 = time.perf_counter()
        message = "Ping " + str(num) + " " + str(time.time())
        clientSocket.sendto(message.encode(), (serverName, serverPort))
        modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
        t2 = time.perf_counter()
        lag = t2 - t1
        print(f"{modifiedMessage.decode()}: {lag:.8f}s")
    except TimeoutError:
        print(f"ping {num} time out!")


if __name__ == "__main__":
    threads = []
    clientSocket = socket(AF_INET, SOCK_DGRAM)
    serverName = "localhost"
    serverPort = 12000
    for i in range(1, 11):
        t = threading.Thread(target=ping_thread, args=(i, clientSocket))
        threads.append(t)
        t.start()

    for t in threads:
        t.join()

    clientSocket.close()

运行结果

PING 2 1710845145.1677558: 0.00191080s
PING 5 1710845145.169752: 0.00126800sPING 6 1710845145.169752: 0.00098610s

PING 1 1710845145.1658478: 0.00588400s
PING 3 1710845145.1677558: 0.00067630s
PING 8 1710845145.1719148: 0.00477900s
PING 10 1710845145.172912: 0.00083350s
ping 10 time out!
ping 4 time out!
ping 2 time out!

疑惑

为何部分线程(例如上述的Ping 2)明明收到了服务器回复,却仍抛出超时异常?

服务器代码

# We will need the following module to generate randomized lost packets
import random
from socket import *

# Create a UDP socket
# Notice the use of SOCK_DGRAM for UDP packets
serverSocket = socket(AF_INET, SOCK_DGRAM)
# Assign IP address and port number to socket
serverSocket.bind(('', 12000))

while True:
    # Generate random number in the range of 0 to 10
    rand = random.randint(0, 10)
    # Receive the client packet along with the address it is coming from
    message, address = serverSocket.recvfrom(1024)
    # Capitalize the message from the client
    message = message.upper()
    # If rand is less than 4, we consider the packet lost and do not respond
    if rand < 4:
        continue
    # Otherwise, the server responds
    serverSocket.sendto(message, address)

解答

问题的核心是所有线程共享同一个UDP套接字,而UDP是无连接协议,套接字的接收缓冲区是全局共享的:

  • 当多个线程同时调用recvfrom()等待响应时,操作系统会把收到的任意一个UDP数据包分配给其中一个线程,不管这个数据包是不是该线程发送的请求对应的。
  • 比如Ping 2的响应被线程5的recvfrom()抢到,线程5会打印Ping 2的回复信息,但线程5自己的Ping 5响应可能被其他线程抢走;而真正发送Ping 2请求的线程2,因为没拿到自己的响应,最终超时触发异常,打印ping 2 time out!。

修复方案

给每个线程创建独立的UDP套接字,这样每个套接字的接收缓冲区相互独立,服务器返回的响应只会被对应的发送线程接收:
修改客户端主函数部分代码:

if __name__ == "__main__":
    threads = []
    serverName = "localhost"
    serverPort = 12000
    for i in range(1, 11):
        # 每个线程创建自己的socket
        clientSocket = socket(AF_INET, SOCK_DGRAM)
        t = threading.Thread(target=ping_thread, args=(i, clientSocket))
        threads.append(t)
        t.start()

    for t in threads:
        t.join()

这样每个线程的请求和响应都会一一对应,不会出现响应被其他线程抢走的情况,也就不会出现“收到回复却超时”的矛盾现象。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 11:45:05