服务器退出时无法向客户端发送exit消息关闭Socket的问题求助
问题根源分析
你遇到的问题主要有两个核心原因:
- 仅针对最后一个客户端发送消息:你的
KeyboardInterrupt处理块中使用的conn_sock变量,只是主线程最后一次接受的客户端连接socket。当有多个客户端连接时,只有最后一个能收到退出消息,其他客户端会一直处于等待服务器消息的阻塞状态。 - 未维护所有活跃连接的集合:服务器没有保存所有当前在线的客户端连接,导致无法在退出时遍历通知每一个客户端。
另外,你的client_thread里调用sys.exit()也不太合适——这会直接终止整个服务器进程(而不是仅仅退出当前线程),如果有其他客户端连接的话,会被强制断开但不会收到退出消息。
解决方案:维护客户端连接列表+线程安全通知
我们需要做以下几个修改:
- 创建一个线程安全的集合来保存所有活跃的客户端socket。
- 每次新客户端连接时,将其socket添加到集合;客户端断开时,从集合中移除。
- 在
KeyboardInterrupt处理块中,遍历集合给每个客户端发送exit消息,然后关闭所有socket。 - 修复
client_thread中的退出逻辑,避免意外终止整个进程。
修改后的server.py代码
import socket import sys from threading import Thread, Lock # 维护所有活跃客户端连接,加锁保证线程安全 active_clients = set() client_lock = Lock() try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) except socket.error as e: print('Error occured while creating the socket {}'.format(e)) server_address = ('localhost', 50000) sock.bind(server_address) print('**** Server started on {}:{} ****'.format(*server_address)) sock.listen(5) def client_thread(conn_sock, client_add): try: with client_lock: active_clients.add(conn_sock) while True: client_msg = conn_sock.recv(1024).decode() if not client_msg: # 客户端异常断开的情况 break if client_msg.lower() != 'exit': print('[{0}:{1}] {2}'.format(*client_add, client_msg)) serv_reply = 'Okay ' + client_msg.upper() conn_sock.send(bytes(serv_reply, 'utf-8')) else: print('{} exitted !!'.format(client_add[0])) break except Exception as e: print(f'Error with client {client_add}: {e}') finally: # 从活跃集合中移除并关闭socket with client_lock: if conn_sock in active_clients: active_clients.remove(conn_sock) conn_sock.close() try: while True: conn_sock, client_add = sock.accept() print('Recieved connection from {}:{}'.format(*client_add)) conn_sock.send( bytes('***** Welcome to {} *****'.format(server_address[0]), 'utf-8')) Thread(target=client_thread, args=(conn_sock, client_add), daemon=True).start() except KeyboardInterrupt: print('\nProgram execution cancelled by user. Closing all client connections...') # 遍历所有活跃客户端,发送退出消息并关闭 with client_lock: for client_sock in list(active_clients): try: client_sock.send(b'exit') client_sock.close() except Exception as e: print(f'Failed to close client connection: {e}') active_clients.clear() sys.exit(0) except Exception as e: print('Some error occured \n {}'.format(e)) finally: sock.close()
修改后的client.py(可选优化)
原客户端代码已经可以处理服务器发来的exit消息,但可以增加一点异常处理,避免客户端在服务器异常断开时卡住:
import socket import sys sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = ('localhost', 50000) print('Connecting to {} on {}'.format(*server_address)) try: sock.connect(server_address) except ConnectionRefusedError: print('Server is not running. Exiting...') sys.exit(1) def exiting(host='You'): print('{} exitted !!'.format(host)) sock.close() sys.exit() try: while True: try: serv_msg = sock.recv(1024).decode() if not serv_msg: # 服务器异常断开,没有消息返回 exiting('Server') if serv_msg.lower() != 'exit': print('{1}: {0}'.format(serv_msg, server_address[0])) client_reply = input('You: ') sock.send(bytes(client_reply, 'utf-8')) if client_reply.lower() == 'exit': exiting() else: exiting('Server') except ConnectionResetError: print('Server disconnected unexpectedly.') exiting() except KeyboardInterrupt: print('\nYou exited manually.') sock.send(b'exit') exiting()
关键修改点说明
- 线程安全的客户端集合:用
set存储活跃socket,搭配Lock保证多线程下添加/移除操作的安全性,避免出现竞态条件。 - 统一的退出处理:在服务器收到Ctrl+C时,遍历所有客户端socket发送
exit消息,确保每个客户端都能收到退出信号并正常关闭。 - 修复线程退出逻辑:将
client_thread中的sys.exit()改为break循环,然后在finally块中清理连接,避免服务器进程意外终止。 - 客户端异常处理:增加对
ConnectionRefusedError和ConnectionResetError的处理,让客户端在服务器未启动或异常断开时能友好退出。
内容的提问来源于stack exchange,提问作者Rohit




