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

Python多线程TCP服务器无法正常关闭问题求助

问题分析
  1. 非守护线程阻止进程退出:创建的客户端线程默认是非守护线程,主线程执行sys.exit(1)后,子线程仍会继续运行,导致服务器进程无法正常终止。
  2. 阻塞的accept()调用无法退出:当服务器卡在s.accept()等待连接时,KeyboardInterrupt触发后,pass语句无法打破这个阻塞,后续的关闭逻辑无法及时执行。
  3. recv_all未处理连接断开:代码中注释了连接断开的判断逻辑,当客户端主动断开时,conn.recv()返回空字节,recv_all会陷入无限循环,子线程无法正常退出。
修复方案

针对上述问题,对代码做以下修改:

1. 将客户端线程设为守护线程

在创建线程后,设置client_thread.daemon = True,这样主线程退出时,所有守护线程会自动终止。

2. 优雅打破accept()阻塞

在捕获KeyboardInterrupt时,先关闭监听socket,让s.accept()抛出异常并退出阻塞,确保后续关闭逻辑执行。

3. 修复recv_all的连接断开处理

取消注释连接断开的判断逻辑,当chunk为空时,抛出异常避免无限循环,同时在客户端处理函数中捕获该异常,确保子线程正常退出。

完整修复后的代码

#! /usr/bin/env python3 
import sys, struct, socket, threading, logging


HOST = "127.0.0.1"
PORT = 52583
MSGLEN = 2048

def recv_all(conn,n):
  chunks = b''
  bytes_recd = 0
  while bytes_recd < n:
    chunk = conn.recv(min(n - bytes_recd, 1024))
    if len(chunk) == 0:
      raise RuntimeError("socket connection broken")
    chunks += chunk
    bytes_recd = bytes_recd + len(chunk)
  return chunks

def handle_client(conn, addr):
    
    print(f"Contacted by {addr}")

    with conn:
        try:
            while True:
                data = recv_all(conn, 4)
                input = struct.unpack("!i", data)[0]
                print(f"Received {input}")
                conn.sendall(struct.pack("!i",input))
                print(f"Sent {input}")
        except RuntimeError:
            print(f"Client {addr} disconnected")
                                     
def main(host=HOST, port=PORT):

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        try:
            s.bind((host, port))
            s.listen()
            print(f"Server listening on {host}:{port}")

            while True:
                print("Waiting for a connection...")
                conn, addr = s.accept()  
                             
                client_thread = threading.Thread(target=handle_client, args=(conn, addr))
                client_thread.daemon = True  # 设置为守护线程
                client_thread.start()

        except KeyboardInterrupt:
            print("Received shutdown signal, closing listening socket...")
            s.close()  # 关闭监听socket,打破accept阻塞
        except OSError:
            pass  # 处理accept被打断后的异常
        print("Server shutting down")
        sys.exit(0)
        
if __name__ == "__main__":
    main()

额外说明

  • 如果需要更优雅地关闭子线程,可维护一个全局退出标志,在handle_client的循环中检查标志并主动退出,避免强制终止。
  • 关闭监听socket后,s.accept()会抛出OSError,捕获该异常可避免不必要的报错信息。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 01:57:46