尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
手写TCP协议栈实验:本地抓包验证三次握手与状态机
简介本资源是燕山大学计算机网络课程三级实践项目——TCP传输数据包的完整实现工程面向高校网络协议学习者与网络编程初学者聚焦TCP可靠传输核心机制的理解与编码落地。项目涵盖三次握手建连、数据分段与序号管理、滑动窗口流量控制、拥塞控制算法慢启动/快速重传及四次挥手断连等关键知识点通过C语言基于Winsock实现客户端/服务器双端通信具备可直接编译运行的工程结构。压缩包共60个文件含4个cpp源码、2个h头文件、2个sln解决方案、2个exe可执行文件以及pdb调试信息、obj中间文件和vcxproj工程配置等总大小27.88MB体现典型VS2019本地编译项目的完整构建链路。已有1355人学习下载提供从协议原理到代码级实现的闭环实践路径助读者深入掌握TCP状态机、Socket编程细节及网络异常处理逻辑。1. 一个能抓到自己发出去的 SYN、ACK、FIN 的 TCP 实验环境燕大计算机网络三级项目不是“画饼”它真能让你在本地 Wireshark 里看到三次握手全过程你有没有试过写完一段 socket 代码connect()返回成功但send()却卡住或者服务器accept()后收不到客户端数据Wireshark 里却看到一堆重传包这时候光看print(connected!)是没用的——你缺的不是日志是一个能和协议栈“面对面”的实验体。燕大计算机网络三级项目 TCP 传输数据包就是这样一个被反复验证过的、轻量但完整的 TCP 实验套件它不依赖任何在线服务或远程服务器所有通信都在本机 loopback127.0.0.1完成它不封装成黑匣子而是把 TCP 报文构造、校验和计算、序列号管理、超时重传逻辑全部摊开在源码里它甚至自带一套精简版的“伪协议栈”模拟层让你能手动触发 SYN、伪造 ACK、故意丢包、观察 RTO 指数退避。这不是教科书里的抽象流程图这是你能gcc -g编译、gdb单步、tcpdump -i lo抓包、对着 RFC 793 一行行比对的实体项目。适合刚学完《计算机网络自顶向下》第3章、正在啃 TCP 状态机、想亲手验证“为什么 FIN_WAIT_2 会等那么久”的本科生也适合需要给新人讲清楚“校验和到底怎么算”的带教工程师。它小得能塞进一个 GitHub Gist实得能跑通从连接建立到优雅关闭的全生命周期。2. 项目结构与技术选型C 语言 raw socket 自定义协议栈模拟层拒绝黑盒一切可控这个项目不是用 Python 的socket库简单封装一下就交差。它明确选择了 C 语言作为实现语言核心动机只有一个必须能直接操作 IP/TCP 头部字节。Python 的struct.pack()虽然也能拼包但一旦涉及校验和跨字节边界、IP 分片标志位、TCP 窗口缩放选项这类细节C 的内存布局控制力和指针运算就成为不可替代的优势。项目没有引入 libpcap 或 libnet 这类第三方库所有网络 I/O 均基于 Linux 原生AF_PACKET类型的 raw socket这意味着你可以用sendto()直接发送任意构造的以太网帧用recvfrom()接收原始二进制数据——这正是观察“协议栈底层行为”的前提。整个架构分为三层最上层是用户交互模块命令行参数解析、状态打印中间层是 TCP 状态机与报文处理核心tcp_fsm.c,tcp_packet.c最底层是 raw socket 封装与环回接口绑定netif.c。这种分层不是为了炫技而是为了隔离当你想改重传逻辑时只动tcp_fsm.c想加一个新选项比如时间戳只改tcp_packet.c中的build_tcp_header()而netif.c保证你永远不用碰setsockopt(SO_BINDTODEVICE)这种容易翻车的系统调用。2.1 核心目录结构与文件职责说明项目解压后共 12 个文件无子目录结构极简但职责清晰tcp_project/ ├── Makefile # 关键定义了 -DDEBUG 编译宏开启详细日志 ├── main.c # 入口解析 -sserver/-cclient、-pport、-ddebug ├── tcp_fsm.c # TCP 状态机主干state_transition() 函数驱动整个连接生命周期 ├── tcp_packet.c # 报文构造与解析build_tcp_syn(), parse_tcp_header(), calc_checksum() ├── netif.c # raw socket 封装open_raw_socket(), send_raw_packet(), recv_raw_packet() ├── utils.c # 工具函数hex_dump()十六进制打印、get_current_time_ms() ├── tcp_fsm.h / tcp_packet.h / netif.h / utils.h # 对应头文件声明关键结构体与函数 └── README.md # 极简说明仅含编译命令、运行示例、已知限制如不支持 IPv6提示tcp_fsm.h中定义的enum tcp_state { TCP_CLOSED, TCP_SYN_SENT, TCP_ESTABLISHED, ... }是理解整个项目逻辑的钥匙。所有状态跳转都围绕这个枚举展开而不是靠字符串匹配或魔法数字。2.2 关键数据结构tcp_control_blockTCB是你的“连接大脑”项目没有使用内核的struct sock而是自己定义了一个精简版 TCB它才是每个连接的真正控制中心// tcp_fsm.h typedef struct { uint32_t local_ip; // 本机 IP固定为 0x7F000001即 127.0.0.1 uint16_t local_port; // 本机端口由 -p 参数指定或随机 uint32_t remote_ip; // 对端 IP同上loopback uint16_t remote_port; // 对端端口 enum tcp_state state; // 当前状态核心 uint32_t snd_nxt; // 下一个要发送的序列号初始为随机 ISN uint32_t snd_una; // 最早未确认的序列号用于计算窗口 uint32_t rcv_nxt; // 下一个期望接收的序列号初始为对方 ISN1 uint16_t snd_wnd; // 发送窗口大小当前硬编码为 512可改 uint16_t rcv_wnd; // 接收窗口大小同上 uint32_t rto; // 重传超时时间毫秒初始 1000指数退避 uint8_t retry_count; // 当前重试次数超过 3 次则断连 uint8_t *snd_buf; // 发送缓冲区malloc 分配最大 1024 字节 size_t snd_buf_len; // 实际待发送数据长度 } tcp_control_block;这个结构体就是你在gdb里最该盯住的对象。snd_nxt和rcv_nxt的变化直接对应着 Wireshark 里 Sequence number 和 Acknowledgment number 的数值rto的增长就是你看到的重传间隔越来越长的原因而retry_count达到阈值后state变成TCP_CLOSED就是连接彻底失败的信号。所有“玄学”问题最终都能在这个结构体的字段变化里找到答案。2.3 编译与调试Makefile 里的 -DDEBUG 是你的后悔药项目Makefile极其简洁但藏着关键开关# Makefile CC gcc CFLAGS -Wall -Wextra -g -DDEBUG TARGET tcp_demo SOURCES main.c tcp_fsm.c tcp_packet.c netif.c utils.c $(TARGET): $(SOURCES) $(CC) $(CFLAGS) -o $ $^ clean: rm -f $(TARGET)注意-DDEBUG这个宏。它直接控制tcp_fsm.c和tcp_packet.c中大量#ifdef DEBUG ... #endif日志的输出。这些日志不是简单的printf(state changed)而是精确到每个字段的快照// tcp_fsm.c 中某处 #ifdef DEBUG printf([DEBUG] %s: state%s - %s, snd_nxt0x%08X, rcv_nxt0x%08X, rto%dms\n, __func__, state_name(old_state), state_name(new_state), tcb-snd_nxt, tcb-rcv_nxt, tcb-rto); #endif这意味着你不需要启动 Wireshark 就能初步判断状态机是否按预期流转。而-g编译选项则让你能在gdb ./tcp_demo -s -p 8080启动后直接b tcp_fsm.c:142在状态跳转关键行下断点p *tcb查看整个控制块内容——这是排查“为什么卡在 SYN_SENT”的最高效路径。3. 从零跑通三步编译、双端启动、Wireshark 验证一次到位别被“raw socket”吓住。这个项目专为本地 loopback 设计绕开了绝大多数权限和路由问题。只要你的 Linux 系统装了gcc和tcpdump或wireshark就能在 2 分钟内看到第一个 SYN 包。3.1 编译一条命令静默成功即代表环境 OK打开终端进入项目根目录执行make如果输出类似gcc -Wall -Wextra -g -DDEBUG -o tcp_demo main.c tcp_fsm.c tcp_packet.c netif.c utils.c且无任何error:或warning:warning: unused parameter这类可忽略说明编译成功。生成的tcp_demo就是你的可执行程序。注意不要用sudo make也不需要 root 权限——因为项目只绑定 loopback 接口Linux 允许普通用户对 127.0.0.1 使用 raw socket。提示如果遇到fatal error: net/ethernet.h: No such file or directory说明缺少libpcap-dev或libc6-dev。Ubuntu/Debian 系统执行sudo apt install build-essential libpcap-dev即可CentOS/RHEL 执行sudo yum groupinstall Development Tools和sudo yum install libpcap-devel。3.2 启动服务端监听端口静候连接在第一个终端中启动服务端监听 8080 端口./tcp_demo -s -p 8080你会看到类似输出[INFO] Server mode enabled, binding to port 8080 on 127.0.0.1 [INFO] Raw socket opened successfully (fd3) [INFO] Waiting for incoming SYN...此时服务端已进入TCP_LISTEN状态开始轮询recv_raw_packet()等待 SYN 包。它不会主动发包纯粹是“守株待兔”。3.3 启动客户端发起连接触发三次握手在第二个终端中启动客户端连接本机 8080./tcp_demo -c -p 8080客户端会立即执行构造一个 SYN 包build_tcp_syn()设置随机 ISN通过send_raw_packet()发送到127.0.0.1切换状态为TCP_SYN_SENT启动rto计时器进入循环等待服务端的 SYN-ACK。几乎同时服务端终端会打印[DEBUG] handle_incoming_packet: received SYN from 127.0.0.1:42152 [INFO] Sending SYN-ACK to 127.0.0.1:42152 [DEBUG] state_transition: TCP_LISTEN - TCP_SYN_RCVD, snd_nxt0x1a2b3c4d, rcv_nxt0x5e6f7g8h而客户端终端会紧接着打印[DEBUG] handle_incoming_packet: received SYN-ACK from 127.0.0.1:8080 [INFO] Sending ACK to complete handshake [DEBUG] state_transition: TCP_SYN_SENT - TCP_ESTABLISHED, snd_nxt0x1a2b3c4e, rcv_nxt0x5e6f7g8i [INFO] Connection established! Sending test data...至此三次握手完成状态变为TCP_ESTABLISHED。客户端会自动发送一段 32 字节的测试数据TCP_DEMO_TEST_DATA_0000000000000000服务端收到后打印Received 32 bytes并关闭连接发送 FIN。3.4 Wireshark 验证亲眼所见才是信服在任一终端用tcpdump抓取 loopback 接口的 TCP 流量sudo tcpdump -i lo -nn -w tcp_handshake.pcap tcp port 8080然后在另一个终端运行客户端./tcp_demo -c -p 8080。结束后CtrlC停止tcpdump用 Wireshark 打开tcp_handshake.pcap。你应该能看到清晰的三包No.TimeSourceDestinationProtocolInfo10.00127.0.0.1:42152127.0.0.1:8080TCP42152 → 8080 [SYN] Seq0x1a2b3c4d20.00127.0.0.1:8080127.0.0.1:42152TCP8080 → 42152 [SYN, ACK] Seq0x5e6f7g8h Ack0x1a2b3c4e30.00127.0.0.1:42152127.0.0.1:8080TCP42152 → 8080 [ACK] Seq0x1a2b3c4e Ack0x5e6f7g8i重点对比Wireshark 里显示的Seq和Ack数值必须与你gdb中看到的tcb-snd_nxt和tcb-rcv_nxt完全一致。这是验证你“真的在操作协议栈”而非只是调用系统 socket 的铁证。4. 避坑指南那些让我重启三次虚拟机才搞懂的常见问题这个项目虽小但 raw socket 和 TCP 状态机的组合天然容易触发一些“看似诡异、实则必然”的问题。以下是我在某高校实验室带学生复现时被问得最多、也踩得最深的 4 个坑每一条都附带现场诊断方法。4.1 现象客户端./tcp_demo -c -p 8080启动后服务端完全无反应Wireshark 也抓不到 SYN 包原因客户端构造的 IP 头部中ip_src或ip_dst字段错误导致内核在发送前就丢弃了该包。项目代码中build_ip_header()函数里iph-saddr和iph-daddr必须严格设置为htonl(0x7F000001)即 127.0.0.1 的网络字节序。如果误写成htonl(0x0100007F)字节序颠倒或0x7F000001未转网络序Linux 内核的ip_output()会检测到源地址非法并静默丢弃。解决在build_ip_header()函数末尾添加调试打印printf([DEBUG] IP src0x%08X, dst0x%08X\n, ntohl(iph-saddr), ntohl(iph-daddr));确保输出为IP src0x7F000001, dst0x7F000001。若不一致检查htonl()调用位置。4.2 现象Wireshark 能抓到 SYN但服务端recv_raw_packet()一直返回 0不触发handle_incoming_packet()原因recv_raw_packet()使用recvfrom()读取 raw socket但未正确设置struct sockaddr_ll的sll_ifindex。项目netif.c中open_raw_socket()函数调用setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, ...)时若传入的设备名如lo字符串未以\0结尾或sll_ifindex未通过if_nametoindex(lo)获取会导致 socket 绑定失败recvfrom()永远阻塞或返回 0。解决在open_raw_socket()中bind()调用后立即检查if_nametoindex(lo)返回值是否为非零。添加if (if_nametoindex(lo) 0) { perror(if_nametoindex); exit(1); }。这是新手最容易忽略的“设备索引”概念。4.3 现象三次握手完成后客户端发送数据服务端 Wireshark 能抓到但handle_incoming_packet()不解析tcp_packet.c中parse_tcp_header()返回 NULL原因TCP 校验和计算错误。calc_checksum()函数需对伪头部12 字节src_ip dst_ip 0 proto tcp_len TCP 头部 TCP 数据进行累加。常见错误是忘记将伪头部的proto值为 6和tcp_lenTCP 头部长度数据长度放入计算或tcp_len未包含 TCP 头部即只算了数据长度。校验和错误会导致内核在交付给 raw socket 前就丢弃该包。解决在calc_checksum()开头打印伪头部内容printf([DEBUG] Pseudo hdr: src0x%08X, dst0x%08X, proto%d, len%d\n, src, dst, proto, len);并对照 RFC 793 附录 A 验证。一个快速验证法将calc_checksum()直接返回0禁用校验若此时数据能被接收即可确认是校验和问题。4.4 现象客户端发送 FIN 后服务端进入TCP_CLOSE_WAIT但迟迟不发 FIN连接卡在半关闭状态原因项目未实现“应用层主动关闭”逻辑。当前代码中服务端在收到客户端 FIN 后仅将状态设为TCP_CLOSE_WAIT但没有调用send_fin()函数。TCP 规范要求进入TCP_CLOSE_WAIT后应用层应尽快调用close()或本项目中的tcp_close()否则连接将无限期挂起。解决在tcp_fsm.c的handle_fin_received()函数中在状态切换为TCP_CLOSE_WAIT后立即添加tcp_close(tcb);调用。这是对 TCP “四次挥手”流程的完整模拟也是学生最容易漏掉的最后一步。5. 二次开发实战从“能跑”到“能改”三个必做改造与验证技巧跑通只是起点。这个项目的真正价值在于它是一块“可雕刻的璞玉”——所有 TCP 的关键机制都以最直白的 C 代码呈现修改它们就像修改一个计算器的加法函数一样直接。下面这三个改造是我带过的每一届学生都必须完成的“成人礼”做完之后他们对 TCP 的理解就从“背状态图”跃迁到了“亲手调试状态机”。5.1 改造一让重传时间 RTO 可配置并验证指数退避当前rto在tcp_fsm.c中硬编码为1000毫秒且重传时只是简单rto * 2。这不符合真实 TCP 的 Karn 算法。改造目标让rto初始值可通过命令行参数-r 2000设置并在每次重传后严格执行rto min(rto * 2, 60000)上限 60 秒。修改步骤在main.c的参数解析部分getopt循环内添加case r: initial_rto atoi(optarg); break;并声明全局变量uint32_t initial_rto 1000;。在tcp_fsm.c的tcp_init_tcb()函数中将tcb-rto 1000;替换为tcb-rto initial_rto;。在tcp_fsm.c的retransmit_pending()函数中修改重传逻辑tcb-rto (tcb-rto 60000) ? tcb-rto * 2 : 60000; printf([INFO] RTO doubled to %dms after timeout\n, tcb-rto);验证技巧启动服务端后用iptables故意丢弃客户端的 ACK 包强制触发重传# 在客户端机器上假设 IP 为 192.168.1.100 sudo iptables -A OUTPUT -p tcp --dport 8080 --tcp-flags ACK ACK -j DROP ./tcp_demo -c -p 8080 # 此时会看到日志RTO doubled to 2000ms, then 4000ms, then 8000ms... sudo iptables -F # 恢复Wireshark 中观察重传间隔必须与日志打印的RTO doubled时间完全吻合。5.2 改造二增加 TCP 时间戳选项RFC 1323并解析 PAWS时间戳是现代 TCP 的标配用于防止序列号回绕PAWS。改造目标在 SYN 包中添加Kind8, Length10, Timestamp当前毫秒数选项并在收到带时间戳的包时验证其有效性防止旧包重放。修改步骤在tcp_packet.c的build_tcp_header()函数中在填充 TCP 头部后、计算校验和前添加// 添加时间戳选项Kind(1)Len(1)TSval(4)TSecr(4) uint8_t ts_option[10] {8, 10, 0, 0, 0, 0, 0, 0, 0, 0}; uint32_t now_ms get_current_time_ms(); // utils.c 中已有此函数 memcpy(ts_option[2], now_ms, 4); // TSval 当前时间 // TSecr 留 0首次 SYN 不携带 memcpy(tcp_hdr tcp_hdr_len, ts_option, 10); tcp_hdr_len 10;在parse_tcp_header()函数中遍历 TCP 选项从tcp_hdr 20开始查找Kind8提取TSval和TSecr存入 TCB 的新字段uint32_t last_ts_val;和uint32_t last_ts_ecr;。在handle_incoming_packet()中收到非 SYN 包时检查if (tcb-last_ts_val ! 0 ts_val tcb-last_ts_val - 10000)10 秒窗口则丢弃PAWS 检查。验证技巧启动客户端时加-d参数开启 DEBUG观察日志中TSval的数值是否随时间递增手动修改get_current_time_ms()返回固定值再发包观察服务端是否因ts_val last_ts_val而丢弃。5.3 改造三实现简易滑动窗口可视化用 ASCII 图展示发送/接收状态理论上的“滑动窗口”很抽象。改造目标在tcp_fsm.c的tcp_send_data()和handle_ack_received()中每当窗口移动时打印一个 ASCII 图直观显示snd_una已确认、snd_nxt待发送、snd_una snd_wnd窗口右边界的位置。修改步骤在utils.c中新增函数void print_window(uint32_t una, uint32_t nxt, uint32_t wnd_size) { printf([WINDOW] ); for (int i 0; i 64; i) { uint32_t seq una i; if (seq una) printf(U); // 已确认起点 else if (seq nxt) printf(N); // 下一个待发 else if (seq una wnd_size) printf(.); else printf( ); } printf( (wnd%d)\n, wnd_size); }在tcp_send_data()发送前、handle_ack_received()更新snd_una后调用print_window(tcb-snd_una, tcb-snd_nxt, tcb-snd_wnd);。效果示例[WINDOW] U........N................................................ (wnd512) [WINDOW] ..U......N................................................ (wnd512)这比任何文字描述都更能让学生理解“窗口是如何滑动的”。从那以后我每次带新人看 TCP都强制走一遍这三个改造先调 RTO 看重传再加时间戳看防回绕最后画窗口看滑动。不是为了炫技而是因为只有当rto的数值在你眼前跳动、TSval的毫秒数在你日志里滚动、U/N/.的 ASCII 图在你终端里平移时TCP 才不再是 RFC 文档里冰冷的术语而成了你键盘敲出来的、屏幕看得见的、Wireshark 抓得到的活生生的协议。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

