GUI正常运行,可以连接到服务器

main
杨博文 3 weeks ago
parent 9404c9efb7
commit 83b215327d

@ -1,2 +1,243 @@
# chatProgram
# P2P Chat - 点对点通信应用
一个基于 Python 的 P2P 网络通信应用程序,支持文本消息、文件传输、图片分享和语音通话。
## 功能特性
- 文本消息通信
- 文件传输(支持断点续传)
- 图片传输与显示
- 音视频播放
- 语音聊天
- 局域网自动发现
- 离线消息缓存
## 环境要求
- Python 3.9+
- MySQL 5.7+(可选,用于持久化存储)
## 安装
### 1. 克隆项目
```bash
git clone <repository-url>
cd chatProgram
```
### 2. 创建虚拟环境
```bash
python -m venv venv
# Windows
venv\Scripts\activate
# Linux/Mac
source venv/bin/activate
```
### 3. 安装依赖
```bash
pip install -r requirements.txt
```
### 4. 系统依赖Linux
```bash
# Ubuntu/Debian
sudo apt install portaudio19-dev ffmpeg libopus-dev
# openEuler/CentOS
sudo dnf install portaudio-devel ffmpeg opus-devel
```
## 快速开始
### 启动服务器
```bash
# 基本启动
python run_server.py
# 指定地址和端口
python run_server.py --host 0.0.0.0 --port 8888
# 调试模式
python run_server.py --debug
```
### 启动客户端(命令行)
```bash
# 基本启动
python run_client.py --username alice
# 连接远程服务器
python run_client.py -u bob -s 192.168.1.100 -p 8888
# 带显示名称
python run_client.py -u alice -d "Alice Wang"
```
### 启动客户端GUI
```bash
# 基本启动
python run_client_gui.py
# 指定服务器
python run_client_gui.py --server 192.168.1.100 --port 8888
```
## 命令行客户端命令
连接成功后可使用以下命令:
| 命令 | 说明 |
|------|------|
| `/list` | 查看在线用户 |
| `/msg <用户> <消息>` | 发送消息 |
| `/file <用户> <路径>` | 发送文件 |
| `/call <用户>` | 发起语音通话 |
| `/endcall` | 结束通话 |
| `/stats` | 查看统计信息 |
| `/refresh` | 刷新用户列表 |
| `/help` | 查看帮助 |
| `/quit` | 退出 |
## 配置
### 环境变量
```bash
# 服务器配置
P2P_SERVER_HOST=0.0.0.0
P2P_SERVER_PORT=8888
P2P_MAX_CONNECTIONS=1000
# 数据库配置
P2P_DB_HOST=localhost
P2P_DB_PORT=3306
P2P_DB_NAME=p2p_chat
P2P_DB_USER=root
P2P_DB_PASSWORD=your_password
# 安全配置
P2P_USE_TLS=true
P2P_CERT_FILE=/path/to/cert.pem
P2P_KEY_FILE=/path/to/key.pem
```
### 配置文件
也可以直接修改 `config.py` 中的默认值。
## 部署到 openEuler 服务器
### 1. 安装依赖
```bash
sudo dnf update -y
sudo dnf install -y python3 python3-pip python3-devel
sudo dnf install -y portaudio-devel ffmpeg opus-devel mysql-server mysql-devel
```
### 2. 部署项目
```bash
mkdir -p /opt/p2p-chat
cd /opt/p2p-chat
# 上传项目文件
# scp -r ./* user@server:/opt/p2p-chat/
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
### 3. 配置防火墙
```bash
sudo firewall-cmd --permanent --add-port=8888/tcp
sudo firewall-cmd --permanent --add-port=8889/udp
sudo firewall-cmd --reload
```
### 4. 设置系统服务
创建 `/etc/systemd/system/p2p-chat.service`
```ini
[Unit]
Description=P2P Chat Server
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/p2p-chat
Environment=PATH=/opt/p2p-chat/venv/bin
ExecStart=/opt/p2p-chat/venv/bin/python run_server.py
Restart=always
[Install]
WantedBy=multi-user.target
```
启用服务:
```bash
sudo systemctl daemon-reload
sudo systemctl enable p2p-chat
sudo systemctl start p2p-chat
sudo systemctl status p2p-chat
```
## 项目结构
```
chatProgram/
├── client/ # 客户端模块
│ ├── ui/ # GUI 组件
│ ├── app.py # 客户端应用
│ ├── connection_manager.py
│ ├── file_transfer.py
│ ├── image_processor.py
│ ├── media_player.py
│ └── voice_chat.py
├── server/ # 服务器模块
│ ├── database.py
│ ├── relay_server.py
│ └── repositories.py
├── shared/ # 共享模块
│ ├── message_handler.py
│ ├── models.py
│ └── security.py
├── tests/ # 测试
├── config.py # 配置
├── run_server.py # 服务器启动脚本
├── run_client.py # 命令行客户端启动脚本
├── run_client_gui.py # GUI 客户端启动脚本
└── requirements.txt # 依赖
```
## 测试
```bash
# 运行所有测试
pytest
# 运行特定测试
pytest tests/test_relay_server.py -v
# 运行集成测试
pytest tests/test_integration.py -v
```
## License
MIT

@ -106,7 +106,8 @@ class P2PClientApp:
logger.info(f"Client started for user: {user_info.username}")
# 初始化语音聊天模块
self._voice_chat = VoiceChatModule(self._connection_manager)
self._voice_chat = VoiceChatModule(self.config)
self._voice_chat.set_send_message_callback(self._send_message_async)
# 请求在线用户列表
await self._request_online_users()

@ -6,9 +6,14 @@
需求: 4.1, 4.3, 4.6, 5.2, 5.3, 5.4, 5.6
"""
from __future__ import annotations
import logging
import os
from typing import Optional, Dict
from typing import Optional, Dict, TYPE_CHECKING
if TYPE_CHECKING:
from typing import Type
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
@ -422,7 +427,7 @@ class FileTransferWidget(QWidget):
dialog = ImagePreviewDialog(image_path, self)
dialog.exec()
def create_image_thumbnail(self, image_path: str) -> Optional[ImageThumbnail]:
def create_image_thumbnail(self, image_path: str) -> Optional["ImageThumbnail"]:
"""
创建图片缩略图组件

@ -328,30 +328,21 @@ class MainWindow(QMainWindow):
def closeEvent(self, event: QCloseEvent) -> None:
"""
窗口关闭事件
实现后台运行 (需求 9.5)
WHEN 应用程序最小化 THEN P2P_Client SHALL 保持后台运行并继续接收消息
"""
# 如果有系统托盘,最小化到托盘而不是关闭
if self._system_tray and self._system_tray.is_available():
event.ignore()
self.hide()
self._system_tray.show_message("P2P通信", "应用程序已最小化到系统托盘")
# 确认退出
reply = QMessageBox.question(
self,
"确认退出",
"确定要退出应用程序吗?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
self._cleanup()
event.accept()
else:
# 确认退出
reply = QMessageBox.question(
self,
"确认退出",
"确定要退出应用程序吗?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
self._cleanup()
event.accept()
else:
event.ignore()
event.ignore()
def resizeEvent(self, event) -> None:
"""

@ -0,0 +1,60 @@
2025-12-26 22:45:58,501 - client.ui.login_dialog - INFO - LoginDialog initialized
2025-12-26 22:46:20,014 - client.ui.login_dialog - INFO - User registered and logged in: ybw
2025-12-26 22:46:21,293 - client.ui.main_window - INFO - MainWindow initialized
2025-12-26 22:46:21,293 - client.ui.main_window - INFO - Current user set: ybw
2025-12-26 22:46:21,333 - client.ui.system_tray - INFO - SystemTrayManager initialized
2025-12-26 22:46:21,441 - client.connection_manager - INFO - ConnectionManager initialized
2025-12-26 22:46:21,442 - client.file_transfer - INFO - FileTransferModule initialized
2025-12-26 22:46:21,443 - client.image_processor - INFO - ImageProcessor initialized
2025-12-26 22:46:21,443 - client.media_player - INFO - AudioPlayer initialized
2025-12-26 22:46:21,443 - client.media_player - INFO - VideoPlayer initialized
2025-12-26 22:46:21,443 - client.media_player - INFO - MediaPlayer initialized
2025-12-26 22:46:21,443 - client.app - INFO - P2PClientApp initialized
2025-12-26 22:46:21,452 - client.connection_manager - INFO - Connection state changed: disconnected -> connecting (Connecting to server)
2025-12-26 22:46:21,452 - client.app - INFO - Connection state changed: connecting (Connecting to server)
2025-12-26 22:46:23,591 - client.connection_manager - INFO - Connection state changed: connecting -> error (Network error: [WinError 1225] 远程计算机拒绝网络连接。)
2025-12-26 22:46:23,591 - client.app - INFO - Connection state changed: error (Network error: [WinError 1225] 远程计算机拒绝网络连接。)
2025-12-26 22:46:23,591 - client.app - ERROR - Failed to start client: Failed to connect: [WinError 1225] 远程计算机拒绝网络连接。
2025-12-26 22:48:08,584 - client.ui.login_dialog - INFO - LoginDialog initialized
2025-12-26 22:48:17,200 - client.ui.login_dialog - INFO - User registered and logged in: ybw
2025-12-26 22:48:17,787 - client.ui.main_window - INFO - MainWindow initialized
2025-12-26 22:48:17,787 - client.ui.main_window - INFO - Current user set: ybw
2025-12-26 22:48:17,802 - client.ui.system_tray - INFO - SystemTrayManager initialized
2025-12-26 22:48:17,871 - client.connection_manager - INFO - ConnectionManager initialized
2025-12-26 22:48:17,873 - client.file_transfer - INFO - FileTransferModule initialized
2025-12-26 22:48:17,873 - client.image_processor - INFO - ImageProcessor initialized
2025-12-26 22:48:17,873 - client.media_player - INFO - AudioPlayer initialized
2025-12-26 22:48:17,874 - client.media_player - INFO - VideoPlayer initialized
2025-12-26 22:48:17,874 - client.media_player - INFO - MediaPlayer initialized
2025-12-26 22:48:17,874 - client.app - INFO - P2PClientApp initialized
2025-12-26 22:48:17,881 - client.connection_manager - INFO - Connection state changed: disconnected -> connecting (Connecting to server)
2025-12-26 22:48:17,881 - client.app - INFO - Connection state changed: connecting (Connecting to server)
2025-12-26 22:48:17,925 - client.connection_manager - INFO - Connection state changed: connecting -> connected (Connected to server)
2025-12-26 22:48:17,926 - client.app - INFO - Connection state changed: connected (Connected to server)
2025-12-26 22:48:17,927 - client.connection_manager - INFO - LAN listener started on port 8889
2025-12-26 22:48:17,927 - client.connection_manager - INFO - Connected to server 113.45.148.222:8888
2025-12-26 22:48:17,928 - client.app - INFO - Client started for user: ybw
2025-12-26 22:48:17,928 - client.app - ERROR - Failed to start client: 'ConnectionManager' object has no attribute 'audio_sample_rate'
2025-12-26 22:55:46,116 - client.ui.login_dialog - INFO - LoginDialog initialized
2025-12-26 22:56:08,430 - client.ui.login_dialog - INFO - User logged in: ybw
2025-12-26 22:56:08,969 - client.ui.main_window - INFO - MainWindow initialized
2025-12-26 22:56:08,969 - client.ui.main_window - INFO - Current user set: ybw
2025-12-26 22:56:08,982 - client.ui.system_tray - INFO - SystemTrayManager initialized
2025-12-26 22:56:09,057 - client.connection_manager - INFO - ConnectionManager initialized
2025-12-26 22:56:09,059 - client.file_transfer - INFO - FileTransferModule initialized
2025-12-26 22:56:09,061 - client.image_processor - INFO - ImageProcessor initialized
2025-12-26 22:56:09,061 - client.media_player - INFO - AudioPlayer initialized
2025-12-26 22:56:09,062 - client.media_player - INFO - VideoPlayer initialized
2025-12-26 22:56:09,062 - client.media_player - INFO - MediaPlayer initialized
2025-12-26 22:56:09,062 - client.app - INFO - P2PClientApp initialized
2025-12-26 22:56:09,067 - client.connection_manager - INFO - Connection state changed: disconnected -> connecting (Connecting to server)
2025-12-26 22:56:09,067 - client.app - INFO - Connection state changed: connecting (Connecting to server)
2025-12-26 22:56:09,109 - client.connection_manager - INFO - Connection state changed: connecting -> connected (Connected to server)
2025-12-26 22:56:09,109 - client.app - INFO - Connection state changed: connected (Connected to server)
2025-12-26 22:56:09,110 - client.connection_manager - INFO - LAN listener started on port 8889
2025-12-26 22:56:09,110 - client.connection_manager - INFO - Connected to server 113.45.148.222:8888
2025-12-26 22:56:09,111 - client.app - INFO - Client started for user: ybw
2025-12-26 22:56:09,111 - client.voice_chat - INFO - VoiceChatModule initialized
2025-12-26 22:56:09,112 - __main__ - INFO - Connected to server
2025-12-26 22:56:09,132 - client.app - INFO - Online users updated: 1 users
2025-12-26 22:56:09,133 - __main__ - INFO - Online users: 1

@ -29,7 +29,7 @@ class ServerConfig:
@dataclass
class ClientConfig:
"""客户端配置"""
server_host: str = "127.0.0.1"
server_host: str = "113.45.148.222"
server_port: int = 8888
# LAN discovery

@ -0,0 +1,470 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
P2P Chat Client GUI 启动脚本
用法:
python run_client_gui.py [--server HOST] [--port PORT]
示例:
python run_client_gui.py
python run_client_gui.py --server 192.168.1.100 --port 8888
"""
import asyncio
import argparse
import logging
import sys
import os
from typing import Optional, List
# 确保能找到模块
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from PyQt6.QtWidgets import QApplication, QMessageBox, QSplashScreen
from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal, QObject
from PyQt6.QtGui import QPixmap, QFont
from client.app import P2PClientApp
from client.connection_manager import ConnectionState
from client.ui import (
MainWindow, LoginDialog, ChatWidget, ContactListWidget,
FileTransferWidget, MediaPlayerWidget, VoiceCallWidget, SystemTrayManager
)
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),
logging.FileHandler('client_gui.log', encoding='utf-8')
]
)
logger = logging.getLogger(__name__)
class AsyncWorker(QThread):
"""异步工作线程,用于运行 asyncio 事件循环"""
connected = pyqtSignal(bool, str) # (success, message)
disconnected = pyqtSignal(str) # reason
message_received = pyqtSignal(object) # Message
user_list_updated = pyqtSignal(list) # List[UserInfo]
state_changed = pyqtSignal(str, str) # (state, reason)
def __init__(self, client: P2PClientApp, user_info: UserInfo):
super().__init__()
self.client = client
self.user_info = user_info
self._running = False
self._loop: Optional[asyncio.AbstractEventLoop] = None
def run(self):
"""运行异步事件循环"""
self._loop = asyncio.new_event_loop()
asyncio.set_event_loop(self._loop)
self._running = True
try:
# 连接到服务器
success = self._loop.run_until_complete(
self.client.start(self.user_info)
)
if success:
self.connected.emit(True, "连接成功")
# 设置回调
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)
# 保持运行
while self._running:
self._loop.run_until_complete(asyncio.sleep(0.1))
else:
self.connected.emit(False, "连接失败")
except Exception as e:
logger.error(f"Worker error: {e}")
self.connected.emit(False, str(e))
finally:
self._loop.close()
def stop(self):
"""停止工作线程"""
self._running = False
if self._loop and self._loop.is_running():
self._loop.call_soon_threadsafe(self._loop.stop)
def _on_message(self, message: Message):
"""消息回调"""
self.message_received.emit(message)
def _on_state_change(self, state: ConnectionState, reason: Optional[str]):
"""状态变化回调"""
self.state_changed.emit(state.value, reason or "")
if state == ConnectionState.DISCONNECTED:
self.disconnected.emit(reason or "连接断开")
def _on_user_list(self, users: List[UserInfo]):
"""用户列表更新回调"""
self.user_list_updated.emit(users)
def send_message(self, peer_id: str, content: str) -> bool:
"""发送消息(线程安全)"""
if self._loop and self._running:
future = asyncio.run_coroutine_threadsafe(
self.client.send_text_message(peer_id, content),
self._loop
)
try:
return future.result(timeout=5.0)
except Exception as e:
logger.error(f"Send message error: {e}")
return False
return False
def send_file(self, peer_id: str, file_path: str) -> bool:
"""发送文件(线程安全)"""
if self._loop and self._running:
future = asyncio.run_coroutine_threadsafe(
self.client.send_file(peer_id, file_path),
self._loop
)
try:
return future.result(timeout=300.0) # 5分钟超时
except Exception as e:
logger.error(f"Send file error: {e}")
return False
return False
def refresh_users(self):
"""刷新用户列表(线程安全)"""
if self._loop and self._running:
asyncio.run_coroutine_threadsafe(
self.client.refresh_online_users(),
self._loop
)
class P2PChatGUI(QObject):
"""P2P 聊天 GUI 应用程序"""
def __init__(self, config: ClientConfig):
super().__init__()
self.config = config
self.app: Optional[QApplication] = None
self.main_window: Optional[MainWindow] = None
self.client: Optional[P2PClientApp] = None
self.worker: Optional[AsyncWorker] = None
self._current_user: Optional[UserInfo] = None
self._current_chat_peer: Optional[str] = None
def run(self) -> int:
"""运行 GUI 应用程序"""
# 创建 Qt 应用
self.app = QApplication(sys.argv)
self.app.setApplicationName("P2P Chat")
self.app.setApplicationVersion("0.1.0")
# 设置默认字体
font = QFont("Microsoft YaHei", 10)
self.app.setFont(font)
# 设置样式
self.app.setStyle("Fusion")
# 应用退出时清理资源
self.app.aboutToQuit.connect(self.cleanup)
# 显示登录对话框
if not self._show_login():
return 0
# 创建主窗口
self._create_main_window()
# 连接到服务器
self._connect_to_server()
# 运行应用
return self.app.exec()
def _show_login(self) -> bool:
"""显示登录对话框"""
dialog = LoginDialog()
# 设置服务器地址
dialog._login_server_input.setText(
f"{self.config.server_host}:{self.config.server_port}"
)
dialog._reg_server_input.setText(
f"{self.config.server_host}:{self.config.server_port}"
)
if dialog.exec():
self._current_user = dialog.get_user_info()
host, port = dialog.get_server_address()
self.config.server_host = host
self.config.server_port = port
return True
return False
def _create_main_window(self):
"""创建主窗口"""
self.main_window = MainWindow(self.config)
if self._current_user:
self.main_window.set_current_user(self._current_user)
# 设置系统托盘
try:
tray = SystemTrayManager(self.main_window)
self.main_window.set_system_tray(tray)
except Exception as e:
logger.warning(f"Failed to create system tray: {e}")
# 连接菜单动作
self._connect_menu_actions()
self.main_window.show()
def _connect_menu_actions(self):
"""连接菜单动作"""
# 发送文件
self.main_window._send_file_action.triggered.connect(self._on_send_file)
# 发送图片
self.main_window._send_image_action.triggered.connect(self._on_send_image)
# 语音通话
self.main_window._voice_call_action.triggered.connect(self._on_voice_call)
def _connect_to_server(self):
"""连接到服务器"""
if not self._current_user:
return
# 更新状态
self.main_window.connection_state_changed.emit("连接中...")
# 创建客户端
self.client = P2PClientApp(self.config)
# 创建工作线程
self.worker = AsyncWorker(self.client, self._current_user)
self.worker.connected.connect(self._on_connected)
self.worker.disconnected.connect(self._on_disconnected)
self.worker.message_received.connect(self._on_message_received)
self.worker.user_list_updated.connect(self._on_user_list_updated)
self.worker.state_changed.connect(self._on_state_changed)
# 启动工作线程
self.worker.start()
def _on_connected(self, success: bool, message: str):
"""连接结果回调"""
if success:
self.main_window.connection_state_changed.emit("已连接")
self.main_window._statusbar.showMessage(f"已连接到服务器")
logger.info("Connected to server")
# 刷新用户列表
QTimer.singleShot(500, self._refresh_users)
else:
self.main_window.connection_state_changed.emit("连接失败")
QMessageBox.critical(
self.main_window,
"连接失败",
f"无法连接到服务器: {message}"
)
def _on_disconnected(self, reason: str):
"""断开连接回调"""
self.main_window.connection_state_changed.emit("已断开")
self.main_window._statusbar.showMessage(f"连接断开: {reason}")
# 询问是否重连
reply = QMessageBox.question(
self.main_window,
"连接断开",
f"与服务器的连接已断开: {reason}\n\n是否尝试重新连接?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
self._connect_to_server()
def _on_message_received(self, message: Message):
"""消息接收回调"""
if message.msg_type == MessageType.TEXT:
content = message.payload.decode('utf-8')
sender = message.sender_id
# 显示通知
if self.main_window.isMinimized() or not self.main_window.isActiveWindow():
self.main_window.show_notification(
f"来自 {sender} 的消息",
content[:50] + ("..." if len(content) > 50 else "")
)
# 更新状态栏
self.main_window._statusbar.showMessage(
f"收到来自 {sender} 的消息", 3000
)
logger.info(f"Message from {sender}: {content[:50]}...")
elif message.msg_type == MessageType.VOICE_CALL_REQUEST:
sender = message.sender_id
self.main_window.show_notification(
"来电",
f"{sender} 正在呼叫你"
)
def _on_user_list_updated(self, users: List[UserInfo]):
"""用户列表更新回调"""
logger.info(f"Online users: {len(users)}")
self.main_window._statusbar.showMessage(
f"在线用户: {len(users)}", 3000
)
def _on_state_changed(self, state: str, reason: str):
"""状态变化回调"""
state_text = {
"disconnected": "已断开",
"connecting": "连接中...",
"connected": "已连接",
"reconnecting": "重连中..."
}.get(state, state)
self.main_window.connection_state_changed.emit(state_text)
def _refresh_users(self):
"""刷新用户列表"""
if self.worker:
self.worker.refresh_users()
def _on_send_file(self):
"""发送文件"""
from PyQt6.QtWidgets import QFileDialog
file_path, _ = QFileDialog.getOpenFileName(
self.main_window,
"选择文件",
"",
"所有文件 (*.*)"
)
if file_path and self._current_chat_peer:
self.main_window._statusbar.showMessage(f"正在发送文件...")
# TODO: 实现文件发送
def _on_send_image(self):
"""发送图片"""
from PyQt6.QtWidgets import QFileDialog
file_path, _ = QFileDialog.getOpenFileName(
self.main_window,
"选择图片",
"",
"图片文件 (*.png *.jpg *.jpeg *.gif *.bmp)"
)
if file_path and self._current_chat_peer:
self.main_window._statusbar.showMessage(f"正在发送图片...")
# TODO: 实现图片发送
def _on_voice_call(self):
"""发起语音通话"""
if self._current_chat_peer:
self.main_window._statusbar.showMessage(f"正在呼叫...")
# TODO: 实现语音通话
def cleanup(self):
"""清理资源"""
logger.info("Cleaning up resources...")
if self.worker:
self.worker.stop()
self.worker.wait(3000)
self.worker = None
if self.client:
# 在新的事件循环中停止客户端
loop = asyncio.new_event_loop()
try:
loop.run_until_complete(self.client.stop())
except Exception as e:
logger.error(f"Error stopping client: {e}")
finally:
loop.close()
self.client = None
logger.info("Cleanup completed")
def parse_args() -> argparse.Namespace:
"""解析命令行参数"""
parser = argparse.ArgumentParser(
description='P2P Chat Client (GUI)',
formatter_class=argparse.RawDescriptionHelpFormatter
)
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()
def main() -> int:
"""主函数"""
args = parse_args()
# 设置日志级别
if args.debug:
logging.getLogger().setLevel(logging.DEBUG)
# 加载配置
config = load_client_config()
# 命令行参数覆盖配置
if args.server:
config.server_host = args.server
if args.port:
config.server_port = args.port
# 创建并运行 GUI
gui = P2PChatGUI(config)
try:
exit_code = gui.run()
return exit_code
except KeyboardInterrupt:
logger.info("Application interrupted")
gui.cleanup()
return 0
except Exception as e:
logger.error(f"Application error: {e}")
gui.cleanup()
return 1
if __name__ == "__main__":
sys.exit(main())
Loading…
Cancel
Save