1. 项目概述:基于PyQt5的局域网即时通讯工具
最近用PyQt5做了个挺有意思的小工具——一个纯局域网的即时通讯软件。这个ChatAPP最大的特点就是完全基于WiFi网络运行,不需要连接外网,特别适合办公室内部、学校机房或者家庭局域网环境下的快速通讯。代码全部用Python实现,核心功能加起来不到500行,但实现了消息收发、用户列表更新、简单的聊天记录保存等实用功能。
传统即时通讯工具大多依赖中心服务器转发消息,而这个方案采用P2P(点对点)架构。每个客户端同时充当服务器角色,通过UDP广播实现用户发现,TCP连接保障消息可靠传输。这种设计既避免了单点故障,又能在没有互联网的环境下正常工作。实测在百兆局域网内,消息延迟可以控制在50ms以内,完全满足日常通讯需求。
2. 技术架构解析
2.1 网络通信设计
核心通信模块采用双协议组合方案:
- UDP广播:用于用户发现和心跳维持
- 端口号:37020(可自定义)
- 广播地址:255.255.255.255
- 心跳间隔:5秒
- TCP直连:用于实际消息传输
- 端口范围:37021-37030
- 采用多线程处理并发连接
python复制# UDP广播示例代码
class BroadcastThread(QThread):
def run(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
while True:
sock.sendto(b'HEARTBEAT', ('255.255.255.255', 37020))
time.sleep(5)
2.2 PyQt5界面实现
主界面采用经典的三栏式布局:
- 左侧:用户列表(QListView)
- 中间:消息显示区域(QTextBrowser)
- 底部:消息输入框+发送按钮(QLineEdit+QPushButton)
python复制# 界面初始化示例
self.user_list = QListView()
self.message_area = QTextBrowser()
self.input_box = QLineEdit()
self.send_btn = QPushButton("发送")
# 布局设置
layout = QHBoxLayout()
left_panel = QVBoxLayout()
left_panel.addWidget(QLabel("在线用户"))
left_panel.addWidget(self.user_list)
layout.addLayout(left_panel, 1)
right_panel = QVBoxLayout()
right_panel.addWidget(self.message_area, 4)
right_panel.addWidget(self.input_box, 1)
right_panel.addWidget(self.send_btn)
layout.addLayout(right_panel, 3)
3. 核心功能实现细节
3.1 用户发现机制
当客户端启动时:
- 立即发送UDP广播宣告自身存在
- 持续监听37020端口的心跳包
- 维护一个在线用户字典,结构如下:
python复制{ "user1": {"ip": "192.168.1.100", "last_seen": 1625090000}, "user2": {"ip": "192.168.1.101", "last_seen": 1625090005} } - 超过15秒未收到心跳的用户自动从列表移除
3.2 消息传输流程
完整消息交互过程:
- 发送方选择接收用户
- 建立TCP连接到目标IP的37021端口
- 发送JSON格式的消息包:
json复制{ "from": "user1", "to": "user2", "time": 1625090000, "content": "你好,在吗?", "type": "text" } - 接收方返回ACK确认
- 连接关闭
重要提示:TCP连接采用短连接模式,每条消息独立建立连接。虽然增加了握手开销,但避免了长连接维护的复杂性。
4. 性能优化技巧
4.1 消息队列处理
实测中发现当快速连续发送多条消息时,直接创建TCP连接可能导致界面卡顿。解决方案是引入消息队列:
python复制self.message_queue = Queue()
# 发送线程
class SendThread(QThread):
def run(self):
while True:
msg = self.message_queue.get()
# 实际发送逻辑
time.sleep(0.1) # 控制发送速率
# 界面发送按钮点击事件
def on_send_clicked():
self.message_queue.put({
'content': self.input_box.text(),
'to': self.selected_user
})
4.2 历史消息缓存
为避免重复传输,实现简单的消息缓存机制:
- 每个客户端维护一个消息字典
python复制{ "user1": [ {"time": 1625090000, "content": "消息1", "direction": "out"}, {"time": 1625090005, "content": "消息2", "direction": "in"} ] } - 界面切换聊天对象时从本地缓存加载历史
- 定期(如每100条)将消息写入本地SQLite数据库
5. 常见问题解决方案
5.1 防火墙导致连接失败
现象:能看到用户在线但无法发送消息
解决方案:
- 检查防火墙是否放行37020-37030端口
- 在代码中添加端口测试功能:
python复制def test_port(ip, port): try: sock = socket.socket() sock.settimeout(1) sock.connect((ip, port)) return True except: return False
5.2 多网卡环境适配
现象:在同时连接有线网络和WiFi的电脑上无法正常工作
修改方案:
python复制# 获取本机所有IP
def get_local_ips():
ips = []
for interface in socket.if_nameindex():
name = interface[1]
if name.startswith('lo'):
continue
# 获取接口IP逻辑...
return ips
# 广播时发送所有可用IP
sock.sendto(b'HEARTBEAT:192.168.1.100,10.0.0.2', ('255.255.255.255', 37020))
6. 功能扩展方向
6.1 文件传输实现
基于现有架构添加文件传输功能:
-
发送方:
- 将文件分块(如1MB/块)
- 通过TCP发送元信息:
json复制{ "type": "file", "name": "document.pdf", "size": 5242880, "chunks": 5 } - 按顺序发送各数据块
-
接收方:
- 创建临时文件
- 按顺序接收并写入
- 完成校验后提示用户
6.2 消息加密方案
增强通讯安全性:
python复制from cryptography.fernet import Fernet
# 会话开始时交换密钥
key = Fernet.generate_key()
cipher = Fernet(key)
# 加密消息
encrypted = cipher.encrypt(b"secret message")
# 解密
decrypted = cipher.decrypt(encrypted)
7. 部署与打包建议
7.1 使用PyInstaller打包
创建单文件可执行程序:
bash复制pyinstaller --onefile --windowed --icon=app.ico chat_app.py
7.2 跨平台注意事项
-
Windows系统:
- 需要打包VC++运行库
- 建议添加防火墙规则自动配置
-
macOS系统:
- 需要签名否则可能被拦截
- 建议打包为.app格式
-
Linux系统:
- 注意不同发行版的依赖差异
- 可提供.deb/.rpm包
这个项目最让我惊喜的是PyQt5的信号槽机制与Python网络编程的契合度——当收到网络消息时,只需要emit一个信号,界面就能自动更新,完全不需要考虑线程安全问题。在300行核心代码里就实现了一个功能完整的局域网通讯工具,充分展现了Python生态的生产力优势。