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

Python TCP多客户端服务端定向发消息后切换聊天室实现方法

核心问题梳理

你需要实现的TCP服务端分两个明确运行阶段:

  1. 专属握手阶段:客户端连接上报别名后,服务端给别名是Robot的客户端发送VITAr、给别名是Iconet的客户端发送VITAi,等两个客户端都返回约定的yes响应后,自动切换到聊天室模式
  2. 聊天室阶段:接收所有客户端发送的消息并全量广播,支持客户端离线自动清理

你原有代码存在几个直接导致运行失败的问题:

  • 赋值语法错误:判断握手响应后给标记位赋值误用了比较运算符==,标记位永远为初始值False
  • 线程逻辑混乱:每个客户端连接启动的处理线程里,循环遍历所有客户端发握手消息,会导致重复发包、recv阻塞死锁
  • 逻辑判断错误:逻辑与运算误用位运算符&,且聊天室逻辑函数从未被调用,永远进不了聊天阶段
  • 客户端识别不可靠:靠客户端列表索引判断身份,只要客户端连接顺序变化就会发错专属消息
  • 多线程安全问题:多线程同时修改共享的客户端列表、标记位没有加锁,会出现数据错乱
  • 客户端逻辑缺失:原有客户端没有处理VITAr/VITAi握手消息的响应逻辑,会直接卡在握手阶段

修复后可直接运行的代码

服务端代码

import threading 
import socket 

PORT = 1026
SERVER = socket.gethostbyname(socket.gethostname())
ADDR = (SERVER,PORT)
FORMAT = "utf-8"
HEADER = 1024
DISCONNECT_MESSAGE = "END_CYCLE"
EXPECT_RES = "yes"
# 线程锁,保证共享变量操作安全
lock = threading.Lock()
# 两个指定客户端的握手完成标记
robot_handshake_ok = False
iconet_handshake_ok = False
# 聊天室阶段标记
in_chat_mode = False

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(ADDR)
# 用字典存储客户端连接和对应别名,避免双列表索引错位问题
client_map = {}

def broadcast(broadcast_message):
    """广播消息给所有在线客户端"""
    for client in list(client_map.keys()):
        try:
            client.send(broadcast_message)
        except:
            client.close()
            if client in client_map:
                del client_map[client]

def handle_client(client, addr):
    global robot_handshake_ok, iconet_handshake_ok, in_chat_mode
    print(f"[新连接] {addr} 已接入")
    alias = ""
    try:
        # 第一步:接收客户端上报的别名
        client.send("NAME?".encode(FORMAT))
        alias = client.recv(HEADER).decode(FORMAT).strip()
        with lock:
            client_map[client] = alias
        print(f"客户端别名:{alias}")

        # 第二步:按别名发送专属握手消息,等待响应
        if alias == "Robot":
            client.send("VITAr".encode(FORMAT))
            res = client.recv(HEADER).decode(FORMAT).strip()
            print(f"收到Robot握手响应:{res}")
            if res == EXPECT_RES:
                with lock:
                    robot_handshake_ok = True
        elif alias == "Iconet":
            client.send("VITAi".encode(FORMAT))
            res = client.recv(HEADER).decode(FORMAT).strip()
            print(f"收到Iconet握手响应:{res}")
            if res == EXPECT_RES:
                with lock:
                    iconet_handshake_ok = True

        # 检查是否满足进入聊天室的条件
        with lock:
            if robot_handshake_ok and iconet_handshake_ok and not in_chat_mode:
                in_chat_mode = True
                broadcast("系统:所有指定客户端握手完成,进入聊天室模式".encode(FORMAT))
                print("[状态] 已进入聊天室模式")

        # 第三步:聊天室消息处理循环
        while True:
            # 未进入聊天室时短暂等待,避免提前读取消息
            while not in_chat_mode:
                threading.Event().wait(0.1)
            try:
                msg = client.recv(HEADER)
                if not msg:
                    break
                if msg.decode(FORMAT).strip() == DISCONNECT_MESSAGE:
                    break
                # 广播格式化后的消息
                broadcast_msg = f"[{client_map[client]}]: {msg.decode(FORMAT)}"
                broadcast(broadcast_msg.encode(FORMAT))
                print(broadcast_msg)
            except ConnectionResetError:
                break
    except Exception as e:
        print(f"客户端{addr}连接异常:{e}")
    finally:
        # 客户端离线后的资源清理
        with lock:
            if client in client_map:
                leave_alias = client_map[client]
                del client_map[client]
                if leave_alias == "Robot":
                    robot_handshake_ok = False
                elif leave_alias == "Iconet":
                    iconet_handshake_ok = False
                in_chat_mode = robot_handshake_ok and iconet_handshake_ok
        client.close()
        broadcast(f"系统:{alias} 已离开聊天室".encode(FORMAT))
        print(f"[离线] {alias} 已断开连接")

def start():
    server.listen()
    print(f"[监听中] 服务端运行在 {SERVER}:{PORT}")
    while True:
        client, addr = server.accept()
        # 每个客户端启动独立守护线程处理
        thread = threading.Thread(target=handle_client, args=(client, addr), daemon=True)
        thread.start()
        print(f"[在线数] 当前共 {threading.active_count()-1} 个客户端在线")

if __name__ == "__main__":
    print('[启动中] 服务端正在启动')
    start()

客户端代码

import threading
import socket

# 启动时分别输入Robot、Iconet启动两个对应客户端
name = input('输入你的别名(Robot/Iconet):')
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 跨机器测试时把127.0.0.1替换为服务端实际IP
client.connect(('127.0.0.1', 1026))

def client_receive():
    while True:
        try:
            message = client.recv(1024).decode('utf-8')
            if not message:
                print("服务端已断开连接")
                client.close()
                break
            if message == "NAME?":
                client.send(name.encode('utf-8'))
            # 收到专属握手消息自动返回确认响应
            elif message in ("VITAr", "VITAi"):
                print(f"收到服务端握手消息:{message},自动返回确认")
                client.send("yes".encode('utf-8'))
            else:
                print(message)
        except Exception as e:
            print(f'连接出错:{e}')
            client.close()
            break

def client_send():
    while True:
        try:
            content = input("")
            if content == 'quit':
                client.send("END_CYCLE".encode('utf-8'))
                client.close()
                break
            client.send(content.encode('utf-8'))
        except:
            break

if __name__ == "__main__":
    receive_thread = threading.Thread(target=client_receive, daemon=True)
    receive_thread.start()
    send_thread = threading.Thread(target=client_send, daemon=True)
    send_thread.start()
    send_thread.join()

运行步骤
  • 先启动服务端,会自动监听本机1026端口
  • 启动第一个客户端,输入别名Robot,会自动完成专属握手
  • 启动第二个客户端,输入别名Iconet,两个客户端都完成握手后,所有端会收到进入聊天室的系统提示
  • 之后任意客户端发送的消息会同步广播给所有在线端,输入quit即可主动退出聊天室

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 23:57:17