ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

计算机网络实验源码实战:TCP回射、CRC校验与抓包避坑指南

计算机网络实验源码实战:TCP回射、CRC校验与抓包避坑指南 简介中南大学计算机网络实验源代码2022年版覆盖A1、A3两个实验直接对应中南大学计算机网络课程要求适合课程学习者对照练习用于理解Socket编程、TCP/UDP通信与HTTP等协议实现。A1侧重客户端/服务器连接建立、数据收发与异常处理A3深入HTTP等协议报文解析和服务器交互逻辑均以C语言工程呈现并配有Makefile、Python脚本及docx指导书方便本地编译运行与对照调试降低起步门槛。资源共53个文件以.c源文件、.h头文件和.o编译产物为主另含实验结果截图、sh部署脚本以及Makefile可一键编译复现实验环境压缩包仅1.06MB。目前已有743人学习对想快速上手网络编程实验的学生是轻量参考。通过阅读服务器/客户端源码和收发逻辑可直观掌握建立连接、异常处理、抓包验证等关键技能深化对网络分层模型的理解并帮助课后复习。1. 拿到中南大学计算机网络实验源代码你大概会卡在这儿搜这条标题的人多半是实验课老师刚丢出一个压缩包或者计算机网络期末复习时发现自己还没跑通一次 TCP 回射。这个标题背后不是一份标准答案而是一整条“编译—抓包—分析—报告”的链路。解决了它你既能把实验报告写完整也能在计算机网络自顶向下这类教材和考试题之间找到一份真实可运行、可改可验证的样例。适合正在补实验的学生、帮学弟调代码的助教以及想把课程源码改成课程设计起点的初学者。先记住一句话源代码不是抄完就结束它的价值只存在于你能解释每一行参数的时候。2. 实验源码的体系与选择先看懂目录再写第一行代码2.1 常见文件清单不只有 .c还有 Makefile、抓包结果和报告底板搜“中南大学计算机网络实验源代码”时拿到手的往往是一个压缩包或仓库。以大多数网络实验课程的标准做法来看一份能让你少熬夜的源码目录长这样路径作用说明Makefile一键编译避免手动 gcc 时漏参数tcp_server.c / tcp_client.cSocket 实验骨架回射对话的主要代码crc.c / crc.h数据链路层校验验证多项式参数和位序ip_checksum.cIP 报头校验和期末概念题的落地验证scripts/抓包与验证脚本截图和报告的数据来源README.md实验步骤备忘记录你改过哪些参数这份清单是通用结构的整理不是某一次实验唯一答案。真正重要的问题是为什么这些文件要拆开因为源代码管理不能把课堂代码和报告混在一起。我第二次跑实验时改用了这种做法给每轮实验打一个 tag比如lab1-socket、lab2-crc机器上永远保留一份能编译通过的版本再开一个分支去尝试改法。这样到期末不需要翻聊天记录只需要git checkout lab1-socket代码就能回到当时交报告的状态。拿到源码包之后先不要双击打开。我一般会在终端里跑一个tree把文件层级看清楚tree -L 2 已下载的实验源码/-L 2表示最多显示两层目录。如果输出里只有单个.c文件那说明这个压缩包只是片段你还需要自己补 Makefile 和测试脚本。如果看到Makefile和scripts/目录说明这份源码有一定的工程组织值得保留。这个“先看路径”的动作能避免你把文件解压到桌面最后找不到编译产物而重做实验。另一个头脑清晰的办法是把每一次实验的关键结果写进 README。不需要长篇大论只写三行——实验名、验证命令、预期输出。README 的字数不要求多但它是你计算机网络期末复习时最快捷的索引。特别是做 CRC 和 Socket 这类互相依赖的实验时README 里记录的参数组合能让你在三天后重新编译时不再怀疑人生。2.2 为什么选 C Makefile而不是纯 Python第一次做网络实验有人上来就用 Python 的socket模块因为代码量小。但如果你老师要求的是“看懂 TCP 状态或者 IP 报头结构”C 仍然是最合适的语言。套接字返回的是 int 还是指针、read返回的字节数怎么处理这些边界在 Python 里被封装得太干净你很难解释内核为什么要这样设计。反过来用 C 写的 socket 代码即使编译不过生成的警告也比 Python 的报错更能暴露问题。所以我给你的第一个环境建议是在 Ubuntu 或 WSL2 里装一套最小工具集。sudo apt update sudo apt install -y gcc make tcpdump wireshark-common netcat-openbsd这行命令里gcc和make解决编译tcpdump负责抓包wireshark-common提供tshark和抓包辅助工具netcat-openbsd是验证 TCP 服务器的最小客户端。注意netcat在 Ubuntu 上默认是 OpenBSD 版本-q参数可用在部分精简系统里nc可能来自 netcat-traditional参数差异会直接导致测试脚本失败。所以安装完一定要确认nc -h 21 | grep q如果 grep 没有输出就改用ncat或socat。这是一个很细微但会让你实验报告里命令截图出错的坑。选 WSL2 而不是虚拟机的理由很简单WSL2 的启动成本低文件系统访问方便代码放在/home下改了立刻能编译。但 WSL2 的网络模型是 NAT抓不了宿舍路由器的广播包。如果要抓 ARP 或 DHCP我会改用 VMware 桥接模式把虚拟机网卡接到校园网同一网段再在虚拟机里跑同一套命令。工具链统一之后你是不是会想C 太慢能不能用 Python 验证某个想法当然可以。我自己的习惯是提交用的源码用 C验证用的脚本用 Python。比如第四章你会看到一个 CRC 的 Python 实现它不是为了交作业而是为了快速核算参数结果。这样做的好处是你既满足课程的编译要求又能用 Python 把边界条件试个遍。计算机网络自顶向下那本书里有很多伪代码按照同样的思路把伪代码改写成 Python 验证逻辑再把 C 当成性能修正版是最稳妥的组合。3. 最小可复现实验TCP回射服务器的源码、编译和自测3.1 一个能直接编译通过的TCP回射服务端C代码Socket 实验是所有网络实验的第一道坎。这里给出一个最小可复现的 TCP 回射服务端代码风格沿用实验源码常见做法单进程、单连接、阻塞 IO能够解释清楚也算达标。#include stdio.h #include stdlib.h #include string.h #include unistd.h #include arpa/inet.h #include sys/socket.h #define PORT 8888 #define BACKLOG 10 #define BUFFER_SIZE 1024 int main() { int listen_fd, conn_fd; struct sockaddr_in server_addr; struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); char buffer[BUFFER_SIZE]; listen_fd socket(AF_INET, SOCK_STREAM, 0); if (listen_fd 0) { perror(socket); exit(1); } int reuse 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, reuse, sizeof(reuse)); memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_addr.s_addr htonl(INADDR_ANY); server_addr.sin_port htons(PORT); if (bind(listen_fd, (struct sockaddr *)server_addr, sizeof(server_addr)) 0) { perror(bind); close(listen_fd); exit(1); } if (listen(listen_fd, BACKLOG) 0) { perror(listen); close(listen_fd); exit(1); } printf(listening on 0.0.0.0:%d\n, PORT); while (1) { conn_fd accept(listen_fd, (struct sockaddr *)client_addr, client_len); if (conn_fd 0) { perror(accept); continue; } printf(client %s:%d connected\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); ssize_t n; while ((n read(conn_fd, buffer, BUFFER_SIZE - 1)) 0) { buffer[n] \0; printf(recv(%zd): %s, n, buffer); write(conn_fd, buffer, n); } close(conn_fd); printf(client %s:%d disconnected\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); } close(listen_fd); return 0; }这段代码的逻辑不难socket()创建流式套接字setsockopt开启地址复用bind绑定所有接口和固定端口listen设置握手队列长度accept阻塞等待连接。内层read/write循环把客户端发来的每个字节原样写回所以叫回射。这里的三个参数值得你在报告里多写两行PORT可以改成任何大于 1024 且未被占用的端口BACKLOG是未完成握手的队列长度不是连接总数BUFFER_SIZE越大大报文越不容易被分段但别超过 64K。有一个很多人不留意的问题代码里没有处理客户端一次发送超过 1023 字节的情况。实验源码允许这种简化但报告里要写明这是受限于固定缓冲区的简化实现。如果你在跑测试的时候发送了一个超过 1024 字节的字符串服务端会分多次读取并分别回射客户端需要循环接收才能拿全。这一点不写清楚老师细看时会质疑你的协议设计。3.2 编译、启动和自测的三个命令解释为什么这样测代码写完之后最重要的动作是编译时把所有警告当成错误看待。gcc -Wall -Wextra -O0 -g -o tcp_server tcp_server.c-Wall -Wextra打开常见警告-O0禁止优化让调试时行号和变量对应得更准确-g生成调试信息。如果这一行报错不要急着关终端先看undefined reference前面的文件行数那才是问题源头。如果编译通过再启动并自测./tcp_server echo hello from hnu | nc -q2 127.0.0.1 8888用放后台。nc连接后发送字符串-q2表示收到 EOF 后 2 秒关闭连接。如果 nc 不支持-q就用sleep 2 | nc 127.0.0.1 8888代替。观察服务端输出listening on 0.0.0.0:8888 client 127.0.0.1:49152 connected recv(14): hello from hnu client 127.0.0.1:49152 disconnected自测要验证四件事端口是否监听客户端端口是否变化收到的字节数是不是原串长度连接在 EOF 之后是否自动关闭。这四条写进实验报告比单纯的截图更可信。如果你想让报告更完整还可以写一个 Python 客户端来验证回射内容import socket s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((127.0.0.1, 8888)) s.sendall(bcsu network lab\n) data s.recv(1024) print(repr(data)) s.close()这段脚本验证回射数据是否一致。参数方面connect的端口必须和服务器一致而且要先把服务器启动起来否则会报Connection refused。recv(1024)只接收一次如果服务器分两次返回这里可能漏数据但回射实验场景足够。注意这个 Python 脚本不要作为实验源码提交它只是一个快速验证工具。提交的仍然应该是 C 代码原因在第二章说过课程要求现场编译的能力比运行结果更重要。跑通这一套之后建议立刻git init并提交记录“可以回射”的基线。后面的 CRC、IP 校验和实验都在这个基线上改不要每次从头写。如果老师要求支持多个客户端同时连接那就不能用简单的单进程回射了。常见的做法是fork()或者select()但那是进阶话题等你把基础实验交掉再考虑。4. 协议模拟与抓包源码CRC、IP校验和与 tcpdump 的配合4.1 CRC校验和实现的三个必调参数poly、init、xorout数据链路层实验里不少课程会要求自己实现 CRC 或校验和。很多参考源码能跑但报告里三个参数解释不完整老师一问就露馅。这里给出一个精简的 CRC-16 实现并说明为什么调这三个参数。def crc16(data: bytes, poly: int 0x1021, init: int 0xFFFF, xorout: int 0x0000) - int: crc init for byte in data: crc ^ byte 8 for _ in range(8): if crc 0x8000: crc ((crc 1) ^ poly) 0xFFFF else: crc (crc 1) 0xFFFF return crc ^ xorout代码逻辑每个字节先异或到 CRC 高 8 位再按位移。0x8000判断最高位是否为 10xFFFF掩码让结果保持 16 位。三个参数的含义分别是poly是生成多项式去掉最高位后的值init是初始值用0xFFFF可以避免前导零被忽略xorout是输出异或值协议不同取值不同。你从某个源码包里看到poly0x8408不要慌那是反向多项式用于 LSB 位序。发送端和接收端位序必须一致否则校验永远不过。验证方法一定要用标准测试串。b123456789是很多 CRC 算法手册里的标准输入CRC-16/CCITT-FALSE 的结果是0x29B1。如果你的函数输出这个值说明参数组合正确。如果不对先检查字节序再检查初值。常见的错误是把初值写成0x0000那样前导零的数据会得到同一个校验码实验报告如果讨论“帧同步”这个问题就会暴露。CRC 之外还有一个必考的校验算法是 IP 报头检验和。它比 CRC 简单很多def ip_checksum(header: bytes) - int: if len(header) % 2: header b\x00 s sum(int.from_bytes(header[i:i2], big) for i in range(0, len(header), 2)) while s 16: s (s 0xffff) (s 16) return ~s 0xffff这个实现先按 16 位一组做二进制反码求和再把高 16 位回卷最后取反。循环回卷这一步很重要否则大报文求和时进位会丢失。两者的差别在报告里可以作为对比段落CRC 用多项式除法抗突发错误能力强IP 校验和只是补码求和实现简单但检测能力有限。这种概念对比比单纯贴代码要加分不少。4.2 用 tcpdump 命令抓回射流量并导出 Wireshark pcap做完了 Socket 回射下一步不是直接看屏幕而是把流量留成.pcap文件因为实验报告需要验证报文结构。常见做法是脚本一键完成抓包、发包、停止#!/bin/bash OUT${1:-echo.pcap} PORT${2:-8888} sudo tcpdump -i lo -nn -s 0 -c 20 -w $OUT port $PORT TCPDUMP_PID$! sleep 1 echo csu-network-lab | nc -q2 127.0.0.1 $PORT sleep 2 sudo pkill -x tcpdump ls -lh $OUT脚本里-i lo指定回环接口-nn不做域名和端口反向解析-s 0抓完整帧-c 20抓满 20 个包就自动退出-w写 pcap 文件。TCPDUMP_PID虽然记录了下来但后面的pkill -x tcpdump更可靠因为它按进程名杀不会误杀同一个终端里其他 tcpdump。这个脚本的价值在于你每次改端口、换启动顺序不用重复录命令。要抓 TCP 握手细节就把-c 20改为-c 50要抓这个回射连接的全部流量就保留port 8888过滤。如果只想看某个流可以在 Wireshark 里用显示过滤tcp.stream eq 4归拢比命令行里写复杂 BPF 容易得多。如果你没有 sudo 权限替代方案是抓包工具运行在用户态但大多数校园网实验环境都会给 root。另一个关键点是虚拟机里抓包别只依赖命令行-w更好的做法是直接把 pcap 文件拖到 Windows 宿主机用 Wireshark 的时序图观察 TCP 窗口和重传。计算机网络自顶向下这本书里的 TCP 状态实验用这种方式检查特别直观。抓包时注意时间戳如果 pcap 文件里的时间全为 0说明虚拟机时钟漂移需要在每个包之间加sleep 0.1让时间戳可读。5. 避坑编译、端口、虚拟机和验收时的五个坑5.1 现象改了 .c 文件make 后还是旧结果最典型场景你明明把BACKLOG从 10 改成 100运行后队列长度没变化。原因通常是 Makefile 里缺少依赖规则或者源码时间戳没有更新还有一个可能是编译产物被复制到别的目录你改错了文件。解决先make clean再make然后确认当前目录用ls -l --time-stylefull-iso对比源码和产物的时间。如果用了 IDE 的缓存项目删掉 build 目录再来。血泪经验永远不要把编译产物和源码放在同一个目录除非你确认 Makefile 的$和$都配对。5.2 现象bind() 返回 Address already in use上一秒./tcp_server还好好的CtrlC 之后再起来就报端口被占用。原因主要不是程序没退出而是 TCP 的 TIME_WAIT 状态会保留连接约 2MSL操作系统没有立刻释放监听端口。解决在bind前增加setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, reuse, sizeof(reuse));第三章代码已经加了。还有一个容易被忽略的场景WSL1 中 IPv4 和 IPv6 同时监听同一个端口用ss -lntp看监听项别只查netstat -ano。5.3 现象WSL2 里抓包只抓得到 lo网卡看不到校园网广播包这是我第一次做 ARP 实验时翻车的地方。原因WSL2 网络走 NAT 虚拟交换机和宿主机不在同一广播域抓不到物理网卡的广播和 DHCP 流量。解决做 ARP、DHCP 这类实验时改用 VMware 或 VirtualBox 桥接模式把虚拟机网卡接到校园网网段再在虚拟机里运行 Wireshark 或 tcpdump。如果不想换虚拟机可以在 Windows 宿主机上启动 Wireshark选择物理网卡然后把虚拟机里的 TCP 流量指向宿主机 IP也能抓到部分包但结构不完整。折腾 WSL2 的嵌套虚拟化不是正路。5.4 现象Windows 编译通过提交到 Linux 服务器后段错误原因集中在三个地方send/recv的返回值没有用ssize_t类型Windows 的WSAStartup没调用Winsock 初始化失败还有strlen和recv的缓冲区大小不匹配越界写入。解决代码统一用ssize_t n read(...)接收返回值比较时强转成size_t会掩盖负数错误。然后在 Makefile 加上-D_GNU_SOURCE -stdgnu11避免编译器按 C89 解析某些声明。这类问题在跨平台源码里最常见的就是缓冲区大小和返回类型不一致编译不报错运行才崩。5.5 现象实验报告里抓包截图太乱老师说你数据不完整原因抓包时过滤太宽把 loopback 上的时间戳、DNS 等无关流量都截进去了或者抓包进程没有正常停止pcap 文件没有写完。解决截图前用 Wireshark 显示过滤tcp.stream eq 4只保留一条 TCP 连接在首选项里把列改为时间、源、目的、长度、协议信息。抓包时要明确记录开始和结束时间确认tcpdump进程退出后再停止否则文件尾部损坏。如果老师说数据不完整多半是你只截了 TCP 载荷没有包含 TCP 三次握手阶段重抓一次并保留完整连接即可。6. 把一次性实验源码变成可复用工具包一个自动验证脚本的进阶技巧如果你只交一次实验前面四章已经够用。但如果你想把这个方向做成课程设计甚至毕业设计的基础还有一步值得做把编译、运行、回射、校验、抓包串成一条流水线。下面这个 Python 脚本调动了前面所有成品并把 CRC 校验也加进来形成一个自动验证入口#!/usr/bin/env python3 import os, subprocess, socket, sys, time def run(cmd, **kw): print(, cmd) return subprocess.run(cmd, shellTrue, capture_outputTrue, textTrue, **kw) def crc16(data: bytes, poly0x1021, init0xffff, xorout0x0000) - int: crc init for b in data: crc ^ b 8 for _ in range(8): crc ((crc 1) ^ poly) 0xffff if crc 0x8000 else (crc 1) 0xffff return crc ^ xorout # 1. 编译 C 源码 run(gcc -Wall -Wextra -O0 -g -o tcp_server tcp_server.c) # 2. 启动服务器 server subprocess.Popen([./tcp_server], stdoutsubprocess.PIPE, stderrsubprocess.PIPE) time.sleep(1) # 3. 发送一条测试数据并接收回射 s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((127.0.0.1, 8888)) test_msg bcsu-network-lab\n s.sendall(test_msg) echoed s.recv(1024) s.close() print(echoed:, echoed) assert echoed test_msg print(crc16:, hex(crc16(test_msg))) server.terminate()这个脚本把实验中的可见结果打印在同一处每次验证完成会形成一条日志。server subprocess.Popen(...)有一个容易被忽略的参数如果你在别的目录运行此脚本需要给Popen加上cwd实验源码目录否则找不到./tcp_server。核心断言是assert echoed test_msg只要回射对不上后续分析全部没有意义。这也是我自己的习惯最重要的断言放在最前面而不是先到处 print。运行时还可以加一个--save-pcap参数把第四章的 tcpdump 调用也包进去每次验证都会产生一份可复现的 pcap 文件。这样实验报告的截图永远不会过时。我这两年的经验是代码不是越花哨越好而是能复现、能解释、能记录才算完成。这个自动验证脚本就是那个最小可用的骨架希望帮到你。本文还有配套的精品资源点击获取
返回列表