You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

服务器退出时无法向客户端发送exit消息关闭Socket的问题求助

问题根源分析

你遇到的问题主要有两个核心原因:

  1. 仅针对最后一个客户端发送消息:你的KeyboardInterrupt处理块中使用的conn_sock变量,只是主线程最后一次接受的客户端连接socket。当有多个客户端连接时,只有最后一个能收到退出消息,其他客户端会一直处于等待服务器消息的阻塞状态。
  2. 未维护所有活跃连接的集合:服务器没有保存所有当前在线的客户端连接,导致无法在退出时遍历通知每一个客户端。

另外,你的client_thread里调用sys.exit()也不太合适——这会直接终止整个服务器进程(而不是仅仅退出当前线程),如果有其他客户端连接的话,会被强制断开但不会收到退出消息。

解决方案:维护客户端连接列表+线程安全通知

我们需要做以下几个修改:

  1. 创建一个线程安全的集合来保存所有活跃的客户端socket。
  2. 每次新客户端连接时,将其socket添加到集合;客户端断开时,从集合中移除。
  3. KeyboardInterrupt处理块中,遍历集合给每个客户端发送exit消息,然后关闭所有socket。
  4. 修复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块中清理连接,避免服务器进程意外终止。
  • 客户端异常处理:增加对ConnectionRefusedErrorConnectionResetError的处理,让客户端在服务器未启动或异常断开时能友好退出。

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

火山引擎 最新活动