python聊天服务器用户发送的消息不转发给其他用户

问题是服务器启动后第一个客户端连接服务器,给服务器发送消息都没问题,但是第二个客户端连接时如果任何一个客户端发送消息,服务端不会转发消息给其他客户端
服务器代码

import os
import socket
import multiprocessing
import datetime

class ChatServer:
    def __init__(self):
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.host = "127.0.0.1"
        self.port = 12345
        self.clients = {}
        self.running = False
        self.log_filename = None

    def start(self):
        try:
            self.server_socket.bind((self.host, self.port))
            self.server_socket.listen(5)
            self.running = True
            self.create_log_file()
            print(f"服务器已启动,监听 {self.host}:{self.port}")
            multiprocessing.Process(target=self.accept_clients).start()
        except Exception as e:
            print(f"启动服务器失败: {e}")

    def remove_client(self, client_socket):
        if client_socket in self.clients:
            del self.clients[client_socket]
        client_socket.close()

    def accept_clients(self):
        while self.running:
            try:
                client_socket, client_address = self.server_socket.accept()
                print(f"连接来自 {client_address}")
                multiprocessing.Process(target=self.handle_client, args=(client_socket, client_address)).start()
                self.broadcast_message(f"用户 {client_address[0]} 已加入服务器")

            except OSError as e:
                if self.running:
                    print(f"服务器已停止接受新的客户端连接:{e}")
                break

    def handle_client(self, client_socket, client_address):
        username = client_address[0]
        self.clients[client_socket] = username
        try:
            while True:
                message = client_socket.recv(1024).decode()
                if not message:
                    break
                print(f"收到来自 {username} 的消息:{message}")
                self.log_message(f"收到来自 {username} 的消息:{message}")

                if message.startswith("/ip"):
                    ip_port_msg = f"您的IP地址和端口为:{client_address[0]}:{client_address[1]}"
                    client_socket.send(ip_port_msg.encode())

                elif message.startswith("/stop"):
                    parts = message.split(" ")
                    if len(parts) >= 2:
                        password = parts[1]
                        self.prompt_password_and_stop(client_socket, password)
                        break
                    else:
                        client_socket.send("密码错误,请重新输入。".encode())
                elif message.startswith("/list"):
                    self.send_user_list(client_socket)
                    self.output_user_list()
                elif message.startswith("/kick"):
                    self.prompt_password_and_kick(client_socket, message)
                elif message.startswith("/history"):
                    self.send_chat_history(client_socket)
                else:
                    self.broadcast_message(f"{username}: {message}", sender=client_socket)

        except ConnectionResetError:
            print(f"客户端 {username} 异常断开连接")
        finally:
            if client_socket in self.clients:
                del self.clients[client_socket]
            client_socket.close()

    def prompt_password_and_stop(self, client_socket, password):
        if password == "1234":
            self.stop()
            client_socket.send("服务器已停止。".encode())
            self.log_message("管理员停止了服务器")
        else:
            client_socket.send("密码错误,请重新输入。".encode())

    def output_user_list(self):
        print("在线用户列表:")
        for user in self.clients.values():
            print(user)

    def broadcast_message(self, message, sender=None):
        self.log_message(message)

        for client_socket, username in list(self.clients.items()):
            if client_socket != sender:
                try:
                    client_socket.send(message.encode())
                except (ConnectionResetError, socket.error) as e:
                    print(f"与客户端 {username} 的连接意外断开:{e}")
                    self.remove_client(client_socket)

    def log_message(self, message):
        with open(self.log_filename, "a") as f:
            f.write(f"{datetime.datetime.now()} - {message}\n")

    def stop(self):
        self.running = False
        self.server_socket.close()

    def send_user_list(self, client_socket):
        user_list = "在线用户:\n"
        for user in self.clients.values():
            user_list += f"- {user}\n"
        client_socket.send(user_list.encode())

    def prompt_password_and_kick(self, client_socket, message):
        parts = message.split(" ")
        if len(parts) >= 4 and parts[-1] == "1234":
            target_username = parts[1]
            kick_reason = " ".join(parts[2:-1])
            for sock, username in list(self.clients.items()):
                if username == target_username:
                    sock.send(f"您已被管理员踢出服务器,原因:{kick_reason}".encode())
                    del self.clients[sock]
                    sock.close()
                    print(f"用户 {target_username} 已被踢出服务器。")
                    self.log_message(f"管理员踢出用户 {target_username},原因:{kick_reason}")
                    return
                    break
            client_socket.send(f"未找到用户 {target_username}。".encode())
        else:
            client_socket.send("命令格式错误或密码错误。".encode())

    def send_chat_history(self, client_socket):
        try:
            with open(self.log_filename, "r") as f:
                chat_history = f.read()
            client_socket.send(chat_history.encode())
        except FileNotFoundError:
            client_socket.send("聊天记录不存在。".encode())

    def create_log_file(self):
        log_dir = "logs"
        if not os.path.exists(log_dir):
            os.makedirs(log_dir)
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        self.log_filename = os.path.join(log_dir, f"server_log_{timestamp}.txt")

