240 lines
7.0 KiB
Markdown
240 lines
7.0 KiB
Markdown
# `UdpComm` 技术文档
|
||
|
||
```markdown
|
||
# UdpComm - UDP 通信模块技术文档
|
||
|
||
## 概述
|
||
|
||
`UdpComm` 是一个基于 Python 的异步 UDP 通信类,支持单播发送、广播、接收 JSON 或二进制数据,并通过后台线程处理 I/O 多路复用(`select`),适用于轻量级网络服务或设备发现等场景。
|
||
|
||
该模块封装了 UDP 套接字的基本操作,提供非阻塞接收与带缓冲的发送机制,同时支持自定义回调函数处理接收到的数据。
|
||
|
||
---
|
||
|
||
## 依赖说明
|
||
|
||
### 第三方/内部依赖
|
||
- `appPublic.sockPackage.get_free_local_addr`: 获取本机可用 IP 地址。
|
||
- `appPublic.background.Background`: 用于在后台运行任务的线程包装器。
|
||
|
||
> ⚠️ 注意:这两个模块属于项目私有库 `appPublic`,需确保其已安装并可导入。
|
||
|
||
### 内置模块
|
||
- `socket`: 提供底层网络通信功能。
|
||
- `select`: 实现 I/O 多路复用。
|
||
- `json`: 用于序列化和反序列化 JSON 数据。
|
||
- `time`: 控制循环延迟。
|
||
- `traceback.print_exc`: 打印异常堆栈信息。
|
||
|
||
---
|
||
|
||
## 常量
|
||
|
||
| 名称 | 值 | 说明 |
|
||
|------------|--------------|------|
|
||
| `BUFSIZE` | `1024 * 64` | 接收缓冲区大小,即每次最多接收 65536 字节 |
|
||
|
||
---
|
||
|
||
## 类定义:`UdpComm`
|
||
|
||
### 构造函数:`__init__(self, port, callback, timeout=0)`
|
||
|
||
初始化 UDP 通信实例。
|
||
|
||
#### 参数:
|
||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||
|-----------|----------|------|--------|------|
|
||
| `port` | int | 是 | - | 绑定的本地 UDP 端口号 |
|
||
| `callback`| function | 是 | - | 回调函数,用于处理接收到的数据,签名应为 `callback(data, addr)` |
|
||
| `timeout` | float | 否 | 0 | 超时时间(未启用) |
|
||
|
||
#### 初始化行为:
|
||
- 自动获取本机 IP 地址(用于后续广播计算)。
|
||
- 创建并绑定 UDP 套接字到指定端口 (`('', port)`)。
|
||
- 启动后台线程执行 `run()` 方法,持续监听消息。
|
||
- 初始化空发送缓冲队列 `buffer`。
|
||
|
||
> 📌 注:当前版本中 `setblocking` 和 `settimeout` 已被注释,实际为非阻塞模式配合 `select` 使用。
|
||
|
||
---
|
||
|
||
### 核心方法
|
||
|
||
#### `run(self)`
|
||
主循环逻辑,由后台线程执行。
|
||
|
||
##### 功能流程:
|
||
1. 使用 `select([sock], outs, [], 0.1)` 监听:
|
||
- 输入事件:是否有数据可读。
|
||
- 输出事件:是否可以发送数据(当缓冲区不为空时触发)。
|
||
2. 若有数据到达:
|
||
- 调用 `recvfrom()` 读取数据。
|
||
- 解析首字节标识符:
|
||
- `'b'` → 视为原始 **bytes** 数据,直接传递给回调。
|
||
- 其他 → 视为 UTF-8 编码的 JSON 字符串,尝试反序列化后传入回调。
|
||
- 出错时打印异常及原始数据,但不停止运行(仅中断本次处理)。
|
||
3. 若可写且缓冲区有数据:
|
||
- 从 `buffer` 中取出第一条待发消息并使用 `sendto()` 发送。
|
||
4. 循环结束条件:`self.run_flg == False`
|
||
5. 最终关闭套接字。
|
||
|
||
> ⏱️ 每次循环休眠 `0.1` 秒以降低 CPU 占用。
|
||
|
||
---
|
||
|
||
#### `stop(self)`
|
||
安全停止通信线程。
|
||
|
||
##### 行为:
|
||
- 设置标志位 `run_flg = False`,通知 `run()` 结束循环。
|
||
- 关闭 UDP 套接字。
|
||
- 等待后台线程退出(`join()`)。
|
||
|
||
> ✅ 推荐在程序退出前调用此方法释放资源。
|
||
|
||
---
|
||
|
||
#### `broadcast(self, data)`
|
||
向局域网广播消息(目标地址 `.255`)。
|
||
|
||
##### 参数:
|
||
| 参数名 | 类型 | 说明 |
|
||
|-------|------|------|
|
||
| `data` | any (serializable) or bytes | 要广播的数据 |
|
||
|
||
##### 处理逻辑:
|
||
- 若 `data` 非 `bytes` 类型,则将其 JSON 序列化并编码为 UTF-8。
|
||
- 构造广播地址(如本机 IP 为 `192.168.1.100`,则广播地址为 `192.168.1.255`)。
|
||
- 创建临时 UDP 客户端套接字,启用广播选项(`SO_BROADCAST=1`)。
|
||
- 发送数据到 `(broadcast_host, self.port)`。
|
||
|
||
> 🔊 广播不会经过 `send()` 缓冲区,是即时发送。
|
||
|
||
---
|
||
|
||
#### `send(self, data, addr)`
|
||
将数据加入发送缓冲区,异步发送至指定地址。
|
||
|
||
##### 参数:
|
||
| 参数名 | 类型 | 说明 |
|
||
|-------|--------------|------|
|
||
| `data` | str / dict / bytes | 待发送数据 |
|
||
| `addr` | tuple or list | 目标地址 `(ip, port)` |
|
||
|
||
##### 数据封装规则:
|
||
- 若 `data` 不是 `bytes`:添加前缀 `'j'`,表示 JSON 数据。
|
||
- 若 `data` 是 `bytes`:添加前缀 `'b'`,表示二进制数据。
|
||
|
||
> 示例:
|
||
> - `send({"msg": "hello"}, ("192.168.1.2", 5000))` → 实际发送 `'j{"msg":"hello"}'`
|
||
> - `send(b'\x01\x02', ("192.168.1.2", 5000))` → 实际发送 `'b\x01\x02'`
|
||
|
||
> 🔄 数据会被追加到 `self.buffer`,由 `run()` 线程异步发送。
|
||
|
||
---
|
||
|
||
#### `sends(self, data, addrs)`
|
||
批量发送相同数据到多个地址。
|
||
|
||
##### 参数:
|
||
| 参数名 | 类型 | 说明 |
|
||
|--------|-------------|------|
|
||
| `data` | any / bytes | 要发送的数据 |
|
||
| `addrs`| list of tuple/list | 多个目标地址列表 |
|
||
|
||
##### 行为:
|
||
遍历 `addrs` 列表,对每个地址调用 `self.send(data, addr)`。
|
||
|
||
---
|
||
|
||
## 数据格式约定
|
||
|
||
| 首字节 | 含义 | 数据内容 |
|
||
|--------|------------|------------------------|
|
||
| `'b'` | Binary | 原始二进制数据(无编码) |
|
||
| `'j'` | JSON | UTF-8 编码的 JSON 字符串 |
|
||
|
||
接收端根据首字节判断如何解析后续数据。
|
||
|
||
---
|
||
|
||
## 回调函数规范
|
||
|
||
用户提供的 `callback` 函数必须接受两个参数:
|
||
|
||
```python
|
||
def callback(data, addr):
|
||
# data: 解码后的对象(dict / list / bytes)
|
||
# addr: 发送方地址 tuple(ip: str, port: int)
|
||
pass
|
||
```
|
||
|
||
示例:
|
||
```python
|
||
def msg_handle(data, addr):
|
||
print('addr:', addr, 'data=', data)
|
||
```
|
||
|
||
---
|
||
|
||
## 使用示例
|
||
|
||
### 作为服务器启动并监听
|
||
|
||
```bash
|
||
python udpcomm.py 50000
|
||
```
|
||
|
||
交互式输入格式:`目标端口:消息内容`
|
||
|
||
例如:
|
||
```
|
||
50001:{"cmd": "discover"}
|
||
```
|
||
→ 将 JSON 数据发送到 `localhost:50001`(IP 为空时自动替换为 `''`)
|
||
|
||
### 在代码中使用
|
||
|
||
```python
|
||
def on_message(data, addr):
|
||
print(f"Received from {addr}: {data}")
|
||
|
||
# 启动监听
|
||
comm = UdpComm(port=50000, callback=on_message)
|
||
|
||
# 发送消息
|
||
comm.send({"hello": "world"}, ("192.168.1.50", 50000))
|
||
|
||
# 广播
|
||
comm.broadcast({"type": "service_announce"})
|
||
|
||
# 停止服务
|
||
comm.stop()
|
||
```
|
||
|
||
---
|
||
|
||
## 注意事项与限制
|
||
|
||
1. ❗ **线程安全**:`buffer` 未加锁,若多线程调用 `send()` 可能存在竞争风险。建议外部同步或改用线程安全队列。
|
||
2. 💤 **性能**:每轮 `sleep(0.1)` 可能影响实时性;可根据需要调整间隔。
|
||
3. 🧹 **错误处理**:JSON 解析失败仅打印日志,不会终止服务。
|
||
4. 🌐 **广播范围**:依赖子网掩码配置,`.255` 未必总是有效广播地址。
|
||
5. 🚫 **IPv6 不支持**:目前仅限 IPv4。
|
||
|
||
---
|
||
|
||
## TODO 改进建议
|
||
|
||
- [ ] 添加日志模块替代 `print`
|
||
- [ ] 使用 `queue.Queue` 替代 `list` 实现线程安全缓冲
|
||
- [ ] 支持 IPv6
|
||
- [ ] 增加单元测试
|
||
- [ ] 提供上下文管理器(`with` 支持)
|
||
|
||
---
|
||
```
|
||
|
||
> 文档版本:v1.0
|
||
> 最后更新:2025-04-05 |