资讯动态

Python socket实战:TCP数据包封装与可靠传输项目

发布时间:2026/10/17 9:12:33 来源:尧图企业网站定制
简介这份资源是燕山大学计算机网络课程三级项目的TCP传输数据包实践工程面向正在学习计算机网络、需要动手实现可靠传输机制的高校学生与网络编程初学者。项目围绕TCP协议的核心流程展开涵盖三次握手建立连接、数据分片与重组、滑动窗口流量控制、慢启动与快速重传等拥塞控制算法以及四次挥手释放连接帮助读者把协议理论落到可运行的代码上。压缩包共60个文件约27.88MB包含cpp源码、h头文件、sln解决方案、vcxproj工程配置、exe可执行程序以及pdb、obj、tlog等编译调试产物客户端与服务端工程结构完整下载后可直接运行观察通信过程。目前已有1354人学习下载适合作为课程设计参考、网络编程练手或TCP机制验证的实操素材便于读者对照代码理解数据包收发、确认重传与异常处理等关键环节。1. 从一次抓包实验说起这个TCP数据包项目到底能干什么如果你正在上计算机网络课或者刚学完TCP三次握手、四次挥手脑子里全是“SYN、ACK、FIN”这些词但真让你写一个能收发数据包的程序大概率会卡在socket API那一层。这个“燕大计算机网络三级项目TCP传输数据包”就是冲着这个痛点来的——它不是让你背协议而是让你亲手用代码把数据封装成TCP包发出去再在另一端拆开看内容。适合谁正在做课程设计的学生、想补网络编程底子的后端新人以及需要快速搭一个可靠传输Demo的开发者。项目核心就一件事把文件或字符串切成数据块通过TCP连接从客户端传到服务端服务端收齐后还原成原始内容。听起来简单但里面藏着粘包处理、超时重传、连接管理等一堆坑正好是练手的好素材。2. 项目骨架与技术选型为什么用Python socket而不是现成框架2.1 整体架构C/S模式下的三层拆分这个项目采用经典的客户端-服务端C/S架构但代码没有堆在一个文件里而是按职责拆成了三层。最底层是网络通信层直接调用Python的socket模块负责建立连接、发送和接收原始字节流。中间是数据封装层把用户要传的内容比如一个文本文件或一段JSON切分成固定大小的数据块每个块前面加上序号和长度字段形成自定义的“包”。最上层是应用逻辑层客户端读取本地文件、调用封装层发送服务端监听端口、接收数据块、按序号重组最后写入磁盘。这种拆法的好处是你改传输逻辑时不用动文件读写换协议时也不用重写业务代码。常见做法是把配置项IP、端口、缓冲区大小、超时时间抽到一个config.py里方便调试时快速调整。2.2 技术栈标准库够用但有两个细节要注意项目只依赖Python标准库没有第三方包。核心模块就三个socket负责网络通信struct负责把整数打包成二进制因为TCP传的是字节流不能直接发intthreading或select用来处理多客户端或非阻塞接收。为什么不用asyncio因为课程项目通常要求展示对阻塞IO和超时的理解用socket的settimeout()更直观。这里有个容易翻车的地方struct.pack()的格式字符串必须和unpack()完全一致字节序也要统一。我一般会在封装层里固定用网络字节序大端格式写成!I表示一个无符号int。如果客户端用!I打包服务端用I解包在x86机器上可能碰巧能跑换到ARM板子上直接乱码。2.3 目录结构一个能直接跑的参考布局tcp_packet_project/ ├── config.py # 全局配置HOST, PORT, BUFFER_SIZE, TIMEOUT ├── packet.py # 数据包封装与解析build_packet(), parse_packet() ├── client.py # 客户端入口读文件、分块、发送、等待确认 ├── server.py # 服务端入口监听、接收、重组、写文件 ├── utils.py # 辅助函数文件分块、校验和计算 └── test_data/ # 测试用的文本和二进制文件这个结构不是强制的但建议至少把packet.py独立出来。因为封装和解析的逻辑最容易被复用也最容易写错。把config.py单独放是为了避免在代码里硬编码端口号——我见过太多人把PORT 8888写在client.py和server.py里改一个忘一个调试半天发现连不上。3. 核心模块拆解一个数据包从封装到还原的完整链路3.1 数据包格式设计头部8字节后面跟载荷TCP是字节流协议没有消息边界。如果你直接send(hello)再send(world)接收方可能一次recv到helloworld这就是粘包。解决办法是自定义包头告诉对方“这个包有多长”。项目里我设计了一个简单的头部前4字节是序号从0开始递增后4字节是数据长度然后才是真正的数据内容。# packet.py import struct HEADER_FORMAT !II # 两个无符号int网络字节序 HEADER_SIZE struct.calcsize(HEADER_FORMAT) # 8字节 def build_packet(seq, data: bytes) - bytes: 将序号和数据打包成一个完整的TCP包 if len(data) 65535: raise ValueError(单包数据不能超过65535字节) header struct.pack(HEADER_FORMAT, seq, len(data)) return header data def parse_packet(raw: bytes): 从原始字节流中解析出序号和数据返回(seq, data, remaining) if len(raw) HEADER_SIZE: return None, None, raw # 数据不够一个包头 seq, length struct.unpack(HEADER_FORMAT, raw[:HEADER_SIZE]) if len(raw) HEADER_SIZE length: return None, None, raw # 数据还没收全 data raw[HEADER_SIZE:HEADER_SIZE length] remaining raw[HEADER_SIZE length:] return seq, data, remainingbuild_packet里用struct.pack把序号和长度按大端序转成8字节。parse_packet返回三个值序号、数据、剩余未解析的字节。为什么要有remaining因为TCP一次recv可能收到多个包或者半个包。调用方需要把剩余字节缓存起来下次和新的recv结果拼接后再解析。这个“缓冲区循环解析”的模式是处理粘包的标准做法少了它传大文件时必丢数据。3.2 客户端发送逻辑分块、发送、等ACK客户端要做的事很明确读文件、按固定大小切块、逐块发送、每发一块等对方确认。确认机制用简单的ACK包实现——服务端收到序号为N的包后回一个只含头部的空包序号也是N。客户端收到ACK才发下一块超时就重发。# client.py 核心片段 import socket, struct, time from packet import build_packet, parse_packet, HEADER_SIZE from config import HOST, PORT, BUFFER_SIZE, TIMEOUT def send_file(filepath): sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(TIMEOUT) sock.connect((HOST, PORT)) with open(filepath, rb) as f: seq 0 while True: chunk f.read(BUFFER_SIZE) if not chunk: break packet build_packet(seq, chunk) retry 0 while retry 3: try: sock.sendall(packet) ack_raw sock.recv(HEADER_SIZE) ack_seq, _, _ parse_packet(ack_raw) if ack_seq seq: break # 确认成功 except socket.timeout: retry 1 print(f第{seq}包超时重试{retry}) else: raise RuntimeError(f第{seq}包发送失败) seq 1 sock.close()sendall保证把整个包发完但TCP不保证对方一定收到。所以每发一块就等一个ACK超时时间由config.TIMEOUT控制一般设2到5秒。重试3次还失败就抛异常。这里有个细节sock.recv(HEADER_SIZE)只读8字节因为ACK包只有头部。如果服务端回ACK时多发了数据这里会解析错位。所以服务端的ACK必须严格只发头部。3.3 服务端接收与重组缓冲区管理是重点服务端要处理多个客户端的并发连接同时还要正确拼包。我用threading给每个连接开一个线程主线程只负责accept。每个连接线程内部维护一个buffer每次recv后把数据追加到buffer然后循环调用parse_packet直到解析不出完整包为止。# server.py 核心片段 import socket, threading from packet import build_packet, parse_packet, HEADER_SIZE from config import HOST, PORT, BUFFER_SIZE def handle_client(conn, addr): buffer b received {} # seq - data while True: try: chunk conn.recv(BUFFER_SIZE) except ConnectionResetError: break if not chunk: break buffer chunk while True: seq, data, buffer parse_packet(buffer) if seq is None: break received[seq] data # 回ACK只发头部 ack build_packet(seq, b) conn.sendall(ack) # 按序号排序后写入文件 with open(freceived_{addr[1]}.bin, wb) as f: for seq in sorted(received.keys()): f.write(received[seq]) conn.close() def start_server(): srv socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind((HOST, PORT)) srv.listen(5) print(f监听 {HOST}:{PORT}) while True: conn, addr srv.accept() t threading.Thread(targethandle_client, args(conn, addr)) t.start()SO_REUSEADDR是为了避免重启服务端时端口被占用。received字典用序号做key最后按sorted顺序写文件保证内容顺序正确。如果客户端发的包乱序到达TCP一般不会但重传时可能这个字典也能兜住。注意buffer在循环里被parse_packet不断更新每次解析出一个完整包就丢掉对应的字节剩下的留给下次recv拼接。4. 跑起来才算数环境准备、启动顺序与验证方法4.1 环境要求与依赖安装只要机器上有Python 3.6以上版本就行不需要pip install任何东西。检查版本python3 --version # 输出 Python 3.8.10 或更高即可把项目文件放到同一个目录下确保config.py里的HOST设为0.0.0.0服务端监听所有网卡或127.0.0.1本机测试。PORT选一个大于1024的比如8888。BUFFER_SIZE建议设4096或8192太小会导致包数量暴增太大可能超过单包长度限制。4.2 启动服务端与客户端先开一个终端跑服务端python3 server.py # 输出监听 0.0.0.0:8888再开另一个终端跑客户端传一个测试文件echo 这是一个TCP传输测试文件包含中文和English混合内容。 test_data/sample.txt python3 client.py test_data/sample.txt客户端会逐块发送并打印进度。服务端收到后会在当前目录生成received_端口号.bin。用diff对比原文件和接收文件diff test_data/sample.txt received_*.bin # 没有输出就表示完全一致如果传的是二进制文件比如一张图片diff可能因为文件名不同而报错用cmp更准cmp test_data/image.png received_*.bin echo 二进制一致4.3 验证可靠传输手动制造超时和丢包想验证重传机制可以在客户端发送后、服务端回ACK前手动把服务端进程CtrlC停掉。客户端会等TIMEOUT秒后重试3次然后抛异常退出。重新启动服务端再跑一次数据依然能完整传输。另一个办法是用tc命令模拟网络延迟需要root权限但课程项目里通常不需要这么复杂。观察客户端打印的“第N包超时重试M”日志就能确认重传逻辑生效。5. 避坑与排查这5个问题我几乎每次都能遇到5.1 现象客户端发完数据服务端收到的文件少了一段原因服务端在recv返回空字节时直接break但此时buffer里可能还有未解析的半个包。TCP的recv返回b只表示对方关闭了连接不表示数据收完了。如果客户端发完最后一个包就close服务端可能先收到数据再收到FIN但buffer里残留的字节没被处理。解决在break之前再调用一次parse_packet循环把buffer里能解析的包全部处理掉。或者让客户端在发完所有数据后发一个特殊的“结束包”比如序号为-1服务端收到结束包再关闭连接。5.2 现象中文内容传过去变成乱码原因客户端用open(filepath, r)以文本模式读文件Python会自动用系统默认编码Windows上是GBKLinux上是UTF-8解码成字符串然后sendall时又编码成字节。如果两端编码不一致中文就乱了。解决统一用二进制模式rb读文件直接发送原始字节。服务端也用wb写文件。这样不管文件里是中文、英文还是图片都按字节原样传输。如果非要传字符串在应用层显式指定encode(utf-8)和decode(utf-8)。5.3 现象服务端只能处理一个客户端第二个连不上原因accept之后没有开新线程主线程直接进入handle_client的while循环导致无法回到accept去接收新连接。解决用threading.Thread把handle_client包起来start()之后主线程继续accept。注意线程里用的socket对象要传进去不要用全局变量。如果客户端数量很多考虑用select或asyncio但课程项目用线程足够。5.4 现象struct.error: unpack requires a buffer of 8 bytes原因parse_packet里调用struct.unpack时传入的raw长度小于8字节。通常是recv只收到了半个包头但代码没有检查长度就直接解包。解决在parse_packet开头加if len(raw) HEADER_SIZE: return None, None, raw。这个判断必须放在unpack之前否则一定崩。另外recv的参数建议设成HEADER_SIZE BUFFER_SIZE一次尽量多收减少循环次数。5.5 现象传输大文件时内存暴涨原因服务端把所有收到的包都存到received字典里等全部收完再写文件。如果文件有1GB内存就爆了。解决改成边收边写。但TCP可能乱序所以不能直接按到达顺序写。常见做法是维护一个“期望序号”收到期望的包就立即写文件并递增期望值收到乱序的包先缓存到字典里等期望的包到了再连续写入。这样内存里最多缓存乱序的那几个包不会无限增长。6. 二次开发怎么改从课程作业到能用的传输工具6.1 加一个简单的进度条和速度统计课程项目通常只要求功能对但如果你想让Demo看起来更专业可以在客户端加进度显示。核心是记录已发送字节数和总字节数每发一块就刷新一行。import sys, time total_sent 0 start_time time.time() # 在发送循环里 total_sent len(chunk) elapsed time.time() - start_time speed total_sent / elapsed / 1024 # KB/s progress total_sent / total_size * 100 sys.stdout.write(f\r进度: {progress:.1f}% | 速度: {speed:.1f} KB/s) sys.stdout.flush()total_size用os.path.getsize(filepath)获取。\r让光标回到行首实现原地刷新。注意在print其他日志前先输出一个换行否则会覆盖。6.2 把ACK机制换成滑动窗口现在的“发一块等一个ACK”叫停等协议效率很低。改成滑动窗口后客户端可以连续发多个包服务端累积确认。实现上需要给每个包加一个发送时间戳维护一个发送窗口收到ACK后窗口右移。这个改动大概增加50行代码但能把传输速度提升好几倍。我一般会先画一个窗口状态图标出send_base、next_seq、window_size三个变量再写代码。6.3 加一个文件校验功能传输完成后客户端和服务端各自计算文件的MD5或SHA256对比一下就知道有没有出错。用hashlib标准库import hashlib def file_hash(filepath): h hashlib.sha256() with open(filepath, rb) as f: while True: chunk f.read(8192) if not chunk: break h.update(chunk) return h.hexdigest()客户端在发送前算一次通过一个特殊包把哈希值发给服务端服务端收完后算一次对比。不一致就打印“校验失败请重传”。这个功能在调试粘包问题时特别有用因为一旦哈希对不上说明重组逻辑有bug。6.4 改造时的一个血泪教训我刚开始改这个项目时觉得“停等协议太慢”直接动手加滑动窗口结果忘了处理ACK丢失的情况——客户端窗口满了服务端没收到包所以没回ACK客户端一直等死锁。后来我强制自己每次改协议逻辑前先在本子上画出“发送方状态机”和“接收方状态机”标出所有可能丢包的分支再写代码。从那以后我每次动传输层代码都先跑一遍“发一个包、故意丢ACK、看超时重传”的测试用例确认基础路径没问题再往上加功能。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