Python 3.5及以上版本中如何用Ctrl+C中断recvfrom调用?
嘿,我来帮你搞定UDP服务器按下Ctrl+C无法正常退出的问题!
首先得把事儿说透:从Python 3.4开始,按照PEP 475的规范,像socket.recvfrom()这类阻塞的系统调用在收到SIGINT(就是你按Ctrl+C发送的中断信号)时,会自动重启调用,而不是抛出异常或者返回空数据——这就是为啥你的原代码按Ctrl+C没反应,recvfrom会一直死堵着不撒手。
下面给你两种靠谱的解决方案,随便挑一种用就行:
方案一:捕获信号+Socket超时
这种方法通过捕获中断信号标记退出状态,同时给socket设置超时,让循环有机会检查退出标志:
import socket import signal # 定义全局退出标志 should_exit = False def handle_sigint(signum, frame): global should_exit print("\n收到Ctrl+C,准备退出服务器...") should_exit = True # 注册SIGINT信号处理函数 signal.signal(signal.SIGINT, handle_sigint) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(('0.0.0.0', 5555)) # 设置1秒超时,让recvfrom每隔1秒就跳出阻塞,给循环检查退出标志的机会 s.settimeout(1.0) while not should_exit: try: data, addr = s.recvfrom(2048) print("received:", data.decode(), "from", addr) except socket.timeout: # 超时只是用来触发循环检查,不需要额外处理 continue except Exception as e: print(f"运行出错:{e}") break print("服务器已正常退出") s.close()
方案二:用select实现非阻塞监听
另一种思路是用select模块监听socket的可读事件,同时设置超时,既能等待数据又能响应信号:
import socket import select import signal should_exit = False def handle_sigint(signum, frame): global should_exit print("\n收到Ctrl+C,准备退出服务器...") should_exit = True signal.signal(signal.SIGINT, handle_sigint) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(('0.0.0.0', 5555)) while not should_exit: # 监听socket是否可读,超时1秒 readable, _, _ = select.select([s], [], [], 1.0) if s in readable: data, addr = s.recvfrom(2048) print("received:", data.decode(), "from", addr) print("服务器已正常退出") s.close()
为啥这两种方法能行?
- 方案一靠socket超时,让
recvfrom每隔1秒就抛出超时异常回到循环,这时就能检查should_exit标志——按下Ctrl+C时,信号处理函数会把标志改成True,下一次超时后循环就会正常退出。 - 方案二用
select.select()替代直接调用recvfrom,它会在超时时间内等待socket可读,超时后自动回到循环检查退出标志,同样能及时响应Ctrl+C信号。
这两种方法都能让你的UDP服务器优雅处理Ctrl+C,再也不用依赖原代码里“recvfrom返回空数据”的逻辑啦(毕竟PEP475之后这种情况根本不会发生)。
内容的提问来源于stack exchange,提问作者Gang YIN
相关产品推荐
相关产品推荐

