ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

哈工大计网实验1-6完整工程包:GBN/HTTP代理/Wireshark/Packet Tracer实战

哈工大计网实验1-6完整工程包:GBN/HTTP代理/Wireshark/Packet Tracer实战

简介:本资源是哈尔滨工业大学计算机网络课程配套的全套实验材料,面向高校计算机及相关专业本科生,覆盖网络协议原理理解、服务端开发与抓包分析等核心实践能力训练。内容完整包含6个经典实验:HTTP代理服务器设计、HTTP分组收发与转发、GBN可靠传输协议实现、Wireshark协议分析、简单网络组建与配置,每项均含详实实验报告(docx)与可运行源码(js/cpp),辅以README说明、配置文件(json/.babelrc)及网络拓扑pkt文件,便于复现实验环境与调试验证。压缩包共22个文件,以6份docx报告、7个js前端逻辑脚本、3个cpp协议实现代码为主,另有md文档、json配置及pkt抓包文件,总大小4.67MB,结构清晰、开箱即用。已有563人学习下载,适合课程预习复习、实验课参考、协议原理巩固及网络编程入门实践。

1. 哈工大计算机网络实验1-6:一套能跑通、能调试、能交作业的完整工程包

你是不是也经历过:老师刚布置完“用C++实现GBN协议”,手头只有一页伪代码和模糊的实验指导书?Wireshark抓了一堆HTTP包,却分不清哪个是代理转发、哪个是原始请求?改了三遍ipv4.cpp,school.pkt在Packet Tracer里死活不转发——最后发现只是子网掩码少写了个0?这套哈工大计算机网络实验1-6的压缩包,不是零散笔记,也不是截图拼凑的PDF,而是一套带可执行源码、配套抓包验证数据、含逐层注释报告、且全部通过本地环境实测运行的闭环资源。它覆盖从HTTP代理服务器(实验1)、分组收发与转发(实验2–3)、GBN可靠传输(实验4)、Wireshark协议深度分析(实验5)到Packet Tracer网络拓扑搭建(实验6)的全链路。适合正在啃计网实验、需要快速验证逻辑、或想对照标准实现查漏补缺的本科生与自学开发者——尤其当你卡在sendto()返回-1、recvfrom()阻塞超时、或.pkt文件加载后设备灯不亮时,这份资源里的app.cpp编译日志、export.docx中的Wireshark过滤表达式、以及README.md里明确标注的GNS3/Packet Tracer版本兼容说明,就是你翻车现场的后悔药。


2. 实验1–3:HTTP代理与分组收发——从app.js到ipv4.cpp的三层通信验证

2.1 HTTP代理服务器:Node.js实现与端口绑定陷阱

实验1的核心是app.js(非浏览器端JS,而是Node.js服务端脚本),它实现了基础HTTP代理功能:接收客户端请求 → 解析Host头 → 转发至目标服务器 → 回传响应。关键不在“能转发”,而在“能被Wireshark精准捕获”。常见错误是监听0.0.0.0:8080却未关闭系统自带的Web服务冲突,导致EADDRINUSE。正确做法是显式指定本机IP并检查端口占用:

# 先查端口占用(Linux/macOS) lsof -i :8080 # 或 Windows netstat -ano | findstr :8080 # 启动时绑定具体IP(如本机IPv4为192.168.1.100) node app.js --host 192.168.1.100 --port 8080

提示:app.js中http.createServer()的req.socket.remoteAddress必须打印到控制台,这是后续Wireshark比对客户端真实IP的唯一依据;若此处显示::1(IPv6回环),说明请求根本没走代理,而是直连了本地服务。

2.2 分组收发实验:ipv4.cpp的原始套接字权限与ICMP校验

实验2与实验3共用ipv4.cpp,它用原始套接字(Raw Socket)构造并发送自定义IP数据包。这里踩坑率高达70%:

  • Linux下需sudo权限(sudo ./a.out),否则socket(AF_INET, SOCK_RAW, IPPROTO_RAW)直接返回-1;
  • Windows需启用SeCreateGlobalPrivilege(通过ntrights.exe或组策略),普通管理员权限不够;
  • 更隐蔽的是ICMP校验和计算:ipv4.cpp中checksum()函数对IP首部长度字段(IHL)的处理必须是header_length * 4,而非直接取ip_header->ihl——因为该字段单位是4字节,值为5表示20字节,但代码里若误写ip_header->ihl * 5就会校验失败,目标主机静默丢包。
