#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ P2P Chat Client 启动脚本 用法: python run_client.py --username USERNAME [--server HOST] [--port PORT] 示例: python run_client.py --username alice python run_client.py --username bob --server 192.168.1.100 --port 8888 """ import asyncio import argparse import logging import sys import uuid from typing import Optional, List from client.app import P2PClientApp from client.connection_manager import ConnectionState from shared.models import UserInfo, UserStatus, Message, MessageType from config import load_client_config, ClientConfig # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.StreamHandler(sys.stdout) ] ) logger = logging.getLogger(__name__) class ClientRunner: """客户端运行器""" def __init__(self, config: ClientConfig, username: str, display_name: str = ""): self.config = config self.username = username self.display_name = display_name or username self.client: Optional[P2PClientApp] = None self._running = False async def start(self) -> bool: """启动客户端""" self.client = P2PClientApp(self.config) # 创建用户信息 user_info = UserInfo( user_id=str(uuid.uuid4()), username=self.username, display_name=self.display_name, status=UserStatus.ONLINE ) # 注册回调 self.client.add_message_callback(self._on_message) self.client.add_state_callback(self._on_state_change) self.client.add_user_list_callback(self._on_user_list_update) logger.info("=" * 50) logger.info("P2P Chat Client 启动中...") logger.info(f"用户名: {self.username}") logger.info(f"服务器: {self.config.server_host}:{self.config.server_port}") logger.info("=" * 50) # 连接服务器 success = await self.client.start(user_info) if success: self._running = True logger.info("连接成功!") print("\n" + "=" * 50) print("欢迎使用 P2P Chat!") print("命令列表:") print(" /list - 查看在线用户") print(" /msg <用户> <消息> - 发送消息") print(" /file <用户> <路径> - 发送文件") print(" /call <用户> - 发起语音通话") print(" /endcall - 结束通话") print(" /stats - 查看统计信息") print(" /quit - 退出") print("=" * 50 + "\n") return True else: logger.error("连接失败") return False async def run_interactive(self) -> None: """运行交互式命令行""" while self._running: try: # 异步读取输入 line = await asyncio.get_event_loop().run_in_executor( None, input, f"[{self.username}] > " ) if not line.strip(): continue await self._process_command(line.strip()) except EOFError: break except KeyboardInterrupt: break except Exception as e: logger.error(f"命令处理错误: {e}") await self.stop() async def _process_command(self, line: str) -> None: """处理命令""" parts = line.split(maxsplit=2) cmd = parts[0].lower() if cmd == "/quit" or cmd == "/exit": self._running = False elif cmd == "/list": users = self.client.get_online_users() print(f"\n在线用户 ({len(users)}):") for user in users: print(f" - {user.display_name} ({user.username})") print() elif cmd == "/msg": if len(parts) < 3: print("用法: /msg <用户名> <消息>") return target = parts[1] message = parts[2] success = await self.client.send_text_message(target, message) if success: print(f"[发送给 {target}]: {message}") else: print(f"发送失败") elif cmd == "/file": if len(parts) < 3: print("用法: /file <用户名> <文件路径>") return target = parts[1] file_path = parts[2] print(f"正在发送文件: {file_path}") success = await self.client.send_file( target, file_path, progress_callback=lambda p: print(f"\r进度: {p.percentage:.1f}%", end="") ) print() if success: print("文件发送完成") else: print("文件发送失败") elif cmd == "/call": if len(parts) < 2: print("用法: /call <用户名>") return target = parts[1] success = await self.client.start_voice_call(target) if success: print(f"正在呼叫 {target}...") else: print("呼叫失败") elif cmd == "/endcall": self.client.end_voice_call() print("通话已结束") elif cmd == "/stats": stats = self.client.get_stats() print("\n统计信息:") print(f" 用户: {stats['user']}") print(f" 在线用户数: {stats['online_users']}") print(f" 聊天会话数: {stats['chat_sessions']}") print(f" 消息总数: {stats['total_messages']}") print(f" 活跃传输: {stats['active_transfers']}") print(f" 语音通话: {'是' if stats['voice_call_active'] else '否'}") print() elif cmd == "/refresh": await self.client.refresh_online_users() print("用户列表已刷新") elif cmd == "/help": print("\n命令列表:") print(" /list - 查看在线用户") print(" /msg <用户> <消息> - 发送消息") print(" /file <用户> <路径> - 发送文件") print(" /call <用户> - 发起语音通话") print(" /endcall - 结束通话") print(" /stats - 查看统计信息") print(" /refresh - 刷新用户列表") print(" /quit - 退出") print() else: print(f"未知命令: {cmd},输入 /help 查看帮助") async def stop(self) -> None: """停止客户端""" self._running = False if self.client: await self.client.stop() logger.info("客户端已关闭") def _on_message(self, message: Message) -> None: """消息回调""" if message.msg_type == MessageType.TEXT: content = message.payload.decode('utf-8') print(f"\n[来自 {message.sender_id}]: {content}") print(f"[{self.username}] > ", end="", flush=True) elif message.msg_type == MessageType.VOICE_CALL_REQUEST: print(f"\n来电: {message.sender_id} 正在呼叫你...") print("输入 /accept 接听 或 /reject 拒绝") print(f"[{self.username}] > ", end="", flush=True) def _on_state_change(self, state: ConnectionState, reason: Optional[str]) -> None: """状态变化回调""" if state == ConnectionState.DISCONNECTED: print(f"\n连接断开: {reason or '未知原因'}") self._running = False elif state == ConnectionState.RECONNECTING: print(f"\n正在重连...") def _on_user_list_update(self, users: List[UserInfo]) -> None: """用户列表更新回调""" logger.debug(f"用户列表更新: {len(users)} 用户在线") def parse_args() -> argparse.Namespace: """解析命令行参数""" parser = argparse.ArgumentParser( description='P2P Chat Client', formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument( '--username', '-u', type=str, required=True, help='用户名 (必填)' ) parser.add_argument( '--display-name', '-d', type=str, default="", help='显示名称 (默认与用户名相同)' ) parser.add_argument( '--server', '-s', type=str, default=None, help='服务器地址 (默认: 127.0.0.1)' ) parser.add_argument( '--port', '-p', type=int, default=None, help='服务器端口 (默认: 8888)' ) parser.add_argument( '--debug', action='store_true', help='启用调试模式' ) return parser.parse_args() async def main_async(args: argparse.Namespace) -> None: """异步主函数""" # 加载配置 config = load_client_config() # 命令行参数覆盖配置 if args.server: config.server_host = args.server if args.port: config.server_port = args.port # 创建并运行客户端 runner = ClientRunner(config, args.username, args.display_name) success = await runner.start() if success: await runner.run_interactive() def main() -> None: """主函数""" args = parse_args() # 设置日志级别 if args.debug: logging.getLogger().setLevel(logging.DEBUG) try: asyncio.run(main_async(args)) except KeyboardInterrupt: logger.info("客户端被用户中断") except Exception as e: logger.error(f"启动失败: {e}") sys.exit(1) if __name__ == "__main__": main()