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

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)。


原因分析

  1. TCP半开连接状态:客户端休眠时网络被系统切断,但服务端未收到TCP协议层的FIN/RST断开报文,仍判定连接有效,持续阻塞在recv()调用上。客户端唤醒后发送数据,sendall()仅完成本地缓冲区写入,服务端的半开连接无法处理请求,客户端等待响应超时抛出TimeoutError。
  2. 服务端单连接逻辑缺陷:服务端仅调用一次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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 09:22:21