C#基于ARP协议的局域网IP冲突实时监测方案

C#基于ARP协议的局域网IP冲突实时监测方案

简介:本资源是一个基于C#开发的局域网ARP包抓取与分析工具,面向网络运维人员、C#初学者及网络安全实践者,用于快速发现局域网内活跃设备并精准识别IP地址冲突主机。项目采用SharpPcap库实现底层ARP数据包捕获,封装为可复用的C#类&…

📅 2026/11/2 6:43:12
超级鹰版免登录实战(一):Chaojiying.py + DrissionPage 实现【古诗文网图片验证码自动识别免登录】保姆级实战教程

超级鹰版免登录实战(一):Chaojiying.py + DrissionPage 实现【古诗文网图片验证码自动识别免登录】保姆级实战教程

一、 前置回顾(承接上篇文章)承接上篇:超级鹰账号注册、充值、获取 softid 💡 核心重点: 1. 使用现成 Chaojiying_Client 超级鹰打码 2. DrissionPage 操控浏览器,元素截图提取验证码 3. 题型编码 1004&…

📅 2026/11/2 6:38:12
专其利 AI V3.4.0 更新说明

专其利 AI V3.4.0 更新说明

专其利 AI V3.4.0 升级说明 AI 对话,更懂你在写什么 输入框支持 快捷引用——直接关联当前专利、上传文件或图片,AI 自动理解上下文,无需反复粘贴内容图片直接上传:支持拖拽或粘贴图片,AI 自动识别图中技术内容并纳入…

