|
@@ -59,4 +59,130 @@ if __name__ == "__main__":
|
|
|
print("【错误】地址格式应为 IP:端口")
|
|
print("【错误】地址格式应为 IP:端口")
|
|
|
sys.exit(1)
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
- start_server(ip, port)
|
|
|
|
|
|
|
+ start_server(ip, port)
|
|
|
|
|
+
|
|
|
|
|
+"""
|
|
|
|
|
+# 代码详解与运行环境说明
|
|
|
|
|
+
|
|
|
|
|
+## 1. 代码功能概述
|
|
|
|
|
+
|
|
|
|
|
+本程序 `udpserver.py` 是一个基于 UDP 协议的文件接收服务端。它不仅仅是一个简单的 UDP 数据包接收器,更是一个模拟了 TCP 可靠传输机制(如三次握手、四次挥手)的应用层协议实现。该程序旨在演示如何在不可靠的 UDP 传输层之上,构建一个具备基本连接管理和文件传输功能的应用层协议。
|
|
|
|
|
+
|
|
|
|
|
+主要功能包括:
|
|
|
|
|
+1. **UDP Socket 监听**:在指定的 IP 地址和端口上监听来自客户端的数据报文。
|
|
|
|
|
+2. **模拟 TCP 三次握手**:在正式传输数据前,通过 SYN 和 SYN-ACK 报文交互,确认双方的连接意愿,建立逻辑上的“连接”。
|
|
|
|
|
+3. **可靠的元数据接收**:接收包含文件名和文件大小的元数据,并发送确认(ACK),确保接收端做好了接收文件的准备。
|
|
|
|
|
+4. **文件数据流接收**:循环接收文件数据块,并将其写入本地磁盘,直到接收到的字节数达到预期的文件大小。
|
|
|
|
|
+5. **模拟 TCP 四次挥手**:在数据传输完成后,通过 FIN 和 FIN-ACK 报文交互,优雅地关闭连接,释放资源。
|
|
|
|
|
+6. **多客户端支持(串行)**:程序采用无限循环结构,在处理完一个客户端的传输任务后,会自动重置状态,等待下一个客户端的连接请求。
|
|
|
|
|
+
|
|
|
|
|
+## 2. 代码逻辑深度解析
|
|
|
|
|
+
|
|
|
|
|
+### 2.1 模块导入与环境准备
|
|
|
|
|
+代码首先导入了 `socket`、`sys` 和 `os` 三个核心模块。
|
|
|
|
|
+* `socket` 模块是网络编程的基础,提供了创建套接字、绑定地址、发送和接收数据的方法。
|
|
|
|
|
+* `sys` 模块用于处理命令行参数,使得程序可以灵活地从命令行接收 IP 和端口配置。
|
|
|
|
|
+* `os` 模块用于文件路径操作,例如从接收到的元数据中提取纯文件名,防止路径遍历攻击。
|
|
|
|
|
+
|
|
|
|
|
+### 2.2 `start_server` 函数详解
|
|
|
|
|
+这是程序的核心函数,封装了服务端的所有逻辑。
|
|
|
|
|
+
|
|
|
|
|
+#### 2.2.1 Socket 创建与绑定
|
|
|
|
|
+```python
|
|
|
|
|
+server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
|
|
+server_socket.bind((ip, port))
|
|
|
|
|
+```
|
|
|
|
|
+这里创建了一个 IPv4 (`AF_INET`) 的 UDP (`SOCK_DGRAM`) 套接字。与 TCP 不同,UDP 是无连接的,因此不需要 `listen()` 和 `accept()` 调用。`bind()` 方法将套接字绑定到指定的 IP 和端口,使其能够接收发往该地址的数据包。
|
|
|
|
|
+
|
|
|
|
|
+#### 2.2.2 连接建立阶段(模拟握手)
|
|
|
|
|
+程序进入一个无限循环 `while True`,首先处于“等待连接”状态。
|
|
|
|
|
+```python
|
|
|
|
|
+(data, client_addr) = server_socket.recvfrom(1024)
|
|
|
|
|
+if data.decode(...) == "SYN":
|
|
|
|
|
+ server_socket.sendto("SYN-ACK".encode(...), client_addr)
|
|
|
|
|
+```
|
|
|
|
|
+这一步模拟了 TCP 的第一次和第二次握手。
|
|
|
|
|
+* **接收 SYN**:服务端阻塞等待,直到收到一个数据包。如果数据包的内容解码后是 "SYN",则认为这是一个连接请求。
|
|
|
|
|
+* **发送 SYN-ACK**:服务端记录下客户端的地址 (`client_addr`),并立即回复一个 "SYN-ACK" 报文。这表示服务端同意建立连接。
|
|
|
|
|
+* **设计考量**:这里使用 1024 字节的缓冲区,对于仅包含控制指令的报文来说绰绰有余。
|
|
|
|
|
+
|
|
|
|
|
+#### 2.2.3 元数据接收阶段
|
|
|
|
|
+连接建立后,服务端并不立即接收文件内容,而是先等待文件的元数据。
|
|
|
|
|
+```python
|
|
|
|
|
+data, client_addr = server_socket.recvfrom(2048)
|
|
|
|
|
+_, filename, filesize = data.decode(...).split('|')
|
|
|
|
|
+```
|
|
|
|
|
+* **协议格式**:应用层协议定义元数据格式为 `META|文件名|文件大小`。
|
|
|
|
|
+* **解析与确认**:服务端解析出文件名和大小,并发送 "META-ACK" 确认。这一步至关重要,它起到了同步的作用,确保服务端在开始接收大量数据流之前,已经知道了文件的基本信息(如存哪里、存多大)。
|
|
|
|
|
+
|
|
|
|
|
+#### 2.2.4 数据传输阶段
|
|
|
|
|
+这是最核心的数据接收循环。
|
|
|
|
|
+```python
|
|
|
|
|
+with open(save_filename, 'wb') as f:
|
|
|
|
|
+ while received < filesize:
|
|
|
|
|
+ chunk, _ = server_socket.recvfrom(2048)
|
|
|
|
|
+ f.write(chunk)
|
|
|
|
|
+```
|
|
|
|
|
+* **二进制写入**:使用 `'wb'` 模式打开文件,确保无论是文本文件还是图片、视频等二进制文件都能正确保存。
|
|
|
|
|
+* **循环接收**:通过 `while received < filesize` 控制循环,确保接收到的字节数精确等于文件大小。
|
|
|
|
|
+* **缓冲区大小**:这里使用了 2048 字节的缓冲区。虽然以太网 MTU 通常为 1500 字节,但应用层缓冲区稍大一些可以确保完整接收经过 IP 分片重组后的数据包,或者容纳稍大的 UDP 载荷。
|
|
|
|
|
+
|
|
|
|
|
+#### 2.2.5 连接释放阶段(模拟挥手)
|
|
|
|
|
+文件接收完毕后,服务端进入等待断开状态。
|
|
|
|
|
+```python
|
|
|
|
|
+data, _ = server_socket.recvfrom(1024)
|
|
|
|
|
+if data.decode(...) == "FIN":
|
|
|
|
|
+ server_socket.sendto("FIN-ACK".encode(...), client_addr)
|
|
|
|
|
+```
|
|
|
|
|
+* **接收 FIN**:客户端发送 "FIN" 报文表示数据发送完毕,请求断开。
|
|
|
|
|
+* **发送 FIN-ACK**:服务端回复确认,并打印断开连接的日志。
|
|
|
|
|
+* **循环重置**:完成这一步后,程序回到外层 `while True` 循环的开头,重新进入“等待连接”状态,准备服务下一个用户。
|
|
|
|
|
+
|
|
|
|
|
+## 3. 运行环境与配置
|
|
|
|
|
+
|
|
|
|
|
+### 3.1 操作系统
|
|
|
|
|
+本程序基于 Python 标准库开发,具有极强的跨平台性。
|
|
|
|
|
+* **Linux (推荐)**:如 Ubuntu, CentOS, Debian。Linux 的网络栈性能优异,且命令行工具丰富,非常适合运行此类网络服务。
|
|
|
|
|
+* **Windows**:完全兼容。Windows 的 Socket API 与 Linux 基本一致。
|
|
|
|
|
+* **macOS**:完全兼容。
|
|
|
|
|
+
|
|
|
|
|
+### 3.2 Python 版本
|
|
|
|
|
+* **推荐版本**:Python 3.6 及以上。
|
|
|
|
|
+* **依赖库**:仅依赖 Python 标准库 (`socket`, `sys`, `os`),无需安装任何第三方 pip 包。这使得程序非常轻量,易于部署。
|
|
|
|
|
+
|
|
|
|
|
+### 3.3 网络配置
|
|
|
|
|
+* **IP 地址**:
|
|
|
|
|
+ * `0.0.0.0`:绑定到所有网络接口。这意味着局域网内的其他机器、本机(localhost)都可以访问该服务。这是服务端的默认推荐配置。
|
|
|
|
|
+ * `127.0.0.1`:仅绑定到本地回环接口。只有本机可以访问,安全性更高,适合开发测试。
|
|
|
|
|
+ * `特定局域网 IP`(如 `192.168.1.100`):仅允许通过该特定网卡访问。
|
|
|
|
|
+* **端口号**:
|
|
|
|
|
+ * 默认端口为 `7474`。
|
|
|
|
|
+ * 端口范围应在 1024-65535 之间,避免与系统保留端口(0-1023)冲突。
|
|
|
|
|
+ * 确保防火墙(如 `iptables`, `ufw` 或 Windows 防火墙)允许 UDP 流量通过该端口。
|
|
|
|
|
+
|
|
|
|
|
+## 4. 背景知识:UDP 与 TCP 的抉择
|
|
|
|
|
+
|
|
|
|
|
+### 4.1 为什么选择 UDP?
|
|
|
|
|
+用户可能疑惑,既然我们要模拟 TCP 的握手和挥手,为什么不直接用 TCP?
|
|
|
|
|
+* **学习目的**:本实验的核心目的在于深入理解网络协议的底层机制。直接使用 TCP,操作系统内核会帮我们处理所有的连接管理、重传、流控,开发者只能看到一个“流”。而使用 UDP,我们需要自己去思考“如何建立连接”、“如何界定消息边界”、“如何保证可靠性”,这是学习网络编程的最佳途径。
|
|
|
|
|
+* **性能与开销**:UDP 头部仅 8 字节,而 TCP 头部至少 20 字节。在某些对实时性要求极高、对丢包容忍度较高的场景(如视频直播、在线游戏),UDP 是更好的选择。
|
|
|
|
|
+* **灵活性**:基于 UDP,我们可以设计出符合特定需求的私有协议(如 QUIC 协议就是基于 UDP 实现的可靠传输协议)。
|
|
|
|
|
+
|
|
|
|
|
+### 4.2 本程序的局限性
|
|
|
|
|
+虽然本程序模拟了 TCP 的部分行为,但它仍然是一个简化的模型,缺乏 TCP 的许多关键特性:
|
|
|
|
|
+* **丢包重传**:真正的 TCP 有超时重传机制(RTO)。本程序在数据传输阶段如果发生丢包,接收端会一直阻塞在 `recvfrom`,导致死锁。
|
|
|
|
|
+* **乱序重排**:UDP 不保证包的到达顺序。真正的 TCP 有序列号(Seq)机制,接收端可以对乱序到达的包进行重组。本程序假设包是按序到达的。
|
|
|
|
|
+* **流量控制**:真正的 TCP 有滑动窗口机制,防止发送方发得太快淹没接收方。本程序仅在客户端通过 `time.sleep(0.001)` 进行了极其简单的速率限制。
|
|
|
|
|
+* **拥塞控制**:真正的 TCP 会根据网络拥塞程度动态调整发送窗口(慢启动、拥塞避免)。本程序没有此功能。
|
|
|
|
|
+
|
|
|
|
|
+## 5. 扩展思考:如何实现可靠 UDP (RUDP)?
|
|
|
|
|
+
|
|
|
|
|
+如果要将本程序升级为一个真正的工业级可靠文件传输工具,我们需要在应用层实现以下机制:
|
|
|
|
|
+1. **序列号 (Sequence Number)**:给每个数据包编号(Packet 1, Packet 2...)。接收端根据编号判断是否有包丢失或乱序。
|
|
|
|
|
+2. **确认机制 (ACK)**:接收端每收到一个包(或一组包),就回复一个 ACK 包,告诉发送端“我收到了”。
|
|
|
|
|
+3. **超时重传 (Retransmission)**:发送端发出包后启动定时器。如果在规定时间内没收到 ACK,就重发该包。
|
|
|
|
|
+4. **校验和 (Checksum)**:虽然 UDP 头部有校验和,但应用层可以增加更强的校验(如 CRC32 或 MD5),确保文件内容在传输过程中没有一位比特发生错误。
|
|
|
|
|
+
|
|
|
|
|
+## 6. 总结
|
|
|
|
|
+`udpserver.py` 展示了一个基于 UDP 的简易文件传输协议的服务端实现。它通过应用层的握手和挥手机制,赋予了无连接的 UDP 以“连接”的概念;通过元数据交换,实现了文件的定界。虽然它在可靠性上无法与 TCP 媲美,但它是一个极佳的教学案例,帮助开发者理解网络协议设计的核心要素:连接管理、状态同步、数据封装与解封装。通过阅读和修改此代码,你可以亲手触摸到网络通信的脉搏。
|
|
|
|
|
+"""
|