346 lines
8.5 KiB
Markdown
346 lines
8.5 KiB
Markdown
# 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日 |