多线程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
相关产品推荐
相关产品推荐

