Python Socket连接存活但无法传输数据的异常排查求助
问题
我使用Python Socket已有一段时间,但最近发现脚本长时间运行时出现异常:连接看似存活却无法传输消息。
我编写了基础的服务端与客户端代码来复现问题:
服务端代码
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(ADDRESS) s.listen() conn, addr = s.accept() i = 0 while True: print(i, conn.recv(1024)) conn.sendall(b'Hello back client') i += 1
客户端代码
import socket import time s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(ADDRESS) i = 0 while True: s.sendall(b'Hello server') print(i, s.recv(1024)) i += 1 time.sleep(1)
同时运行两端脚本时,每秒都能正常打印消息。但客户端电脑休眠后,连接异常中断:服务端无异常,阻塞在print(i, conn.recv(1024));客户端发送消息看似成功,但在print(i, s.recv(1024))处报TimeoutError。
服务端最后打印内容:
184 b'Hello server' 185 b'Hello server'
客户端最后打印内容:
184 b'Hello back client' 185 b'Hello back client' Traceback (most recent call last): File "<stdin>", line 3, in <module> TimeoutError: [WinError 10060] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond
若不终止服务端进程,客户端重新连接同一地址后仍无法传输数据,两端sendall()调用成功,但recv()始终阻塞,即使对方已发送数据。
备注:服务端为CentOS 8的私有VPS(Python 3.9.5),客户端为Windows 11(Python 3.10.9)。
原因分析
- TCP半开连接状态:客户端休眠时网络被系统切断,但服务端未收到TCP协议层的FIN/RST断开报文,仍判定连接有效,持续阻塞在
recv()调用上。客户端唤醒后发送数据,sendall()仅完成本地缓冲区写入,服务端的半开连接无法处理请求,客户端等待响应超时抛出TimeoutError。 - 服务端单连接逻辑缺陷:服务端仅调用一次
accept(),只能处理一个客户端连接。旧连接卡住后,服务端无法接受新的连接请求——客户端重新连接时,TCP三次握手虽完成,但服务端不会处理新连接的任何数据,导致两端sendall()仅写入本地缓冲区,recv()因无数据返回持续阻塞。
解决方案
1. 给Socket设置超时时间
在服务端和客户端的Socket上设置超时,避免无限期阻塞:
- 服务端修改:
conn, addr = s.accept() conn.settimeout(30) # 设置30秒超时
- 客户端修改:
s.connect(ADDRESS) s.settimeout(10) # 设置10秒超时
超时后会抛出socket.timeout异常,捕获后可关闭连接释放资源。
2. 实现应用层心跳机制
定期发送心跳包检测连接状态,主动清理无效连接:
- 客户端示例(添加心跳逻辑):
import socket import time s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(ADDRESS) s.settimeout(10) i = 0 heartbeat_interval = 5 last_send_time = time.time() while True: # 定期发送心跳,避免连接被判定为无效 if time.time() - last_send_time > heartbeat_interval: s.sendall(b'HEARTBEAT') last_send_time = time.time() else: s.sendall(b'Hello server') print(i, s.recv(1024)) i += 1 time.sleep(1)
服务端需对应处理心跳消息,若连续多次未收到心跳则主动关闭连接。
3. 服务端改为多连接处理模式
使用多线程/异步IO让服务端同时处理多个连接,避免旧连接卡住导致无法接受新请求:
- 多线程服务端示例:
import socket import threading def handle_client(conn, addr): print(f"新连接来自 {addr}") conn.settimeout(30) i = 0 try: while True: data = conn.recv(1024) if not data: break print(i, data) conn.sendall(b'Hello back client') i += 1 except socket.timeout: print(f"{addr} 连接超时") finally: conn.close() print(f"与 {addr} 的连接已关闭") s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(ADDRESS) s.listen(5) # 允许5个等待连接 print("服务端监听中...") while True: conn, addr = s.accept() threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start()
每个客户端连接由单独线程处理,旧连接超时或断开后,服务端仍能正常接受新连接。
4. 开启TCP内置保活机制
利用TCP的SO_KEEPALIVE选项,让操作系统自动检测死连接:
- 服务端设置示例:
conn.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # 设置空闲30秒后开始发心跳,每10秒发一次,连续3次无响应则关闭连接 conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 30) conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10) conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)
开启后操作系统会自动维护连接状态,清理半开连接。
内容的提问来源于stack exchange,提问作者Alejandro Saldarriaga
相关产品推荐
相关产品推荐