📅 2026/11/2 6:38:12
MORE NEWS

更多资讯

📰

完全二叉树、非完全二叉树与哈希表的实现与应用

1. 二叉树与哈希表的基础概念解析在计算机科学领域,数据结构是构建高效算法的基石。完全二叉树、非完全二叉树和哈希表作为三种典型数据结构,各自具有独特的特性和应用场景。理解它们的创建与遍历机制,对于开发者优化程序性能、解决特定问题至…

📰

Python中文词云7案例:从能出图到能控图

简介:这是一份面向初学者与中级Python用户的词云实践教程PDF,聚焦文本挖掘与数据可视化场景,帮助读者把零散文本转化为直观图形。教程围绕wordcloud与matplotlib展开,先说明词云的概念、作用与生成原理,再系统梳理文本…

📰

Conda环境清理全攻略:释放磁盘空间与提升性能

磁盘空间这东西,平时不觉得,一旦红了就让人头皮发麻。尤其是常年跟数据、模型、各种依赖打交道的朋友,Conda 那几层目录动辄几十个 G,删又不敢乱删,留着又实在占地方。这篇内容就是冲着这个痛点来的——把 Conda 环境清…

📰

阶梯碳交易与电制氢综合能源系统24小时调度建模

简介:面向能源系统优化研究人员与具备MATLAB基础的技术人员,这份PDF聚焦阶梯型碳交易与电制氢综合能源系统优化调度,解决碳价分段跃升、电-氢-电多向转换下机组出力安排、成本控制与碳减排问题。内容涵盖传统机组、风电、电解槽、储氢罐、燃料…

📰

冒泡排序与稀疏数组:算法优化与高效存储技术

1. 冒泡排序算法深度解析冒泡排序作为最经典的入门排序算法,其核心思想是通过相邻元素的比较和交换来将较大的元素逐步"浮"到数组的末端。这个看似简单的算法背后蕴含着许多值得深入探讨的细节。1.1 基础实现与时间复杂度分析我们先来看一个标准的冒泡排序…

📰

拆开AI黑箱:从梯度下降到SHAP的模型可解释性实践

模型给这个账户打出 0.93 的欺诈概率,业务同学问了一句“凭什么”,会议室安静了十几秒。这是我在模型交付评审上遇到过最多的一幕:离线指标很漂亮,AUC 0.91、KS 0.48,讲得头头是道,可一旦要落到某个具体样本…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