apppublic/aidocs/sockPackage.md
2025-10-05 11:23:33 +08:00

346 lines
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Socket 通信模块技术文档
本项目提供了一个基于 Python 的简单 TCP 客户端/服务器通信框架,支持多线程并发处理客户端连接,并封装了后台任务调用机制。适用于本地测试、轻量级网络服务开发等场景。
---
## 目录
- [依赖说明](#依赖说明)
- [核心功能概览](#核心功能概览)
- [工具函数](#工具函数)
- [后台任务类](#后台任务类)
- [异常定义](#异常定义)
- [SocketServer 类](#socketserver-类)
- [SocketClient 类](#socketclient-类)
- [主程序示例](#主程序示例)
- [使用建议与注意事项](#使用建议与注意事项)
---
## 依赖说明
```python
import os
import time
import threading
import sys
import socket
```
所需标准库模块:
- `socket`:用于实现 TCP 网络通信。
- `threading`:支持多线程并发处理多个客户端连接。
- `time`:用于延时控制和测试。
- 其他为通用系统操作支持。
---
## 核心功能概览
| 组件 | 功能 |
|------|------|
| `get_free_local_addr()` | 获取当前机器可用的 IP 地址(通过 DNS 请求探测) |
| `background` / `BackgroundCall()` | 异步执行函数的线程包装器 |
| `SocketServer` | 多线程 TCP 服务器,可接受并异步处理多个客户端连接 |
| `SocketClient` | TCP 客户端,用于连接服务器、发送和接收数据 |
| 自定义异常 | `SocketServerError`, `SocketClientError` 提供清晰错误分类 |
---
## 工具函数
### `get_free_local_addr()`
获取本机在联网状态下对外通信所使用的 IP 地址和临时端口。
#### 函数签名
```python
def get_free_local_addr():
```
#### 返回值
- `(ip: str, port: int)`:元组形式返回本机出口 IP 和操作系统分配的临时端口号。
#### 实现原理
通过创建一个 UDP 套接字连接到公共 DNS 服务器 `8.8.8.8:80`,不实际发送数据,仅用于确定本地绑定地址。
> ⚠️ 注意:此方法依赖外网可达性;若无网络或防火墙限制可能失败。
#### 示例
```python
ip, port = get_free_local_addr()
print(ip) # 输出如 '192.168.1.100'
```
---
## 后台任务类
### `class background(threading.Thread)`
封装一个可在后台运行的函数调用。
#### 属性
| 属性名 | 类型 | 描述 |
|-------|------|------|
| `func` | `callable` | 要执行的目标函数 |
| `kw` | `dict` | 关键字参数传递给目标函数 |
#### 方法
##### `__init__(self, func, kw)`
初始化线程对象。
- **参数**
- `func`: 可调用对象(函数)
- `kw`: 字典形式的关键字参数 `{key: value}`
##### `run(self)`
重写 `Thread.run()`,自动调用 `self.func(**self.kw)`
---
### `BackgroundCall(func, datas)`
便捷函数:启动一个后台线程执行指定函数。
#### 参数
- `func`: 待执行函数
- `datas`: 传入该函数的关键字参数字典
#### 行为
- 创建 `background` 实例
- 调用 `.start()` 启动线程
- 不阻塞主线程
#### 示例
```python
def echo(data):
print("Received:", data)
BackgroundCall(echo, {'data': 'Hello'})
# 在后台打印 "Received: Hello"
```
---
## 异常定义
### `class SocketServerError(Exception)`
表示服务器端发生的严重错误,例如绑定地址失败、未就绪运行等。
### `class SocketClientError(Exception)`
表示客户端连接或通信过程中出现的问题,如无法连接、读写出错等。
> 使用自定义异常便于上层捕获特定错误类型进行处理。
---
## SocketServer 类
多线程 TCP 服务器,监听指定地址和端口,每个新连接由独立线程处理。
### 类定义
```python
class SocketServer(threading.Thread)
```
继承自 `threading.Thread`,以非守护模式运行。
---
### 构造函数 `__init__(host, port, max_connect=10, callee=None)`
#### 参数
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `host` | `str` | — | 绑定主机地址,如 `'localhost'``'0.0.0.0'` |
| `port` | `int` | — | 绑定端口号 |
| `max_connect` | `int` | `10` | 最大挂起连接数listen 队列长度) |
| `callee` | `callable` | `None` | 处理每个客户端连接的回调函数 |
#### 回调函数原型
```python
def handler(conn: socket.socket, addr: tuple):
pass
```
其中:
- `conn`: 客户端连接套接字
- `addr`: 客户端地址 `(ip, port)`
#### 初始化行为
- 设置守护线程为 `False`
- 创建并尝试绑定监听套接字
- 若成功则 `ready = True`,否则记录日志但继续构造
---
### 方法
#### `setSocketServer()`
内部方法:创建并配置服务器套接字。
- 创建 TCP 套接字 (`AF_INET`, `SOCK_STREAM`)
- 绑定 `(host, port)`
- 开始监听(最大连接队列长度为 `max_c`
- 成功后设置 `self.ready = True`
> 若失败会打印错误信息但不会抛出异常(构造阶段容错)
#### `run()`
线程入口函数,循环接受客户端连接。
- 检查是否 `ready`,否则抛出 `SocketServerError`
- 进入无限循环等待客户端接入
- 每次接受连接后,使用 `BackgroundCall``callee` 函数异步执行
> 单个连接处理完全解耦,不影响主服务循环
#### `stop()`
请求停止服务器运行。
- 设置标志位 `keep_running = 0`
- 下一次循环将退出 `run()` 函数
- ⚠️ 当前已建立的连接不会被主动关闭
#### `callee(self, conn, addr)`(默认回显处理)
内置默认处理函数:持续接收数据并原样返回(回显服务),直到连接断开。
> ❗ 存在一个拼写错误:`con.close()` 应为 `conn.close()`
##### 修正建议
```python
def callee(self, conn, addr):
try:
while True:
d = conn.recv(1024)
if not d: # 接收到空数据表示连接关闭
break
conn.send(d)
finally:
conn.close() # 正确关闭连接
```
---
## SocketClient 类
TCP 客户端封装,提供连接管理及基本 I/O 操作。
### 构造函数 `__init__(host, port)`
自动尝试连接指定服务器。
#### 参数
- `host`: 服务器地址
- `port`: 端口
#### 行为
- 创建 TCP 套接字
- 调用 `connect()` 方法连接服务器
- 成功则 `ready=True`,失败则抛出 `SocketClientError`
---
### 方法
#### `timeout(tim)`
设置套接字阻塞模式和超时时间。
- `tim == 0`: 非阻塞模式
- `tim > 0`: 阻塞模式 + 超时秒数
> 内部调用 `setblocking()` 和 `settimeout()`
#### `connect()`
重新建立与服务器的连接。
- 若失败打印错误信息并抛出 `SocketClientError`
#### `read(size)`
从服务器读取最多 `size` 字节数据。
- 成功返回字节串(`bytes`
- 失败打印错误并抛出 `SocketClientError`
#### `write(data)`
向服务器发送数据。
- `data` 必须是字节串(`bytes`),若传入字符串需先编码
- 错误时抛出异常
#### `close()`
关闭连接,释放资源。
- 调用 `sock.close()`
- 设置 `ready = False`
---
## 主程序示例
```python
if __name__ == '__main__':
s = SocketServer('localhost', 12232)
s.start()
time.sleep(5) # 等待服务器启动
while True:
c = SocketClient('localhost', 12232)
msg = 'msg1'
print("send:", msg)
c.write(msg.encode()) # 注意:必须 encode 成 bytes
d = c.read(1024)
print("get:", d.decode()) # 解码为字符串
c.close()
time.sleep(1)
```
> ✅ 改进建议:添加异常处理防止客户端崩溃中断循环
---
## 使用建议与注意事项
### ✅ 推荐实践
1. **确保数据编码一致性**
```python
c.write(b'msg') # 字节串
c.write('msg'.encode()) # 字符串转字节
```
2. **捕获异常避免中断**
```python
try:
c = SocketClient(...)
except SocketClientError:
time.sleep(1)
continue
```
3. **合理设置超时**
```python
c.timeout(5) # 5秒超时防卡死
```
### ⚠️ 已知问题
| 问题 | 描述 | 建议修复 |
|------|------|---------|
| `con.close()` 拼写错误 | 导致连接未正确关闭 | 改为 `conn.close()` |
| `callee` 默认函数无异常处理 | 可能导致线程异常退出 | 包裹 `try...finally` |
| 构造中静默忽略异常 | 难以调试绑定失败原因 | 抛出或记录详细日志 |
| `write()` 中错误提示写成了 `'recv error'` | 日志误导 | 改为 `'send error'` |
### 🔧 可扩展方向
- 添加 SSL/TLS 支持
- 支持 IPv6
- 增加连接池或心跳检测
- 提供异步 IO 版本asyncio
---
## 许可证
本代码为示例用途,遵循 [MIT License](https://opensource.org/licenses/MIT)(除非另有声明)。
---
> 文档版本v1.0
> 更新日期2025年4月5日