news 2026/10/18 7:10:41

手写Python网络协议实验:HTTP解析、UDP构造与TCP可靠传输模拟

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手写Python网络协议实验:HTTP解析、UDP构造与TCP可靠传输模拟

简介:这是一份源自黑龙江大学计算机网络课程的实验报告与Python源码包,适合正在学习网络原理、需要完成类似实验或复习协议实现的学生。包内共有二十四个文件,以十一个Python脚本为主,另有五份Word实验报告、四个HTML辅助页面及说明文本等,压缩包约三百四十一KB,结构清晰便于对照查阅。目前已有一千零八十一人学习下载。内容包括Winsock编程实现TCP/IP通信、简单HTTP服务器与请求处理、停止等待ARQ算法模拟、RIP路由协议的路由更新模拟,以及常用网络命令的实践。通过报告中的设计思路与可运行代码,读者能直观理解数据封装、差错控制、路由选择等核心机制,同时提升Python网络编程与调试能力。所有源码均直接可运行,适合课后验证与二次练习。

1. 为什么计算机网络实验需要自己动手写一套Python方案

很多人以为计算机网络实验就是看课件、抓几个包、截图交差。等到课程设计要求“实验报告+源码”,才发现课讲的三次握手、停止等待、滑动窗口,自己一行能跑的Python都写不出来。要么抄开源demo,要么只会用requests调接口,问到底层socket怎么收发、报文怎么解析,一句都答不上。

这套方案就解决这个缺口:把HTTP报文分析、UDP报文构造、TCP可靠传输模拟、滑动窗口这些核心实验,从课本翻译成直接可运行、可验证的Python代码,并顺手整理出写实验报告要的格式与输出。适合正在赶课程设计的学生、为网络方向面试做基础准备的人,以及想补协议栈底子的开发新手。

我见过不少人在Socket编程上卡了整整一周,最后交上去的报告只有运行截图没有协议分析,原因不是懒,而是不知道代码应该长什么样。下面会把一套能直接落地的Python实验源码拆开讲:代码结构、关键参数、运行结果怎么验证、出错时看哪里,一层层交代清楚。

2. 实验模块划分与代码骨架:从课程要求倒推Python结构

2.1 四个核心实验模块我先拆成哪些

计算机网络课程的知识点分布在应用层、传输层、网络层和链路层。做实验不能贪多,我一般只挑四个模块,正好把课程的几个重点串起来,报告也好组织。每个模块对应一个独立脚本,脚本间通过公共工具层复用代码。

实验模块对应知识点代码文件报告验证方式
HTTP报文解析HTTP/1.1报文格式、请求方法、头部字段http_client.py + http_parser.py抓包能看到GET请求和服务器响应的完整报文
UDP数据报构造UDP报文格式、Internet校验和、字节序udp_packet.py构造的报文能用Wireshark或hexdump人工解析
停等协议可靠传输原理、超时重传stop_wait.py模拟丢包后能观察到重传事件日志
滑动窗口流量控制、窗口状态迁移sliding_window.py打印出窗口坐标和左移过程

这个拆法有两条原则。第一,每个模块只讲一个协议,别把滑动窗口和拥塞控制揉进一个脚本。代码一旦交叉,老师追问关联关系时很难讲清楚,而且调试时一个逻辑错误会牵扯另一段逻辑。第二,每个模块必须有可观测的输出——要么是抓包截图,要么是带序号的状态日志,不能只有一行“运行成功”。

我见过有人写了一个tcp_simulator.py,里面塞了滑动窗口、拥塞避免、快速重传三个部分,结果窗口状态和拥塞窗口更新相互干扰,报告里的图根本对不上。正确的做法是先让每个实验独立成进程,再通过统一入口管理,代码逻辑清晰,报告也能按模块复制输出,不用从一堆日志里找自己需要的那一段。

2.2 公共工具层:校验和、字节序、十六进制转储

四个模块里有些底层操作会反复用到:整数转网络字节序、二进制流里读写头部字段、计算Internet校验和、打印原始报文。这些要提前收进utils.py,否则每个脚本各写一遍,很容易出现实现不一致。尤其是校验和这种算法细节,写错一次排查半天,还容易被误判为网络环境问题。

先看校验和函数,UDP和IP报文里都用同一套算法:

