""" 客户端核心 —— 负责与服务器的 TCP 通信 """ import socket import struct import threading import json import time from datetime import datetime class ChatClient: def __init__(self, host='127.0.0.1', port=8888): self.host = host self.port = port self.sock = None self.connected = False self._running = False self._lock = threading.Lock() # 用户信息(登录成功后填充) self.user_id = None self.username = None self.nickname = None # 在线用户缓存 {user_id: username} self.online_users = {} # 回调(由 ChatApplication 设置) self.on_message = None # fn(message_dict) self.on_connected = None # fn(bool) # ── 连接管理 ────────────────────────────────────────── def connect(self): try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(5) self.sock.connect((self.host, self.port)) self.sock.settimeout(5) # 短超时,让 recv_loop 能及时响应 _running=False self.connected = True self._running = True threading.Thread(target=self._recv_loop, daemon=True).start() threading.Thread(target=self._heartbeat_loop, daemon=True).start() return True, "连接成功" except Exception as e: return False, f"连接失败: {e}" def disconnect(self): if not self._running: return self._running = False self.connected = False try: self.sock.close() except Exception: pass if self.on_connected: self.on_connected(False) # ── 收发 ────────────────────────────────────────────── def _recv_exact(self, n): buf = b'' while len(buf) < n: if not self._running: return None try: chunk = self.sock.recv(n - len(buf)) if not chunk: return None buf += chunk except socket.timeout: continue return buf def _recv_loop(self): while self._running: try: header = self._recv_exact(4) if not header: break msg_len = struct.unpack('>I', header)[0] data = self._recv_exact(msg_len) if not data: break try: msg = json.loads(data.decode('utf-8')) self._handle_incoming(msg) except json.JSONDecodeError: print("[client] 收到无效JSON") except socket.timeout: continue except Exception as e: if self._running: print(f"[client] 接收出错: {e}") break self.disconnect() def send(self, message): if not self.connected: return False, "未连接到服务器" try: data = json.dumps(message, ensure_ascii=False).encode('utf-8') with self._lock: self.sock.sendall(struct.pack('>I', len(data)) + data) return True, "ok" except Exception as e: return False, str(e) def _heartbeat_loop(self): while self._running: time.sleep(25) if self._running: self.send({'type': 'heartbeat', 'timestamp': time.time()}) # ── 消息处理 ────────────────────────────────────────── def _handle_incoming(self, msg): t = msg.get('type') if t == 'login_response' and msg.get('success'): info = msg.get('user_info', {}) self.user_id = info.get('id') self.username = info.get('username') self.nickname = info.get('nickname', self.username) elif t == 'user_status': uid = msg.get('user_id') if msg.get('is_online'): self.online_users[uid] = msg.get('username') else: self.online_users.pop(uid, None) elif t == 'heartbeat_response': return # 静默处理 if self.on_message: self.on_message(msg) # ── 业务接口 ────────────────────────────────────────── def login(self, username, password): return self.send({'type': 'login', 'username': username, 'password': password}) def register(self, username, password, nickname=''): return self.send({'type': 'register', 'username': username, 'password': password, 'nickname': nickname}) def send_chat(self, receiver, content): return self.send({'type': 'chat', 'receiver': receiver, 'content': content}) def send_group_chat(self, group_id, content): return self.send({'type': 'group_chat', 'group_id': group_id, 'content': content}) def get_users(self): return self.send({'type': 'get_users'}) def search_users(self, keyword): return self.send({'type': 'search_users', 'keyword': keyword}) def add_friend(self, username): return self.send({'type': 'add_friend', 'username': username}) def remove_friend(self, friend_id): return self.send({'type': 'remove_friend', 'friend_id': friend_id}) def get_friends(self): return self.send({'type': 'get_friends'}) def get_history(self, friend_username, limit=50, offset=0): return self.send({'type': 'get_history', 'username': friend_username, 'limit': limit, 'offset': offset}) def get_group_history(self, group_id, limit=50, offset=0): return self.send({'type': 'get_group_history', 'group_id': group_id, 'limit': limit, 'offset': offset}) def create_group(self, group_name): return self.send({'type': 'create_group', 'group_name': group_name}) def get_groups(self): return self.send({'type': 'get_groups'}) def get_all_groups(self): return self.send({'type': 'get_all_groups'}) def join_group(self, group_id): return self.send({'type': 'join_group', 'group_id': group_id}) def get_group_members(self, group_id): return self.send({'type': 'get_group_members', 'group_id': group_id}) def leave_group(self, group_id): return self.send({'type': 'leave_group', 'group_id': group_id}) def invite_to_group(self, group_id, username): return self.send({'type': 'invite_to_group', 'group_id': group_id, 'username': username}) def search_messages(self, chat_type, target_id, keyword): return self.send({'type': 'search_messages', 'chat_type': chat_type, 'target_id': target_id, 'keyword': keyword}) def get_all_history(self, limit=200): return self.send({'type': 'get_all_history', 'limit': limit}) def get_recent_history(self, chat_type, target_id, limit=50): return self.send({'type': 'get_recent_history', 'chat_type': chat_type, 'target_id': target_id, 'limit': limit}) def change_username(self, new_username): return self.send({'type': 'change_username', 'new_username': new_username}) def change_nickname(self, new_nickname): return self.send({'type': 'change_nickname', 'new_nickname': new_nickname}) def change_password(self, old_password, new_password): return self.send({'type': 'change_password', 'old_password': old_password, 'new_password': new_password}) def send_chat_image(self, receiver, filename, file_data_b64): return self.send({'type': 'chat_image', 'receiver': receiver, 'filename': filename, 'data': file_data_b64}) def send_group_chat_image(self, group_id, filename, file_data_b64): return self.send({'type': 'group_chat_image', 'group_id': group_id, 'filename': filename, 'data': file_data_b64})