计网实验报告可复现:抓包取证、Socket并发与可靠传输 简介这份西安电子科技大学计算机网络课程实验报告面向正在学习计网课程、需要完成实验或整理报告的学生也可供复习网络基础配置的读者参考。内容围绕六个实验展开双绞线制作与网络设备访问配置、交换机VLAN划分、Windows下DNS与DHCP服务器配置、WEB与FTP服务搭建、路由器基础配置以及静态路由和三层交换机配置每部分包含实验目的、设备环境、拓扑与配置截图、要点和心得体会。压缩包内共1个doc文件大小约3.67MB汇总成一份可直接查阅的文档便于按实验模块对照操作与查漏补缺。已有1197人学习适合作为课程实验的过程记录和配置思路参考帮助读者理解星型以太网、IP与子网掩码、VLAN隔离、域名解析、动态地址分配、IIS与FTP服务、路由表及三层交换等知识点。1. 实验报告不是指导书的复印件一份能被别人照着复现的记录长什么样期末前一周某高校的实验室里最常见的场景是有人把实验指导书的截图拼成三十页 PDF交上去被打回来重写。计算机网络这门课的实验报告真正被审的从来不是排版和页数而是证据链别人拿着你写的命令、过滤表达式和字段表能不能在他自己的机器上把同一个现象再抓一遍、再对一次数。这篇笔记讲的就是这件事——抓包取证怎么留痕Socket 编程怎么把并发和异常写成可核对的记录可靠传输的状态机怎么用可观察的量说话以及那些让报告看起来完整、其实被追问一句就露馅的地方。适合正在做计网课程实验、或者要把网络行为写成可复核文档的读者。2. 抓包取证Wireshark 显示过滤与 tshark 字段导出的证据链写法抓包实验是计网课程里最容易拿分也最容易丢分的一块。拿分是因为现象直观丢分是因为大部分人只交了一张截图图上没有任何上下文不知道是在哪个接口抓的、抓的时候跑了什么命令、过滤条件是什么、时间基准从哪一刻算起。换个同学拿着这张图根本没法复现。我在整理这类材料时给自己定了一条硬规矩任何一张抓包截图旁边必须配一行能直接粘贴执行的命令否则这张图就等于没做。2.1 一条合格的证据链得凑齐四样东西先说清楚“证据链”具体指什么。它不是一个抽象概念而是四件可以逐项核对的东西时间基准、过滤表达式原文、关键字段表、结论句。时间基准指frame.time_relative这类相对时间而不是绝对时间戳绝对时间戳受抓包机时钟和时区影响读者没法对齐过滤表达式原文指的是你用了哪条显示过滤器把无关流量筛掉比如tcp.port8080 tcp.flags.syn1这条必须原样写进报告关键字段表是把同一批包里你要论证的那几个字段抽出来排成表格让读者一眼看出序号、标志位和确认号之间的对应关系结论句则要落到具体数字上比如“SYN 到 SYN-ACK 之间耗时 0.42 ms”而不是“握手成功”。很多报告翻车是因为把“Wireshark 里看到了三次握手”当成结论。这句话既不可证伪也不可复现。换成“在 lo 接口上以tcp port 8080为捕获过滤器抓包客户端端口 51234、服务端端口 8080第 1 帧 SYN 带 seq0第 2 帧 SYN-ACK 带 seq0 ack1第 3 帧 ACK 带 seq1 ack1相对时间分别为 0.000、0.042、0.048 秒”性质就完全不一样了任何一个装了同版本工具的人都能对着跑一遍。下面这张表是我在每份抓包报告开头都会附的字段说明把“图里那条横线到底是什么意思”一次讲清楚字段抓包里的含义报告里为什么必须写frame.number帧在整个 pcap 里的绝对序号结论句引用帧号才不会指错frame.time_relative相对第一帧的时间单位秒唯一可跨机器对齐的时间基准tcp.seq/tcp.ack相对序号与相对确认号判断重传、乱序、丢包的依据tcp.flags.str标志位组合的字符串形式比单独看 SYN/ACK 位更不容易误判tcp.analysis.retransmission工具推断的重传标记报告里要注明“工具推断”不是原始字段tcp.len本帧承载的 TCP 载荷长度算吞吐、判断是否纯 ACK 的依据表格最后一行值得单独强调tcp.analysis.*这一族是 Wireshark 根据序列号推出来的派生字段不是协议头里真实存在的东西。报告里引用它必须写清楚是推断值否则一旦读者的抓包点位置不同比如抓在中间设备上同一个包可能不再被判为重传结论就对不上了。这一点属于典型的血泪经验被追问过一次就再也不会忘。2.2 先造可控流量再谈抓包对着浏览器乱点然后抓一堆包是效率最低的做法。比较稳的方式是自己起一个行为完全确定的被测服务让流量的数量、顺序、大小都在你的掌控之内。下面这段代码起了一个本地 HTTP 服务每次 GET 返回固定长度的纯文本方便你在抓包结果里精确数出请求和响应的字节数from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer class Handler(BaseHTTPRequestHandler): # HTTP/1.1 才会复用连接默认的 1.0 每条请求后关闭 protocol_version HTTP/1.1 def do_GET(self): body bhello-cn-lab\n # 固定 13 字节便于核对 tcp.len self.send_response(200) self.send_header(Content-Type, text/plain) self.send_header(Content-Length, str(len(body))) # 必须给否则靠 FIN 分帧 self.end_headers() self.wfile.write(body) def log_message(self, fmt, *args): pass # 关掉默认日志避免干扰终端输出 if __name__ __main__: srv ThreadingHTTPServer((127.0.0.1, 8080), Handler) srv.serve_forever()逻辑上没几行但每行都有讲究。protocol_version HTTP/1.1决定了连接能不能复用如果你要观察的是“一个 TCP 连接上跑多条 HTTP 请求”就必须设这一行否则每条请求结束都会看到一次 FIN抓出来的图会让你误以为服务端在频繁断连。Content-Length是响应分帧的唯一依据少了这个头客户端只能靠连接关闭来判断响应结束那你在抓包里看到的结束标志就不是长度而是四次挥手结论会整个偏掉。ThreadingHTTPServer而不是普通的HTTPServer是因为前者每来一个连接起一个线程你能同时压多个并发连接而不会互相阻塞。参数方面端口选 8080 是为了避开需要特权的低位端口在非 root 环境下也能直接绑定绑定127.0.0.1而不是0.0.0.0是为了让流量只走回环不经过任何外部网络抓出来的包干净且没有隐私顾虑。这两点看起来是小事但在多人共用一台实验机的时候绑定范围选错会导致端口冲突和抓包串流两个问题同时出现。流量造好之后抓包这一步用命令行工具比图形界面更适合留证据因为命令本身就是报告内容的一部分# 终端 1后台抓包只抓 8080 端口的 TCP20 秒后自动停 tshark -i lo -f tcp port 8080 -w handshake.pcap -a duration:20 # 终端 2起服务并制造三条请求 python3 http_echo.py sleep 1 for i in 1 2 3; do curl -s -o /dev/null http://127.0.0.1:8080/item$i; done-i lo指定回环接口这一步是最常出错的地方Linux 上回环叫lo部分系统上需要在下拉框里单独选中回环适配器选成物理网卡的话一个包也抓不到然后你会以为是服务没起来。-f是捕获过滤器用 BPF 语法在抓取阶段就丢包能显著减小文件体积它和 Wireshark 里那个显示过滤器不是一回事别混用。-a duration:20表示抓满 20 秒自动停止并落盘比手动 Ctrl-C 更可控尤其是在写脚本自动跑的时候。2.3 tshark 一行命令把 pcap 变成表格图形界面适合看不适合贴。报告里真正有价值的是把关键字段导成 CSV 或对齐文本让读者能逐行核对。下面这条命令是我每次都会跑一遍的tshark -r handshake.pcap \ -Y tcp.port 8080 tcp.flags.syn 1 \ -T fields \ -e frame.number -e frame.time_relative \ -e ip.src -e tcp.srcport -e tcp.dstport \ -e tcp.flags.str -e tcp.seq -e tcp.ack -e tcp.len \ -E headery -E separator, -E quoted \ handshake_syn.csv逐段解释-r读已有文件-Y是显示过滤器语法和 Wireshark 界面里那个输入框完全一致所以报告里写一次过滤表达式界面和命令行都能复用。-T fields表示按字段输出而不是按包输出这是把抓包变成表格的关键开关。每个-e跟一个字段名字段名可以在 Wireshark 状态栏里直接看到不用背。-E headery输出表头-E separator,用逗号分隔-E quoted给字符串字段加双引号防止标志位那种带逗号的值把列冲散。常见做法是先跑一个不带-Y的版本看看有多少条再逐步收窄过滤条件。如果导出的表是空的九成是显示过滤器写错了tcp.port只匹配 TCP如果你抓的是 UDP 的 DNS 查询就得换成udp.port 53想同时匹配两个端口要用tcp.port 8080 || tcp.port 8081写成逗号是不行的。另外tcp.flags.syn 1会把 SYN-ACK 也算进来因为 SYN-ACK 的 SYN 位同样是 1。如果你要的只是纯 SYN得写成tcp.flags.syn 1 tcp.flags.ack 0。这个坑我在第一次交报告时就踩过导出的表里混进了 SYN-ACK导致“客户端发起连接数”多算了一倍。导出之后表格不会自己变成结论还需要一步人工对照。我的习惯是把客户端端口、服务端端口、三个包的 seq/ack 关系写成一句话再附上 CSV 的前三行。这样读者既能看结论也能验证结论是不是从数据里来的。抓包实验的评分点基本都落在这个“数据到结论”的推导上而不是你抓了多少个包。3. Socket 编程实验从阻塞回显到 select 多路复用的三个必调参数Socket 编程这一块大部分人卡住不是因为不会写socket()而是因为写出来的东西只能在自己机器上、只连一个客户端、只发一条短消息的时候正常工作。课程实验的验收往往会加两个动作同时连多个客户端、发一条超过缓冲区大小的长消息。这两个动作一加阻塞式写法立刻原形毕露。所以这部分我一般按“能跑通的最小骨架 → 参数怎么调 → 并发怎么改 → 压测数据怎么留”四步走每一步都在报告里留一条对应的记录。3.1 阻塞式 TCP 回显二十行代码与四个必调参数先看能跑通的最小版本所有参数都显式写出来不用默认值import socket HOST, PORT 127.0.0.1, 9000 BACKLOG 128 # 已完成三次握手但还没被 accept 的连接队列长度 BUF 4096 # 单次 recv 的上限不是“一条消息”的长度 def serve(): srv socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 允许绑定处于 TIME_WAIT 的地址避免重启时 Address already in use srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind((HOST, PORT)) srv.listen(BACKLOG) print(flistening on {HOST}:{PORT}) while True: conn, addr srv.accept() # 阻塞点 1没有新连接就停在这 with conn: conn.settimeout(30) # 防止某个客户端挂住不放 while True: data conn.recv(BUF) # 阻塞点 2没有数据就停在这 if not data: # 对端关闭recv 返回空字节 break conn.sendall(data) # sendall 内部循环保证全部写出 if __name__ __main__: serve()四个参数值得在报告里各写一句话。SO_REUSEADDR解决的是服务端主动关闭后端口进入 TIME_WAIT、立刻重启绑不上端口的问题这个在反复调试阶段几乎每天都会遇到加上它之后不用再等两分钟。BACKLOG不是“最大连接数”而是内核里已完成握手等待应用层accept的队列长度队列满了之后新来的 SYN 会被丢弃或收到 RST所以压测时如果并发上不去先怀疑这个值太小而不是代码写错。BUF决定单次recv最多拿多少字节TCP 是字节流协议recv(4096)可能只返回 100 字节也可能一次返回 4096 字节绝不能假设“一次 recv 等于一条完整消息”。settimeout(30)是给阻塞调用加一个上限避免某个客户端连上不发数据把整个单线程服务拖死。sendall和send的区别也要写清楚这是新手最容易忽略的一处。send返回实际写出的字节数可能小于你传入的长度sendall内部循环直到全部写出或者抛异常。报告里如果只写send又不检查返回值长消息场景下就会丢数据而且丢得很隐蔽——短消息测试完全正常一上长度就出问题。3.2 用 select 改成事件驱动顺带解释粘包阻塞版只能顺序服务第二个客户端必须等第一个断开。改成多路复用之后一个线程就能盯住所有连接。下面这个版本用select逻辑直白适合写进报告当对照组import select import socket srv socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind((127.0.0.1, 9000)) srv.listen(128) srv.setblocking(False) # 非阻塞是 select 版本的前提 rlist [srv] pending {} # 每个连接一个应用层缓冲区 while True: readable, _, _ select.select(rlist, [], [], 1.0) # 1 秒超时便于退出 for s in readable: if s is srv: conn, _ srv.accept() conn.setblocking(False) rlist.append(conn) pending[conn] b else: chunk s.recv(4096) if not chunk: rlist.remove(s) pending.pop(s, None) s.close() else: pending[s] chunk # 先攒 s.sendall(chunk) # 再原样回写关键点在最后两行。select告诉你“这个 socket 可读”但没告诉你“读到的是一条完整消息”。如果客户端连续发了两条短消息两次recv可能一次拿到两条拼接的内容也可能第一条被切成两半分两次拿到这就是粘包和拆包。解决办法只有一个应用层自己定边界要么用长度前缀要么用分隔符然后在缓冲区里判断“够不够一条”。报告里如果能写出这一段并且给出一次“连续发三条 5 字节消息、服务端分两次读到 12 字节”的实测记录说服力比贴十张截图都强。非阻塞模式还有一个必须处理的返回值recv在没有数据可读时会抛BlockingIOErrorsendall在发送缓冲区满时也可能阻塞。select保证的是“可读”而不是“一定读得到完整数据”所以真实的非阻塞服务端通常还要维护一个写缓冲队列把没发完的部分挂起来等下一次可写事件。课程实验做到select这一层已经够用但报告里提一句这个边界能明显看出你知道自己在做什么。3.3 并发压测怎么留数据而不是只说“能同时连多个”“能同时连多个客户端”是一句没法验证的话。可验证的写法是给出并发数、每连接消息数、成功率和耗时。下面这段压测脚本会启动 200 个线程每个线程建一条连接、往返 20 次最后统计成功连接数import socket import threading import time N, MSG, ROUNDS 200, bping * 64, 20 ok 0 lock threading.Lock() def worker(): global ok try: with socket.create_connection((127.0.0.1, 9000), timeout5) as s: for _ in range(ROUNDS): s.sendall(MSG) got b # TCP 是字节流必须循环读到长度够不能只 recv 一次 while len(got) len(MSG): chunk s.recv(4096) if not chunk: return got chunk with lock: ok 1 except (ConnectionError, OSError): pass t0 time.time() threads [threading.Thread(targetworker) for _ in range(N)] for t in threads: t.start() for t in threads: t.join() print(fsuccess{ok}/{N} elapsed{time.time() - t0:.2f}s)MSG取 256 字节、ROUNDS取 20这两个数字不是随手写的消息短到能塞进一个 MSS往返次数多到足以暴露缓冲区处理不当的问题同时又不会让测试跑太久。timeout5是为了在服务端卡死时不至于永远等下去。那段while len(got) len(MSG)的循环是整个脚本的核心少了它你就会遇到“明明服务端回写了客户端却偶尔读不全”的诡异现象而这类现象往往会被误判成服务端 bug。压测结果写进报告时至少要带三样并发数、成功率和耗时再加一句环境说明同机回环、单线程select服务端。同机回环的时延极低测出来的数字不能拿去和真实网络比这一点要在报告里主动声明反而显得严谨。如果两个版本的对比数据都有——阻塞版在 200 并发下的成功率和select版的成功率——那这张对比表基本就是这一章最有分量的内容。4. 可靠传输实验停等、GBN 的窗口参数与丢包注入怎么在报告里说清楚可靠传输这部分是计网课程里理论味最重的一块也是最容易写成“抄一遍协议流程”的地方。把它拉回可复现的关键是把“协议”变成“状态机 参数 可观测指标”。状态机决定有哪些状态和迁移条件参数决定它在什么条件下会退化指标决定你怎么证明它按预期退化。三者缺一个报告就还是描述性文字。4.1 状态机该怎么画进报告而不是贴一张流程图很多报告里的状态机图只有发送方而且只有“发送—等待—收到确认—继续”这条主干。真正能看出理解的是异常路径超时重传、收到重复确认、收到失序帧、收到否定确认。这四条边如果不画读者没法判断你的实现到底有没有处理这些情况。我一般用表格代替流程图因为表格能把“事件 / 当前状态 / 动作 / 下一状态”四列一次摆清楚比图形更容易核对。发送方大致是这几个状态空闲、等待确认、重传中、窗口满阻塞。接收方简化成两个期望序号匹配、期望序号不匹配。把“收到序号等于期望值的帧”和“收到序号不等于期望值的帧”分别写一行那么 GBN 和 SR 的差别就自然地落在了“不匹配时丢弃还是缓存”这一格上。表格还有一个好处是能和代码对得上。如果你在报告里写了“收到失序帧时回送最后一个正确接收帧的确认号”那么代码里就必须能找到这句逻辑对应的那一行否则评审一句“你这里实际是怎么处理的”就问住了。4.2 停等、GBN、SR 的窗口与序列号空间约束这三者的对比如果只写“窗口大小不同”基本等于没写。真正要写的是约束条件和它背后的原因。下面这张表可以直接用协议发送窗口序列号空间 k 位下的约束接收端缓存重传粒度停等1序列号 1 位即可无单帧GBNWW ≤ 2^k − 1无失序即丢窗口内全部未确认帧SRWW ≤ 2^(k−1)有逐帧缓存仅丢失那一帧GBN 的W ≤ 2^k − 1和 SR 的W ≤ 2^(k−1)这两个约束是报告里最值钱的两行因为它们是“为什么序列号位数不能随便定”的直接答案。拿 3 位序列号0~7举例GBN 的窗口最多取 7取到 8 就会出现“新帧和老帧的序号完全重合、接收方分不清是重传还是新数据”的死局。这个推导过程可以写一小段配上一次故意把窗口设成 8 的失败实验说服力会非常强。超时时间 RTO 也别用固定值糊过去。教科书上的 Jacobson/Karels 估算可以直接实现报告里附上公式和一段样本数据就能把“RTO 为什么要自适应”讲明白# RFC 6298 风格的 RTO 估算alpha1/8, beta1/4 alpha, beta 0.125, 0.25 srtt, rttvar, rto None, None, 1.0 for sample in rtt_samples: # 每次测得的往返时延 if srtt is None: srtt, rttvar sample, sample / 2.0 else: rttvar (1 - beta) * rttvar beta * abs(srtt - sample) srtt (1 - alpha) * srtt alpha * sample rto srtt max(0.2, 4 * rttvar) # 下限 0.2s防止过度重传 print(fsrtt{srtt:.4f}s rttvar{rttvar:.4f}s rto{rto:.4f}s)alpha小意味着平滑后的 RTT 更稳、对突发抖动不敏感beta大一点意味着方差估计跟得更快。4 * rttvar这个系数是让 RTO 覆盖大部分正常波动max(0.2, ...)的下限是为了防止 RTT 极小的回环环境里 RTO 被算到毫秒级、导致大量无谓重传。在回环上跑这段代码你会发现 RTO 很容易被压到 0.2 秒的下限这正好可以写进报告当一个观察结论低时延环境下固定下限起主导作用自适应部分体现不出来。4.3 丢包注入与可观测指标实验要在“没有真实丢包”的环境里验证重传逻辑只能自己造丢包。最可控的方式是在发送路径上加一个随机丢包过滤器按固定种子丢弃这样每次跑出来的序列完全一样报告里的数字才能被复现import random def gbn_simulate(total20, window4, drop_rate0.15, seed1): rng random.Random(seed) # 固定种子 结果可复现 base, expected 0, 0 # 窗口左沿 / 接收方期望序号 acked set() # 接收方已按序收到的序号 frames_sent 0 # 含重传的总发送次数 while base total: for seq in range(base, min(base window, total)): frames_sent 1 if rng.random() drop_rate: continue # 这一帧在信道上丢了 if seq expected: # 按序到达接收方累计确认前移 acked.add(seq) expected 1 while expected in acked: expected 1 # 失序到达GBN 接收方直接丢弃不回缓存 while base in acked: base 1 # 窗口左沿前移到第一个未确认帧 return frames_sent for p in (0.0, 0.05, 0.15, 0.30): print(fdrop{p:.2f} frames_sent{gbn_simulate(drop_ratep)})seed固定是这份代码能进报告的前提否则每跑一次数字都不一样读者没法核对。window和drop_rate是两个自变量frames_sent是应变量跑出四组数据就是一张“丢包率—发送次数”曲线。把它和停等协议window1的同口径数据放在一起能直接看出“窗口越大单次丢包的代价越高”这个结论而不是靠嘴说。参数怎么选也有讲究。drop_rate从 0 开始是为了拿到一个无丢包的基准值任何实验都要有基准0.30 这种高丢包率是为了把重传逻辑逼到极限看会不会出现死循环或者窗口卡死。window取 4 是在 3 位序列号的约束上限 7之内留了余量方便你另外再跑一组window8的越界实验做对照那一组的表现会明显异常正好用来支撑前面那张约束表。可观测指标除了发送次数还有两个值得写一是“从开始到全部确认的轮次数”反映吞吐二是“重复收到的帧数”反映带宽浪费。这两个数都能从同一份模拟里顺手统计出来报告里三个指标并排比单个数字更像一次正经实验。5. 避坑与排查计网实验报告被打回的 5 个高频原因下面这五条基本覆盖了我见过和踩过的绝大多数退回理由每一条都按“现象—原因—解决”写方便对着自查。第一条截图很漂亮但没人能复现。现象是报告里每张图都能看清三次握手但看不到抓的是哪个接口、用的什么过滤条件。原因是把“看到”当成了“证据”忽略了抓包是受环境和过滤条件影响的。解决办法是每张图配一行完整命令包含接口、捕获过滤器、显示过滤器和时间基准字段命令放在图的上方而不是附录里。第二条回环上抓不到任何包。现象是服务明明起来了、curl也有返回抓包文件却只有几十字节。原因是接口选错了抓在了物理网卡上而流量走的是回环。解决是先确认流量路径回环流量必须在回环接口上抓Linux 上是lo部分系统需要显式选择回环适配器实在不确定就用不带接口限制的方式先抓几秒看看有没有包再收窄。第三条服务端重启时报 Address already in use。现象是第一次跑正常Ctrl-C 之后立刻重跑就绑不上端口。原因是上一个进程的套接字还在 TIME_WAIT 状态占着四元组。解决办法是给监听套接字加上SO_REUSEADDR同时在调试阶段用ss -lntp或lsof -i :9000确认端口上到底还挂着哪个进程别急着重启机器。第四条GBN 窗口设成序列号空间大小出现“幽灵帧”。现象是全部确认都收到了接收端却把新数据当成重复帧丢掉或者把重传当成新数据收下。原因是窗口取到了2^k新老帧的序号完全重合接收方无从区分。解决是严格按W ≤ 2^k − 1取值并且在报告里把这个约束的推导写出来而不是只写一个数字。第五条吞吐量算错数字大得离谱。现象是报告里算出几百 Mbps和实验环境明显不符。原因通常有两个把 RTT 当成单程时延代进公式或者把“窗口大小”直接当成“带宽”。解决是统一用“传输字节数 / 起止时间差”来算起止时间取同一份抓包里的frame.time_relative并在报告里注明时间戳来自哪一端、是否包含握手阶段。6. 把实验变成可复现的验收脚本一份 40 行的 run_all.sh 与判定阈值表做到前面几章每项实验单独跑都没问题但交报告前还要把这些步骤重新走一遍截图这个过程很容易漏步骤、也容易在不同时间点跑出不一致的结果。我后来的习惯是把所有能自动化的部分收进一个脚本跑一次生成全部证据文件报告里只引用文件名。这样即使隔两周再改报告数据也是同一批。下面这份脚本的顺序是有讲究的先起后台抓包再起被测服务然后造流量最后等抓包自然结束再导出。顺序错了会抓到空文件。#!/usr/bin/env bash set -euo pipefail OUTevidence mkdir -p $OUT # 1) 后台抓包20 秒后自动停止落盘 tshark -i lo -f tcp port 8080 -w $OUT/http.pcap -a duration:20 CAP$! # 2) 起被测服务 python3 http_echo.py SRV$! trap kill $SRV $CAP 2/dev/null || true EXIT sleep 1 # 3) 造三条可数的请求 for i in 1 2 3; do curl -s -o /dev/null http://127.0.0.1:8080/item$i done # 4) 等抓包进程结束再导出字段表 wait $CAP tshark -r $OUT/http.pcap -Y http.request -T fields \ -e frame.number -e frame.time_relative -e ip.src -e tcp.srcport -e http.request.uri \ -E headery -E separator, $OUT/http_requests.csv echo done: $OUT/http_requests.csvset -euo pipefail让脚本在任一命令失败时立刻退出避免“前半段失败后半段还在跑”产生看起来正常实则缺数据的文件。trap保证异常退出时后台进程被清理否则下一次跑会撞上端口占用。wait $CAP是这份脚本里最关键的一行-a duration:20到点后抓包进程才把缓冲区刷进文件如果不 wait 就直接读很可能读到一个还没写完的 pcap。配合脚本我在报告里会放一张判定阈值表把“什么样算通过”写死避免自评时含糊实验项验收命令期望输出判定阈值HTTP 请求数wc -l evidence/http_requests.csv表头 3 行恰好 4 行三次握手完整tshark -r evidence/http.pcap -Y tcp.flags.syn1 -T fields -e frame.number | wc -l含 SYN 与 SYN-ACK≥ 2并发回显python3 stress.pysuccess200/200成功率 100%GBN 重传次数python3 gbn_simulate.py随丢包率单调上升0.15 时 0.0 时的 2 倍阈值这一列是给自己看的也是给别人复现时对答案的。比如“0.15 丢包率下的发送次数要超过无丢包时的两倍”这个数不是拍脑袋是前面模拟跑出来的一旦某次重跑没达到说明代码或参数被改过能第一时间发现。最后说一个我自己的习惯。每次交报告前我会把整个目录复制到另一个路径删掉所有中间文件只保留脚本和源文件然后从头跑一遍bash run_all.sh。如果它能在一台“干净”的目录里重新生成全部证据文件这份报告才算真的可复现跑不出来问题一定在我自己写的某一行命令里而不是在评审那边。这个动作花不了五分钟但省下来的返工时间远不止五分钟。希望帮到你。本文还有配套的精品资源点击获取