Python TCP多客户端服务端定向发消息后切换聊天室实现方法
核心问题梳理
你需要实现的TCP服务端分两个明确运行阶段:
- 专属握手阶段:客户端连接上报别名后,服务端给别名是Robot的客户端发送
VITAr、给别名是Iconet的客户端发送VITAi,等两个客户端都返回约定的yes响应后,自动切换到聊天室模式 - 聊天室阶段:接收所有客户端发送的消息并全量广播,支持客户端离线自动清理
你原有代码存在几个直接导致运行失败的问题:
- 赋值语法错误:判断握手响应后给标记位赋值误用了比较运算符
==,标记位永远为初始值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
相关产品推荐
相关产品推荐