// ipv4.cpp 关键修正段(原版易错点) uint16_t checksum(uint16_t *addr, int len) { uint32_t sum = 0; while (len > 1) { sum += *addr++; len -= 2; } if (len == 1) sum += *(uint8_t*)addr; while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); return (uint16_t)(~sum); } // 构造IP首部时,IHL字段必须按规范设置 ip_header->ihl = 5; // 表示首部20字节,不可写成 ip_header->ihl = 20; ip_header->tot_len = htons(20 + data_len); // 总长=首部20+数据长度

2.3 分组转发实验:双网卡配置与路由表干预

实验3要求实现“转发”而非“发送”,即本机作为中间节点透传数据包。这要求系统开启IP转发,并手动添加路由规则。export.docx中给出的命令仅适用于Ubuntu 20.04,新版内核需额外处理:

# 1. 开启IPv4转发(临时) echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward # 2. 永久生效(写入/etc/sysctl.conf) echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf sudo sysctl -p # 3. 关键:添加策略路由(避免回环) # 假设eth0接客户端(192.168.1.0/24),eth1接服务器(10.0.0.0/24) sudo ip rule add from 192.168.1.0/24 table 100 sudo ip route add 10.0.0.0/24 via 10.0.0.1 dev eth1 table 100

注意:若跳过第3步,ipv4.cpp发出的数据包会被本机路由表直接投递到lo接口,Wireshark在eth0上抓不到转发流——这是实验3失败最隐蔽的原因。


3. 实验4:GBN协议实现——滑动窗口状态机与超时重传的硬核调试

3.1 GBN核心逻辑:发送方窗口与ACK确认的严格同步

code/目录下的GBN实现(gbn_sender.cpp与gbn_receiver.cpp)采用经典状态机设计。其难点不在算法本身,而在时间戳驱动的超时重传与ACK乱序到达的冲突处理。原版代码中sender_timeout_handler()存在一个致命缺陷:当连续收到多个重复ACK(如ACK=2)时,若未清空已确认序列号队列,会导致窗口左边界卡死,新数据无法发送。修复方案是在每次处理ACK后强制推进窗口:

// gbn_sender.cpp 中 ACK处理逻辑修正 void handle_ack(int ack_seq) { if (ack_seq >= window_base && ack_seq < window_base + WINDOW_SIZE) { // 清除已确认包的定时器 cancel_timer(ack_seq); // 关键:推进窗口基线(原版缺失此行) while (window_base <= ack_seq) { window_base++; if (window_base > ack_seq) break; } // 触发新数据发送 send_new_packets(); } }

3.2 抓包验证:Wireshark过滤表达式与序列号映射

实验4要求用Wireshark验证GBN行为,但原始export.docx中只写了tcp.port == 8080,这完全无效——GBN使用UDP自定义协议,无TCP端口概念。必须基于UDP载荷内容过滤:

过滤目标Wireshark Display Filter说明
所有GBN数据包udp.dstport == 9999 && udp.length > 28实验约定目的端口9999,且最小包含8字节头部+20字节数据
发送方重传包udp.dstport == 9999 && udp.payload[12:4] == 01:00:00:00自定义头部第12–15字节为标志位,0x01000000表示重传(原版协议定义)
接收方ACK包udp.srcport == 9999 && udp.payload[8:4] == 00:00:00:00第8–11字节为ACK类型标识,0x00000000表示ACK

血泪经验:Wireshark默认不解析UDP载荷,需右键UDP包 → “Decode As…” → 将端口9999强制设为“Raw”才能看到十六进制载荷,否则payload[12:4]过滤永远为空。

3.3 超时参数调优:RTT估算与初始RTO设置

GBN性能高度依赖超时重传时间(RTO)。原版gbn_sender.cpp将RTO硬编码为1000ms,但在千兆局域网中这会导致过度重传。应改为基于RTT采样的动态RTO:

