Python线程中TCP连接建立失败,Mac/Linux报ConnectionRefusedError
多线程TCP聊天程序:Mac/Linux下ConnectionRefusedError问题排查
问题场景
编写支持多客户端的Python聊天程序时,使用_thread模块实现多客户端连接,但线程内建立TCP连接时,客户端的tcp_socket.connect((IP, TCP_PORT))抛出ConnectionRefusedError: [Errno 61] Connection refused错误。
- 不使用线程时代码正常运行,排除端口本身问题
- 相同代码在Windows运行正常,仅在Mac/Linux出现错误
简化代码
server.py
from _thread import * import socket def createClientConnection(client_id, client_address): tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_socket.bind((IP, TCP_PORT)) tcp_socket.listen() client_socket, address = tcp_socket.accept() # 此处未执行,线程直接结束 print("\n* TCP socket bound to %s\n" %(TCP_PORT)) # 后续通信逻辑... IP = '127.0.0.1' TCP_PORT = 1234 # 启动线程处理连接 start_new_thread(createClientConnection, (client_id, client_address)) # 如果在主线程执行TCP连接逻辑,一切正常
client.py
import socket IP = '127.0.0.1' TCP_PORT = 1234 tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_socket.connect((IP, TCP_PORT)) # 此处抛出Errno 61错误 print("\n* TCP socket created\n")
问题根源
- 错误的多线程逻辑:你在每个线程中重复创建、绑定并监听同一个端口
TCP_PORT。在Unix-like系统(Mac/Linux)中,默认不允许多个socket绑定到同一端口,第一个线程绑定端口后,后续线程的bind操作会静默失败(未做错误捕获),导致线程直接退出,没有实际的监听socket在运行,客户端自然无法连接。 - Windows与Unix-like的socket差异:Windows默认允许端口复用(默认开启
SO_REUSEADDR),多个线程的bind操作不会失败,因此代码能“正常”运行,但这本身是错误的逻辑——多个监听socket会导致连接被随机分配,造成资源浪费。
修正方案
正确的多客户端TCP服务端逻辑应该是:主线程只做一次绑定和监听,循环接受新连接,每收到一个连接就启动新线程处理该连接的通信。
修正后的server.py代码
from _thread import * import socket def handle_client(client_socket, client_address): # 处理与单个客户端的通信逻辑 print(f"* 已连接客户端: {client_address}") # 示例:接收客户端消息并回显 while True: data = client_socket.recv(1024) if not data: break client_socket.sendall(data) client_socket.close() print(f"* 客户端 {client_address} 断开连接") IP = '127.0.0.1' TCP_PORT = 1234 # 主线程创建监听socket,仅执行一次绑定和监听 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置SO_REUSEADDR,避免端口释放后短时间内无法复用 server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((IP, TCP_PORT)) server_socket.listen(5) # 设置等待连接的队列长度 print(f"* 服务端已启动,监听端口 {TCP_PORT}") # 循环接受新客户端连接 while True: client_socket, client_address = server_socket.accept() # 启动新线程处理该客户端的通信 start_new_thread(handle_client, (client_socket, client_address))
关键说明
- 监听socket只需要在主线程创建一次,绑定并监听端口后,通过
accept()循环等待新连接 - 每个新连接对应一个线程,线程负责处理该连接的具体通信,无需再创建监听socket
- 添加
SO_REUSEADDR选项可以避免服务端重启时出现“端口已被占用”的错误,提升鲁棒性
内容的提问来源于stack exchange,提问作者Zia Kim
相关产品推荐
相关产品推荐