def main():
    server = ChatServer()
    server.start()

if __name__ == "__main__":
    main()

客户端代码:

import socket
import threading
import sys

class ChatClient:
    def __init__(self):
        self.server_ip = "127.0.0.1"
        self.server_port = 12345
        self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.running = False

    def connect_to_server(self):
        try:
            self.client_socket.connect((self.server_ip, self.server_port))
            self.running = True
            threading.Thread(target=self.receive_messages).start()
            print("连接到服务器成功!")
        except ConnectionRefusedError:
            print("连接被拒绝。服务器可能未启动或者无法连接。")

    def receive_messages(self):
        while self.running:
            try:
                message = self.client_socket.recv(1024).decode()
                if not message:
                    print("与服务器的连接已断开。")
                    self.running = False
                    break  # 服务器关闭连接时退出循环
                print(message)
            except ConnectionResetError:
                print("与服务器的连接意外断开。")
                self.running = False
                break

    def send_message(self, message):
        try:
            self.client_socket.send(message.encode())
        except ConnectionAbortedError:
            print("与服务器的连接意外断开。")
            self.running = False

    def reconnect(self):
        self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect_to_server()

    def leave(self):
        self.running = False
        self.client_socket.close()
        print("已离开聊天室。")

def main():
    client = ChatClient()
    client.connect_to_server()
    while client.running:
        message = input()
        if message == "/leave":
            client.leave()
            sys.exit()
        elif message == "/reconnect":
            client.reconnect()
        else:
            client.send_message(message)

if __name__ == "__main__":
    main()
Jason990420
最佳答案

The attribute self.clients in target self.handle_client for each multiprocessing.Process is not the same variable and not sharing, so you will always get empty value, no matter how many clients connected, then no client will receive the broadcast message.

Similar case

from multiprocessing import Process

class Jobs():

    def __init__(self, n):
        self.values = {}
        for i in range(n):
            p = Process(target=self.job, args=(i,))
            p.start()
            p.join()
        print(self.values)

    def job(self, i):
        self.values[i] = i

if __name__ == '__main__':
    jobs = Jobs(5)
{}

Example Code (Maybe not a good method for your case)

from multiprocessing import Process,  Manager

class Jobs():

    def __init__(self, n, manager):
        self.values = manager.dict()
        for i in range(n):
            p = Process(target=self.job, args=(i,))
            p.start()
            p.join()
        print(self.values)

    def job(self, i):
        self.values[i] = i

if __name__ == '__main__':
    with Manager() as manager:
        jobs = Jobs(5, manager)
{0: 0, 1: 1, 2: 2, 3: 3, 4: 4}
3个月前 评论
讨论数量: 1
Jason990420

The attribute self.clients in target self.handle_client for each multiprocessing.Process is not the same variable and not sharing, so you will always get empty value, no matter how many clients connected, then no client will receive the broadcast message.

Similar case

from multiprocessing import Process

class Jobs():

    def __init__(self, n):
        self.values = {}
        for i in range(n):
            p = Process(target=self.job, args=(i,))
            p.start()
            p.join()
        print(self.values)

    def job(self, i):
        self.values[i] = i

if __name__ == '__main__':
    jobs = Jobs(5)
{}

Example Code (Maybe not a good method for your case)

from multiprocessing import Process,  Manager

class Jobs():

    def __init__(self, n, manager):
        self.values = manager.dict()
        for i in range(n):
            p = Process(target=self.job, args=(i,))
            p.start()
            p.join()
        print(self.values)

    def job(self, i):
        self.values[i] = i

if __name__ == '__main__':
    with Manager() as manager:
        jobs = Jobs(5, manager)
{0: 0, 1: 1, 2: 2, 3: 3, 4: 4}
3个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!