// 在 sender.cpp 中添加 RTT 采样逻辑 struct timespec start_time; clock_gettime(CLOCK_MONOTONIC, &start_time); // 发送时记录时间戳(存入包头部) packet.header.timestamp = start_time.tv_sec * 1000000 + start_time.tv_nsec / 1000; // 收到ACK时计算RTT struct timespec end_time; clock_gettime(CLOCK_MONOTONIC, &end_time); long rtt_us = (end_time.tv_sec - start_time.tv_sec) * 1000000 + (end_time.tv_nsec - start_time.tv_nsec) / 1000; update_rto(rtt_us); // 使用RFC6298算法更新RTO

4. 实验5:Wireshark协议分析——从抓包到报文字段级解读的落地路径

4.1 HTTP代理流量分离:三元组过滤与会话追踪

实验5要求分析代理场景下的HTTP交互,但Wireshark默认将代理请求与目标服务器响应混在同一会话。必须用四元组(源IP:端口 + 目标IP:端口)精确隔离:

# 过滤代理服务器自身流量(假设代理运行在192.168.1.100:8080) ip.addr == 192.168.1.100 && tcp.port == 8080 # 过滤客户端到代理的请求(客户端IP为192.168.1.50) ip.src == 192.168.1.50 && tcp.dstport == 8080 # 过滤代理到目标服务器的请求(目标为httpbin.org,IP假设为34.123.45.67) ip.src == 192.168.1.100 && ip.dst == 34.123.45.67 && tcp.dstport == 80

提示:在report.docx中,所有HTTP请求的Host头与GET路径必须与Wireshark中http.request.full_uri字段完全一致——这是验证代理是否正确解析并转发的关键证据。

4.2 TCP三次握手与四次挥手的时序断点

实验5强调“可靠连接建立与释放过程”,但学生常把SYN、SYN-ACK、ACK当成独立事件。实际应关注时间轴上的微秒级间隔:

  • 正常局域网中,SYN→SYN-ACK延迟应<1ms,若>10ms说明网络拥塞或代理中间件介入;
  • FIN-WAIT-1状态持续时间若>60s,表明对方未响应FIN,可能进程崩溃或防火墙拦截;
  • export.docx中提供的tcp.analysis.flags过滤器(如tcp.analysis.retransmission)必须配合Statistics → Flow Graph使用,才能可视化重传位置。

4.3 避坑:Wireshark常见问题与排查

现象 → 原因 → 解决:

  • 现象:Wireshark抓不到任何HTTP包,但浏览器能正常访问。
    原因:代理设置错误(如Chrome设置了系统代理但Firefox未设),或app.js未监听正确IP。
    解决:在浏览器地址栏输入http://127.0.0.1:8080/test.html直连代理,同时Wireshark过滤http && ip.addr == 127.0.0.1。

  • 现象:抓到大量TCP Retransmission但代理功能正常。
    原因:Wireshark在高负载下丢包,或网卡驱动未启用巨型帧(Jumbo Frame)。
    解决:降低抓包缓冲区大小(Edit → Preferences → Capture → Buffer size = 2MB),并禁用网卡节能模式。

  • 现象:http.request.uri显示/但实际访问的是/api/data。
    原因:HTTP/2协议下URI被HPACK压缩,Wireshark默认不解析。
    解决:强制浏览器使用HTTP/1.1(Chrome启动参数加--unsafely-treat-insecure-origin-as-secure="http://127.0.0.1:8080" --user-data-dir=/tmp/chrome-test --unsafely-allow-http-for-local-testing)。


5. 实验6:Packet Tracer网络组建——school.pkt拓扑验证与故障注入技巧

5.1 school.pkt拓扑结构与设备角色定义

experiment-6/school.pkt构建了一个典型校园网模型:

  • 核心层:Router0(运行OSPF,连接各区域);
  • 汇聚层:Switch0(VLAN 10:教师办公区;VLAN 20:学生机房);
  • 接入层:PC0(教师PC,IP 192.168.10.10)、PC1(学生PC,IP 192.168.20.10);
  • 外联:Router1(模拟Internet,配置静态路由指向Router0)。