def checksum(data: bytes) -> int: """ 计算Internet校验和:把数据按16位分组求和,进位回卷,最后取反。 返回的是0~65535之间的整数,写入报文时要转成网络字节序。 """ if len(data) % 2 == 1: data += b'\x00' # 奇数长度补一个零字节参与计算,这个字节不发送 s = 0 for i in range(0, len(data), 2): word = (data[i] << 8) + data[i + 1] # 大端拼法:前一个字节是高8位 s += word # 每次累加后把高16位进位回卷到低16位 s = (s & 0xffff) + (s >> 16) return (~s) & 0xffff

逻辑说明:Internet校验和来自RFC 1071,要求把数据切成16位的序列,按大端拼成整数后累加。累加可能产生超过16位的进位,必须把进位不断回卷到低16位,直到没有进位为止。最后对整个累加和取反码,得到的就是发送端要填进报文里的校验和。这里补零只参与计算,并不真正发送,这是很多人容易搞混的地方。

这个函数里最容易写错的是s = (s & 0xffff) + (s >> 16)这一行。有人会图省事写成s += s >> 16,但这样只做了一次回卷,如果累加过程中持续产生进位,最终结果就会出错。我在调试UDP校验和时在这个地方翻过车,最后是拿全零数据和已知报文逐字节比对才定位到问题。另外,发送端计算校验和时,报文里的校验和字段本身要先置为0,接收端则要把整个伪头部、UDP头和数据一起做校验,结果应为0xFFFF才算通过。

再看字节序转换和hexdump,这两个是排障的利器:

def to_net_bytes(value: int, length: int = 2) -> bytes: """把整数转成网络字节序(大端)的字节串,length指定字节数。""" return value.to_bytes(length, byteorder='big') def hexdump(data: bytes, base_addr: int = 0) -> str: """把字节串按16字节一行打印,左边是偏移地址,中间十六进制,右边ASCII可见字符。""" lines = [] for offset in range(0, len(data), 16): chunk = data[offset:offset + 16] hex_part = ' '.join(f'{b:02x}' for b in chunk) ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk) lines.append(f'{base_addr + offset:04x} {hex_part:<48} {ascii_part}') return '\n'.join(lines)

网络字节序固定是大端,Python的socket库在做地址转换时会自动处理,但自己拼报文时不会。to_net_bytes保证了塞进头部字段的端口号、长度、校验和按正确顺序排列。hexdump则是给报告加分的工具,老师看到带偏移地址的十六进制转储,就知道你有真正的报文级调试能力,而不是拿print("done")糊弄。

2.3 统一入口:每个实验一条命令跑完

四个模块不要各写各的main函数,收进一个带子命令的入口文件。我用argparse实现一个很轻的CLI,每个实验对应一个子命令,运行参数里带URL、模拟丢包率、窗口大小等,报告里写“运行命令如下”,老师照着跑就能复现。

# 各实验统一从lab_runner.py进入,参数含义在下方说明 python lab_runner.py http get --url http://127.0.0.1:8080/index.html python lab_runner.py udp send --dest 127.0.0.1 --port 9000 --payload "hello" python lab_runner.py stopwait run --loss 0.2 --timeout 0.5 python lab_runner.py window run --window 4 --loss 0.1 --packets 100

参数说明:url是HTTP实验要请求的地址,支持http和https,https的证书处理在避坑章节单独说明;dest和port是UDP目标地址端口;loss是模拟丢包率,0到1之间的浮点数;timeout是停等协议超时时间,单位秒;window是滑动窗口大小,单位是报文数;packets是要发送的包总数。这里的丢包率和窗口大小是实验报告里的两个核心自变量,改参数跑多组是后面做对比曲线的基础。

报告里的“实验环境”一节也需要固定格式。我建议在入口里加一个--env开关,输出当前Python解释器和主要库的版本,做报告时直接贴结果:

def cmd_env(args): """输出当前运行环境信息,供实验报告“实验环境”一节使用。""" import platform import sys print(f'Python: {sys.version.split()[0]}') print(f'OS: {platform.platform()}') for mod in ('socket', 'struct', 'time', 'threading'): try: __import__(mod) print(f'{mod}: 标准库') except ImportError: print(f'{mod}: 缺失')

