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

UDP客户端需定时发送Ping并监听消息,求异步实现方案

解决UDP客户端同时发送与监听的异步方案

下午好!刚接触Socket和异步编程不用着急,你遇到的是同步IO阻塞的典型问题——当前代码里listen函数的recvfrom是阻塞式的,没收到服务器消息就会一直卡着,自然没法按时继续发送Ping了。异步确实是解决这个问题的核心思路,我给你几个实用的实现方案,从易到难:

方案1:多线程(最直观,改动最小)

把发送Ping和监听消息放到两个独立的线程里,这样监听的阻塞不会影响发送的定时任务。

修改后的代码如下:

import socket
from time import sleep
from contextlib import contextmanager
import threading

UDP_IP_ADDRESS = "127.0.0.1"
UDP_PORT_NO = 14004
Message = b"Hello World, From Client B"

@contextmanager
def socket_ctx():
    my_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    my_socket.bind(('127.0.0.1', 0))
    try:
        yield my_socket
    finally:
        my_socket.close()

def send_loop(client_sock):
    while True:
        client_sock.sendto(Message, (UDP_IP_ADDRESS, UDP_PORT_NO))
        print("Ping sent")
        sleep(2)

def listen_loop(client_sock):
    while True:
        data, addr = client_sock.recvfrom(100)
        print(f"Received from {addr}: {data}")

with socket_ctx() as sock:
    # 启动监听线程,设置daemon=True让线程随主线程退出
    listen_thread = threading.Thread(target=listen_loop, args=(sock,), daemon=True)
    listen_thread.start()
    
    # 主线程负责定时发送Ping
    send_loop(sock)
  • 关键点:UDP Socket是线程安全的,所以可以直接在两个线程共享;守护线程的设置能避免程序退出时残留线程。
  • 优点:理解简单,改动原有代码少;缺点:线程切换有少量开销,对于你的游戏客户端场景完全够用。

方案2:asyncio异步IO(更现代的异步方式)

用Python原生的asyncio库实现协程异步,不需要线程,单线程内处理并发任务,适合高并发场景。

代码示例:

import asyncio
import socket

UDP_IP_ADDRESS = "127.0.0.1"
UDP_PORT_NO = 14004
Message = b"Hello World, From Client B"

async def send_ping(sock):
    while True:
        sock.sendto(Message, (UDP_IP_ADDRESS, UDP_PORT_NO))
        print("Ping sent")
        await asyncio.sleep(2)

async def listen_messages(sock):
    while True:
        # 用asyncio提供的异步socket方法,不会阻塞事件循环
        data, addr = await asyncio.get_event_loop().sock_recvfrom(sock, 100)
        print(f"Received from {addr}: {data}")

async def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('127.0.0.1', 0))
    try:
        # 同时运行发送和监听两个协程
        await asyncio.gather(
            send_ping(sock),
            listen_messages(sock)
        )
    finally:
        sock.close()

if __name__ == "__main__":
    asyncio.run(main())
  • 关键点:asyncio.gather能让多个协程并行执行;sock_recvfrom是异步IO方法,不会卡主整个程序的事件循环。
  • 优点:无线程切换开销,适合大量客户端的高并发场景;缺点:需要理解协程和异步IO的基础概念,学习成本稍高。

方案3:selectors多路复用(底层高效方案)

用selectors模块实现IO多路复用,单线程内同时监听Socket的可读事件,同时定时触发发送操作,适合需要精细控制IO的场景。

代码示例:

import socket
import selectors
import time

UDP_IP_ADDRESS = "127.0.0.1"
UDP_PORT_NO = 14004
Message = b"Hello World, From Client B"
sel = selectors.DefaultSelector()

def send_ping(sock):
    sock.sendto(Message, (UDP_IP_ADDRESS, UDP_PORT_NO))
    print("Ping sent")
    # 记录下次发送时间
    global next_send_time
    next_send_time = time.time() + 2

def handle_read(sock, mask):
    data, addr = sock.recvfrom(100)
    print(f"Received from {addr}: {data}")

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('127.0.0.1', 0))
# 注册Socket的可读事件
sel.register(sock, selectors.EVENT_READ, handle_read)

next_send_time = time.time()
send_ping(sock)

try:
    while True:
        # 计算下次发送前的等待时间,避免无限阻塞
        timeout = max(0, next_send_time - time.time())
        events = sel.select(timeout=timeout)
        for key, mask in events:
            callback = key.data
            callback(key.fileobj)
        # 检查是否到了发送Ping的时间
        if time.time() >= next_send_time:
            send_ping(sock)
finally:
    sel.unregister(sock)
    sock.close()
    sel.close()
  • 关键点:通过select监听Socket的可读事件,同时定时检查发送时间,实现非阻塞的并发处理。
  • 优点:底层高效,完全可控;缺点:代码相对复杂,适合有一定Socket编程基础的场景。

你可以根据自己的需求选择方案,刚入门的话推荐先试多线程方案,理解起来最快,能快速解决你的问题;如果想深入异步编程,再尝试asyncio的方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:16:50