关键验证点是跨VLAN通信:PC0 ping PC1必须成功,且Wireshark在Router0的G0/0接口应捕获到802.1Q标签帧(VLAN ID=10/20)。

5.2 故障注入:模拟真实网络问题并定位

README.md中隐藏了一个教学技巧:通过手动制造故障,训练排错能力。例如:

  • 步骤1:在Switch0上删除VLAN 20的SVI接口(no interface vlan 20);
  • 步骤2:观察PC1的ping 192.168.10.10持续超时;
  • 步骤3:在Router0上执行show ip route,发现无192.168.20.0/24路由;
  • 步骤4:在Switch0上恢复VLAN 20并配置interface vlan 20,再show vlan brief确认PC1端口已加入。

注意:Packet Tracer 7.3.1以上版本中,show running-config输出的VLAN配置必须包含switchport access vlan 20,若缺失则PC1实际处于VLAN 1,导致跨VLAN失败。

5.3 配置同步:避免Packet Tracer与GNS3的版本鸿沟

school.pkt在Packet Tracer 7.2.1中可完美运行,但若用GNS3 2.2.x导入,会出现路由器无法启动问题。根本原因是IOS镜像不兼容:

  • Packet Tracer使用精简版IOS(pt124-2.bin),仅支持基础命令;
  • GNS3需完整IOS(如c3640-a3jk9s-mz.124-25d.image),且内存需≥192MB。
    解决方案:在GNS3中新建路由器时,选择c3640平台,分配256MB RAM,挂载完整IOS,并在启动配置中禁用ip cef(Packet Tracer不支持CEF加速)。

6. 从源码到报告:一份实验资源的闭环验证方法论与我的强制习惯

真正让这套资源区别于网上泛滥的“答案集”的,是它内置的三层验证锚点:源码可编译、抓包可复现、报告可溯源。我曾帮某高校实验室重构计网实验体系,发现学生交的报告里90%的Wireshark截图来自百度图片,而export.docx中每张图都带时间戳水印与过滤器文本框——这不是为了美观,而是倒逼验证闭环。下面是我现在带学生必做的三步验证法,已固化为checklist.md模板:

6.1 编译层验证:确保源码在目标环境零报错

不满足于g++ -o a.out *.cpp能运行,必须检查:

  • 符号表完整性:nm a.out | grep "U "输出为空(U表示undefined symbol);
  • 动态库依赖:ldd a.out显示所有库路径有效(尤其libpcap.so在Ubuntu 22.04需sudo apt install libpcap-dev);
  • 内存安全:用valgrind --leak-check=full ./a.out运行10秒,确认definitely lost: 0 bytes。

6.2 抓包层验证:Wireshark与源码日志的双向对齐

这是最容易被忽略的环节。以实验1的app.js为例:

  • 在代码中插入console.log([PROXY] ${new Date().toISOString()} ${req.method} ${req.url} → ${targetHost});
  • Wireshark中导出HTTP流(右键包 → Follow → TCP Stream),保存为proxy_flow.txt;
  • 用Python脚本比对时间戳与URL:
    # validate_proxy.py import re with open("proxy_flow.txt") as f: flow = f.read() urls = re.findall(r"GET\s+(/.*?)\s+HTTP", flow) # 与console.log中的URL列表逐行比对

6.3 报告层验证:文档结论必须有数据支撑

report.docx中所有结论性语句,如“GBN协议在丢包率15%时吞吐量下降42%”,必须对应gbn_sender.cpp中stats.throughput变量的实时打印,且该打印需在main()循环末尾强制刷新:

printf("Throughput: %.2f KB/s\n", stats.throughput); fflush(stdout); // 关键!否则重定向到文件时内容延迟

没有fflush(),report.docx里的性能数据就是空中楼阁。

从那以后我每次带学生做计网实验,都强制走一遍这三步验证:先make clean && make看编译器脸色,再开Wireshark抓包比对日志,最后打开Word文档逐句核对结论来源。不是为了形式主义,而是让学生亲手触摸到“理论→代码→信号→结论”的完整物理链路——当ipv4.cpp发出的字节流真正在网线里变成光信号,当school.pkt里红灯变绿的瞬间,抽象的协议才有了温度。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进