逻辑说明:socket、struct、time、threading都是标准库,不涉及第三方依赖,代码在Python 3.6以上的机器上能直接跑。第三方库不是不能用,而是多一道安装步骤,有些评审环境禁止在线安装,纯标准库最稳。这一节内容虽然简单,但能把“为什么实验环境可复现”讲清楚,很多报告里环境写得含糊,答辩时一问就露馅。

3. 手写HTTP请求与报文解析:只用socket不看requests

3.1 发送GET请求的最小实现

做HTTP实验,得分点不在于“你能访问一个网页”,而在于你能解释请求报文里每一行的含义。所以这里不用requests,而是用socket手工拼HTTP请求文本,再完整读取服务器的二进制响应。

import socket def send_http_get(host: str, port: int, path: str) -> bytes: """用socket发一个HTTP/1.1 GET请求,返回服务器响应的完整字节流。""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5.0) # 5秒没响应就抛异常,防止程序挂死 sock.connect((host, port)) # HTTP请求报文:请求行 + 首部字段 + 空行 request = ( f'GET {path} HTTP/1.1\r\n' f'Host: {host}:{port}\r\n' f'Connection: close\r\n' f'User-Agent: lab-http-client\r\n' '\r\n' ) sock.sendall(request.encode('utf-8')) chunks = [] while True: data = sock.recv(4096) if not data: break chunks.append(data) sock.close() return b''.join(chunks)

逻辑说明:socket(AF_INET, SOCK_STREAM)创建TCP套接字,connect完成三次握手,sendall发送完整的请求字节流。HTTP请求必须用\r\n\r\n结尾的空行表示头部结束,否则服务器一直等请求头。recv在TCP对端正常关闭后会返回空字节串b'',正好作为循环退出条件。

参数说明:host传主机名或IP,port默认80,path是资源路径。Connection: close这个请求头很重要,如果不加,服务器可能按HTTP/1.1的keep-alive机制保持连接,recv循环会一直阻塞直到超时,拿不到完整响应。如果你就是想测keep-alive,那不能靠断开判断结束,要靠Content-Length或chunked编码来解析边界,这是后面一节的内容。

3.2 响应解析:状态行、首部字段、正文的边界判定

请求发出去后,收到的响应是二进制流,格式是状态行、若干首部字段、空行、实体正文。解析的关键在于\r\n分隔的位置:状态行和首部字段是按行分界的,首部结束后是一个空行,之后是正文。如果响应头里有Content-Length,正文长度就固定;如果是chunked编码,要按chunk size逐块解析。实验里处理Content-Length这一种就够。

def parse_http_response(raw: bytes) -> dict: """把HTTP响应字节流解析成状态行、头部字典和正文三部分。""" head, sep, body = raw.partition(b'\r\n\r\n') if not sep: raise ValueError('未找到头部与正文的分隔符,报文不完整') lines = head.split(b'\r\n') status_line = lines[0].decode('utf-8', errors='replace') headers = {} for line in lines[1:]: if b':' in line: name, _, value = line.partition(b':') headers[name.decode('utf-8').strip().lower()] = ( value.decode('utf-8').strip() ) declared = int(headers.get('content-length', 0)) if declared and len(body) != declared: raise RuntimeError(f'Content-Length不匹配:声明{declared},收到{len(body)}') return { 'status_line': status_line, 'status_code': int(status_line.split(' ')[1]), 'headers': headers, 'body': body, 'body_text': body.decode('utf-8', errors='replace'), }

逻辑说明:partition(b'\r\n\r\n')把响应切成两段,前半段是状态行和首部,后半段是正文。首部字段按冒号拆开名字和值,名字统一转小写,这样后续用headers['content-length']访问时不用考虑大小写。Content-Length断言是这个函数最提分的地方,只有确认实际收到的字节数和声明一致,才说明recv循环完整读取了响应,否则程序可能只打印前几百字节却没被发现。

参数说明:status_code用于快速判断请求是否成功,报告里写“服务器返回200 OK”就靠它;headers字典建议报告里挑content-type、content-length、server、date几个字段展示,正好对应课程里HTTP首部字段一节。如果把头部字段全部打印出来,报告会显得没有筛选能力,老师反而觉得你在凑字数。

3.3 输出报告素材:程序打印加抓包对照

实验报告需要截图。与其用截图工具截控制台,不如让程序直接生成紧凑的结果块。解析完成后输出关键信息:

# HTTP实验收尾:打印核心字段,供报告截图使用 resp = parse_http_response(raw_data) print(f'状态行: {resp["status_line"]}') print(f'状态码: {resp["status_code"]}') print('关键响应头:') for k in ('content-type', 'content-length', 'server', 'date'): if k in resp['headers']: print(f' {k}: {resp["headers"][k]}') print(f'正文前100字节: {resp["body_text"][:100]!r}')

运行这个实验前,先在本机起一个简单的HTTP服务。Python自带的模块就能做到:

# 在项目目录下启动简易HTTP服务,绑定本机8080端口 python -m http.server 8080 --bind 127.0.0.1

然后另开一个终端跑实验客户端。报告里我一般建议放两张图:一张是程序输出的请求行和响应字段,另一张是Wireshark里捕获到的同一个请求的原始报文。两边一对照,Wireshark里看到的GET /index.html HTTP/1.1\r\nHost:...和代码里拼出来的字符串逐字符对应,这个实验的报文分析部分就拿稳了。注意Wireshark要在回环接口上抓包,具体原因避坑章节会说。

4. 停等协议与滑动窗口:把TCP可靠传输从课本搬到代码里

4.1 为什么要在模拟链路上跑协议,而不是直接连真实网络

TCP可靠传输是最难直观看到的部分,因为真实网络里丢包和乱序你控制不了。做这个实验的常见做法是:自己写一个模拟链路层,在发送端和接收端之间夹一层带丢包率、延迟参数的“虚拟通道”,每次重传都在掌控中,报告里可以精确写“丢包率10%时发生3次超时重传”。

我见过有人在两台物理机器之间用真实网络做停等实验,结果局域网太稳定,跑了十分钟一次重传都没发生,报告里根本没有重传数据可写。另一个极端是在本机回环上跑,但没有丢包逻辑,纯属自欺欺人。下面实现一个带随机丢包的模拟链路类,停等协议和滑动窗口都用它:

import random import time class LossyChannel: """模拟不可靠链路:以loss_rate概率丢弃数据,以delay秒模拟传播时延。""" def __init__(self, loss_rate: float = 0.1, delay: float = 0.01): self.loss_rate = loss_rate self.delay = delay def send(self, sender, receiver, packet): # 先判断是否丢包,再判断是否延迟,两个过程互不影响 if random.random() < self.loss_rate: return time.sleep(self.delay) receiver.receive(packet)

逻辑说明:LossyChannel是双方共享的虚拟链路。send先判断随机数是否小于loss_rate,是则直接返回,相当于报文在链路中被丢弃,接收方无感知;否则sleep模拟传播时延,再调用接收方的receive方法。丢包逻辑不侵入协议代码,跑丢包实验只需要改构造时的loss_rate参数。

参数说明:loss_rate建议取0、0.05、0.1、0.2四档,覆盖“无丢包”“轻度丢包”“中度丢包”三种场景;delay设成固定值10ms,不要用随机抖动,固定时延才能在对比实验里保持单一变量。报告里窗口大小和丢包率两个参数正交,一次只改一个,曲线才讲得清因果。

4.2 停等协议发送方:超时重传的定时器怎么设计

停等协议逻辑用伪代码描述像流程图,但落到Python时,难点在定时器。发送方发一个包后要等ACK,等不到就重发,每个send动作必须在“等待超时”和“收到ACK”两条路径之间切换。

import time class StopWaitSender: """停等协议发送方:每发一个数据包就等ACK,超时则重传。""" def __init__(self, channel, receiver, timeout: float = 0.5): self.channel = channel self.receiver = receiver self.timeout = timeout self.ack_received = None # 由接收方线程写入当前收到的ACK序号 def send_data(self, data_packets): for seq, packet in enumerate(data_packets): self.ack_received = None while self.ack_received != seq: print(f'[发送] seq={seq}') self.channel.send(self, self.receiver, (seq, packet)) deadline = time.time() + self.timeout while time.time() < deadline: if self.ack_received == seq: print(f'[确认] seq={seq}') break time.sleep(0.01) # 轮询ACK,避免忙等占满CPU if self.ack_received == seq: break print(f'[超时] seq={seq},触发重传')

逻辑说明:外层循环遍历所有数据包,内层循环负责等到ACK。deadline用time.time() + timeout计算绝对超时点,而不是每轮减剩余时间,这样判断不会因为循环开销累积误差。ack_received由接收方的ACK处理线程写入,发送方轮询它,每10ms检查一次,避免忙等。内层循环退出后检查是否收到ACK,没有就打印超时并回到while循环重传。

序列号从0开始,每发送一个包递增。真实TCP的序号是字节序号且初始值随机,这里用包序号是为了让日志更直观。如果想更接近TCP,可以改成分组编号加初始随机偏移,但报告的讲解重点会从“重传机制”偏到“序号空间管理”,没有必要。参数timeout设0.5秒时,在10ms延迟链路上足够从容;如果链路延迟改成100ms,超时要相应调大到1秒以上,否则会频繁误判超时,报告里写参数时把这条对应关系写清楚就能加分。

4.3 滑动窗口发送方:窗口移动、累积确认和缓冲区清理

滑动窗口比停等复杂在发送方不必等ACK就能连续发多个包,所以要有两个核心状态:窗口左沿base和下一个待发序号next_seq。收到累积ACK时,base整体右移,缓冲区里已经确认的包释放。

class SlidingWindowSender: """滑动窗口发送方:窗口大小固定,收到累积ACK后窗口左移。""" def __init__(self, channel, receiver, window_size: int = 4): self.channel = channel self.receiver = receiver self.window_size = window_size self.base = 0 # 窗口左沿:最早的未确认包 self.next_seq = 0 # 下一个要发送的序列号 self.buffer = {} # seq -> packet,保存已发未确认的包 self.ack_received = -1 # 累积ACK:0..ack_received都已收到 def send_all(self, total: int): while self.base < total: # 窗口没满就继续发送 while (self.next_seq < self.base + self.window_size and self.next_seq < total): print(f'[发送] seq={self.next_seq}') self.channel.send(self, self.receiver, (self.next_seq, f'data-{self.next_seq}')) self.buffer[self.next_seq] = f'data-{self.next_seq}' self.next_seq += 1 # 收到累积ACK就把窗口整体左移 if self.ack_received >= self.base: new_base = self.ack_received + 1 for seq in list(self.buffer): if seq < new_base: del self.buffer[seq] print(f'[窗口左移] base {self.base} -> {new_base}') self.base = new_base else: time.sleep(0.01)

逻辑说明:第一个while循环把窗口内允许发送的包全部发出;第二个if分支处理累积ACK的推进。所谓累积ACK,就是ACK=n表示0到n-1的包都已正确收到,所以base可以跳到n+1,窗口左移后,缓冲区里小于新base的包条目清理掉。这是在接收方每收到一个有序包就更新ack_received的前提下成立的。代码里没有把接收线程写进来,是为了让发送方状态机更突出,报告里需要说明“简化模型只体现窗口移动逻辑”。

window_size是窗口内可容纳的最大未确认包数,建议实验时分别跑1、4、8、16四组。total是要发送的包总数,建议至少100个,这样才能在丢包率变化时看出吞吐量差异。窗口大小为1时滑动窗口退化为停等协议,这个对照实验能验证代码的正确性:两种实现的吞吐量应该基本一致。

4.4 跑一轮对比实验,把吞吐量曲线填进报告

代码写完后,拉开差距的是数据组织方式。用bash脚本循环调用滑动窗口实验,分别控制丢包率和窗口大小,记录总耗时:

# 固定窗口=4,改变丢包率,记录发送100个包的耗时 for loss in 0 0.05 0.1 0.2; do python lab_runner.py window run --window 4 --loss $loss --packets 100 done # 固定丢包率=5%,改变窗口大小,再跑一组 for win in 1 4 8 16; do python lab_runner.py window run --window $win --loss 0.05 --packets 100 done

把两组命令的输出整理进表格,算出发送速率,就是报告里的吞吐量对比数据。曲线会呈现两个结论:窗口增大吞吐量提升,但超过某个阈值后提升变缓,因为瓶颈转向了链路时延和确认往返时间;丢包率升高时重传增多,有效吞吐量明显下滑。这两个结论写进“实验结果分析”,比空喊“实现了可靠传输”有说服力得多。报告的表格里要注明每次实验的窗口、丢包率、总包数、耗时、推算吞吐量五列,老师扫一眼就知道你的实验设计是完整的。

5. 计算机网络实验避坑指南:最容易踩到的几个坑

5.1 recv永远收不齐数据:TCP是字节流不是报文

现象:发送一个完整请求,recv只返回一部分,或者第一次recv拿到了头部,第二次recv却是空的,程序报“Content-Length不匹配”。

原因:TCP是面向字节流的协议,没有“报文边界”概念。应用层read可能拿到半包、整包甚至多个包粘在一起。如果recv只读一次就退出,多读或漏读都是正常的。

解决:在第3章的send_http_get里已经用循环接收解决——while True里不停recv直到拿到空字节串。另一个关键点:实验里必须在请求头加Connection: close,否则服务器不主动断开连接,recv循环阻塞到超时抛socket.timeout。如果确实要测keep-alive,就不能靠断开判断数据结束,而是按Content-Length收完指定字节再退出循环。

5.2 校验和怎么算都不对:进位回卷写错

现象:自己构造的UDP报文发出去,对方一直不回包,Wireshark里协议栈标记checksum错误,包字体变红。

原因:大部分人在实现校验和时漏了“进位回卷”。Internet校验和不是简单的16位累加取反,每加一个16位字后,必须把超过16位的进位回卷到低16位,而且要反复执行直到没有进位。有人写的s += s >> 16只做了一次回卷,累加过程中产生多次进位时结果必错。

解决:按2.2节的checksum实现,关键是s = (s & 0xffff) + (s >> 16)写在for循环里,每个16位字相加后都做回卷。完成后用已知数据自测:全零数据校验和应为0xFFFF,单字节0x00补零后校验和也应为0xFFFF。这两个自测用例写进报告,能直接证明算法实现正确。

5.3 校验和字段字节序反转:高字节变低字节

现象:校验和算出来是0x1234,Wireshark里显示0x3412。

原因:计算结果是16位整数,写进报文时要按网络字节序大端写入,即高字节在前。有人用struct.pack('<H', checksum)用了小端,于是高低字节互换了。

解决:用struct.pack('!H', checksum)或第2章的to_net_bytes(checksum, 2)。注意UDP伪头部里的IP地址字段也按大端读取,不要逐个字节做反转。字节序这类问题最折磨人,看起来运行成功但协议栈不认,自测方法就是把Wireshark的checksum字段值和代码输出逐一对比,能对上就说明字节序没写反。

5.4 抓包工具在虚拟机里抓不到自己的流量

现象:在虚拟机上跑实验,Wireshark选了网卡后只看到ARP和广播,看不到自己程序的TCP/UDP流量。

原因:虚拟机默认NAT模式下,流量经过宿主机的NAT引擎转发,虚拟网卡上不一定能看到本机回环流量;也可能抓包网卡选错,选成了仅主机模式。

解决:把虚拟机的网络适配器改成桥接模式,让虚拟机和宿主机在同一网段;或者更省事:实验里程序都连127.0.0.1回环地址,Wireshark选Loopback接口直接抓。回环实验一样能证明协议实现,因为TCP/UDP协议栈的处理路径没有变,只是物理网卡变成了虚拟回环接口。抓不到包时优先检查接口选择,其次是网络模式。

5.5 实验报告“截图漂亮但逻辑不自洽”

现象:报告里有截图、有代码,但代码输出里的序列号和截图对不上;或者文字写“丢包率20%时有大量重传”,截图里却一次重传记录都没有。

原因:报告是后补的,截图从别的实验复制或重新跑时改了参数。

解决:实验日志要自带参数前缀。比如停等协议的每行输出都前置[loss=0.2][seq=3],截图里天然带实验条件,后补时一眼能看出不一致。每张截图下方用一行字写清“运行命令+关键参数”,这个习惯是我在帮人看课设代码时验证出来的,凡是这么写的报告,基本不会被老师挑格式毛病。

6. 把实验方案升级成可复用脚本:自动校验、配置化参数和验收标准

这套方案在校内课程场景已经够用,但想让它经得起反复修改和复用,我建议加三层。

第一层是自动校验。把Content-Length断言、校验和自测、重传计数、窗口推进检查都收进一个验证脚本,跑完实验直接输出通过/失败。改代码时不必逐个实验人工复查。

# validate_report.py 片段:校验各实验输出是否满足验收条件 import json results = { 'http_status_ok': resp['status_code'] == 200, 'http_content_length_ok': len(resp['body']) == int(resp['headers']['content-length']), 'checksum_zero_ok': checksum(b'\x00\x00\x00\x00') == 0xffff, 'stopwait_retransmit_ok': retransmit_count > 0 if loss_rate > 0 else True, 'window_base_advance_ok': final_base == total_packets, } print(json.dumps(results, indent=2, ensure_ascii=False))

逻辑说明:每个检查项对应一个布尔表达式,只有前四项全为true且窗口推进正确,才说明这轮实验完整跑通。loss_rate > 0条件保证了“无丢包时要求重传次数大于0”这种错误判断不会出现。

第二层是参数配置化。把丢包率、时延、窗口、超时从代码里抽出来,放到一个配置文件或命令行参数里。当前代码已经通过lab_runner.py接收这些参数,可以进一步加一个config.yaml或简单的config.json,报告里直接贴配置文件内容,老师和评审都能看到你的默认参数,不用猜。

第三层是连接建立与释放的状态机。目前滑动窗口实验只覆盖数据传输阶段,你可以继续加三次握手和四次挥手的日志输出,把连接状态迁移打印出来。这样报告就从“两个孤立协议点”扩展成“一条完整传输链路”,在课程设计里的完备性会明显高一个档次。

这几层升级的内在逻辑,是我在校验某个带丢包率前缀的协议日志时悟出来的:网络实验的第一原则是可观测性,日志和断言比代码本身更能体现工程素养。后来我把“所有日志必须带参数前缀、所有结论必须有断言支撑”写进了自己的实验规范,就再没在答辩时被问住过。

希望这篇东西能帮你把计算机网络实验做成一套真正跑得动、讲得清、识得破的方案,祝顺利。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 7:10:29

LSTM与卡尔曼滤波联合建模:解决状态估计中的模型失配与噪声时变问题

简介&#xff1a;本资源是一个面向机器学习与信号处理初学者的温度预测实践项目&#xff0c;聚焦LSTM神经网络与卡尔曼滤波器的协同建模方法&#xff0c;适用于时间序列预测、传感器数据去噪及状态估计等实际场景。项目已通过实测验证&#xff0c;提供完整可运行代码与清晰使用…

作者头像 李华
网站建设 2026/10/18 7:09:57

Agent 烧的钱,大半花在了不该花的地方

给团队上的 Agent 跑了两个月&#xff0c;账单出来就是一个总数&#xff1a;这个月四千七百多。你想知道是哪几个任务烧的&#xff0c;后台只能按天拉曲线&#xff0c;往下再拆就拆不动了。这不是你一个人的问题。Agent 和聊天框不一样&#xff0c;一次任务背后是几十次调用&am…

作者头像 李华
网站建设 2026/10/18 7:06:07

会议录音生成周报怎么操作?AI工具如何使用

日常使用中常见操作偏差包括&#xff1a;零散录音归集不全导致跨会议待办遗漏、混合方言场景关键信息识别偏差、隐私类会议内容上传后的数据风险。所有操作环节没有统一标准路径&#xff0c;核心校验点集中在素材完整度、参数匹配度、场景适配性三个维度。录音生成周报的操作环…

作者头像 李华
网站建设 2026/10/18 7:05:45

一文搞懂更新DOM:基础API、性能陷阱与框架原理

有一次在答疑群里看到有人问“更新DOM是什么操作”&#xff0c;我第一反应是&#xff1a;这个问题问得特别好。很多新人在浏览器控制台里写过document.querySelector()&#xff0c;用过innerHTML拼过列表&#xff0c;也见过框架项目里那句“不要直接操作 DOM”&#xff0c;但真…

作者头像 李华
网站建设 2026/10/18 7:05:12

用Dart统一Flutter与鸿蒙构建:GitHub Actions跨端流水线治理实践

如果你的 Flutter 工程和鸿蒙应用工程挤在同一个仓库里&#xff0c;发版前要在两三套 CI 之间来回切换&#xff0c;脚本一会儿 Python 一会儿 Shell 一会儿又是 Node Action&#xff0c;那这篇内容大概率对你有用。核心是 actions_toolkit_dart 这层工具库。这段时间我一直在折…

作者头像 李华