从零手写Ping程序:Winsock原始套接字与ICMP协议实战

发布时间:2026/9/30 9:42:06

从零手写Ping程序:Winsock原始套接字与ICMP协议实战 简介这份计算机网络课程设计报告面向高校计算机相关专业学生聚焦用Winsock技术实现Ping应用程序这一典型网络编程课题适合正在完成课设、需要理解套接字编程与ICMP协议原理的学习者参考。资源包内含1个doc文档大小约333KB内容按绪论、需求分析、详细设计、设计小结、参考文献与附录组织结构完整可直接作为课设撰写与实现的参考模板。报告从Ping、Winsock、ICMP三个基础概念切入梳理了WSAStartup、socket、inet_addr、gethostbyname、recv、sendto等关键函数的用途与调用方式并给出Ping程序的设计流程与实现思路能帮助读者理解应用层如何直接调用网络层ICMP完成回送请求与应答的收发。目前已有235人学习适合需要快速搭建课设框架、理清Winsock编程脉络并补充排错思路的读者。1. 从零手写 Ping为什么 Winsock 课设绕不开 ICMP很多人做计算机网络课程设计选题第一反应就是“用 Winsock 写个 Ping 程序”。听起来简单——不就是发个包收个包吗真动手才发现卡住你的不是 C 语言语法而是 ICMP 协议在 Windows 下的原始套接字权限、校验和计算、超时重传逻辑以及“为什么我 ping 本机通、ping 网关就不通”这类玄学问题。这个题目的本质是让你用 Winsock 的原始套接字SOCK_RAW直接构造并解析 ICMP 报文绕过系统自带的 ping.exe从字节层面理解“连通性探测”到底发生了什么。它适合已经学过 TCP/IP 协议栈、想真正把“IP 层以上、传输层以下”那层黑匣子撬开的学生也适合工作中需要自己写网络探测工具、不想只会调 ping 命令的工程师。热词里“ping 命令与 ICMP 协议分析”“ping 命令的功能和用法”反复出现说明大家真正想搞懂的是那条ping 192.168.1.1背后Winsock 到底替你做了什么、又没替你做什么。2. 动手之前ICMP 报文结构与 Winsock 原始套接字选型2.1 ICMP 回显报文长什么样每个字段为什么不能填错Ping 用的是 ICMP 的 Echo Request类型 8和 Echo Reply类型 0。一个完整的 ICMP 回显报文由 8 字节固定头和可变长度数据组成。固定头里类型和代码各占 1 字节校验和占 2 字节标识符和序列号各占 2 字节。标识符通常填进程 ID用来区分同一台机器上多个 ping 实例序列号从 1 开始递增用来匹配请求和应答。数据部分随便填常见做法是填 32 字节的递增模式或时间戳方便肉眼核对。这里最容易翻车的是校验和。ICMP 校验和覆盖整个 ICMP 报文头数据算法是 16 位反码求和再取反。很多人从网上抄一段 checksum 函数结果 ping 本机通、ping 外网不通就是因为校验和算错了但本机回环不校验。下面这段代码是我反复用过、在 Windows 和 Linux 下都验证过的校验和实现// 计算 ICMP 校验和16 位反码求和后取反 unsigned short checksum(unsigned short *buf, int len) { unsigned long sum 0; while (len 1) { sum *buf; // 逐 16 位累加 len - 2; } if (len 1) { sum *(unsigned char *)buf; // 奇数长度补一个字节 } sum (sum 16) (sum 0xFFFF); // 回卷高位 sum (sum 16); // 再回卷一次 return (unsigned short)(~sum); // 取反 }参数说明buf指向 ICMP 报文起始地址len是报文总长度。注意传入前必须把校验和字段清零否则算出来的结果是错的。这个函数对奇数字节的处理是很多教材忽略的细节但实际抓包对比时能看出差别。2.2 为什么必须用 SOCK_RAW以及 Windows 下的权限门槛Winsock 里能发 ICMP 的套接字类型只有SOCK_RAW协议参数填IPPROTO_ICMP。创建方式SOCKET sock socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (sock INVALID_SOCKET) { printf(socket() failed: %d\n, WSAGetLastError()); return -1; }这段代码在 Windows 上有一个硬门槛必须用管理员权限运行。普通用户调用socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)会返回WSAEACCES10013。这不是代码写错了是 Windows 从 Vista 开始收紧的原始套接字策略。解决办法只有两个右键“以管理员身份运行”或者在 manifest 里声明requireAdministrator。很多同学在 IDE 里直接 F5 调试忘了提权然后对着 10013 错误码查半天这是血泪经验里排第一的坑。另外SOCK_RAW收到的数据包含 IP 头默认情况下所以解析 ICMP 报文时要先跳过 20 字节的 IP 头如果 IP 头有选项则更长但一般没有。发送时则不需要自己构造 IP 头Winsock 会替你填。这个“收包含 IP 头、发不包含 IP 头”的不对称性是第二个容易搞混的地方。2.3 超时与重传别让 recvfrom 把整个程序卡死Ping 的核心交互是发一个 Echo Request等 Echo Reply超时就重发或报“请求超时”。如果用阻塞式recvfrom网络不通时程序会一直卡住。正确做法是用setsockopt设置接收超时int timeout 1000; // 单位毫秒 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)timeout, sizeof(timeout));这样recvfrom最多等 1 秒超时返回SOCKET_ERROR错误码WSAETIMEDOUT。然后你可以在外层循环里决定是重发还是记录超时。一般课设要求发 4 个包每个包超时 1 秒总共不超过 4 秒。注意SO_RCVTIMEO在 Windows 下参数类型是const char *在 Linux 下是struct timeval跨平台移植时要改。还有一个细节收到的不一定是自己的 Echo Reply。网络上可能有其他 ICMP 包比如目标不可达所以收到包后要检查IP 头里的源地址是否等于目标地址、ICMP 类型是否为 0、标识符是否等于自己的进程 ID。三个条件都满足才算有效应答。只检查类型不检查标识符在多实例同时 ping 时会串包。3. 从代码到可运行Winsock Ping 的完整实现路径3.1 初始化 Winsock 与解析目标地址Windows 下用 Winsock 必须先调WSAStartup退出时调WSACleanup。目标地址可以用inet_addr把点分十进制字符串转成in_addr也可以用getaddrinfo支持域名。课设一般要求支持 IP 和域名两种输入所以推荐getaddrinfoWSADATA wsaData; WSAStartup(MAKEWORD(2, 2), wsaData); // 请求 Winsock 2.2 struct addrinfo hints {0}, *res NULL; hints.ai_family AF_INET; // 只用 IPv4 hints.ai_socktype SOCK_RAW; // 原始套接字 hints.ai_protocol IPPROTO_ICMP; // ICMP 协议 int ret getaddrinfo(www.example.com, NULL, hints, res); if (ret ! 0) { printf(getaddrinfo failed: %d\n, ret); WSACleanup(); return -1; } struct sockaddr_in *target (struct sockaddr_in *)res-ai_addr;参数说明MAKEWORD(2,2)表示 Winsock 2.2 版本这是目前最通用的。hints里指定AF_INET就只返回 IPv4 地址避免拿到 IPv6 地址后SOCK_RAW行为不一致。getaddrinfo返回的链表用完要freeaddrinfo释放。如果解析失败常见原因是 DNS 配置问题或域名拼写错误可以先在命令行nslookup验证一下。3.2 构造并发送 Echo Request构造 ICMP 报文时我习惯定义一个结构体但要注意字节对齐问题。Windows 下默认按 4 字节对齐而 ICMP 头是 8 字节刚好对齐所以直接sizeof没问题。但如果你在数据部分放了char数组整个结构体大小可能被填充。稳妥做法是用#pragma pack(1)或者手动拼字节。下面用结构体方式清晰易读#pragma pack(push, 1) // 按 1 字节对齐避免填充 typedef struct { unsigned char type; // 类型8 表示 Echo Request unsigned char code; // 代码0 unsigned short checksum; // 校验和 unsigned short id; // 标识符填进程 ID unsigned short seq; // 序列号 char data[32]; // 数据部分 } ICMPHeader; #pragma pack(pop) ICMPHeader packet; packet.type 8; packet.code 0; packet.checksum 0; packet.id (unsigned short)GetCurrentProcessId(); packet.seq htons(seq); // 序列号转网络字节序 memset(packet.data, A (seq % 26), sizeof(packet.data)); // 填充可识别数据 packet.checksum checksum((unsigned short *)packet, sizeof(packet)); int sent sendto(sock, (char *)packet, sizeof(packet), 0, (struct sockaddr *)target, sizeof(*target));逻辑说明#pragma pack(1)保证结构体没有填充字节否则sizeof(ICMPHeader)可能变成 40 而不是 4083240刚好对齐但换个数据长度就会出问题。id用进程 ID 是为了区分不同 ping 进程。seq用htons转网络字节序因为网络协议是大端。checksum必须在所有字段填好、且校验和字段为 0 时计算。sendto的最后一个参数是目标地址长度SOCK_RAW下必须传sizeof(struct sockaddr_in)。发送成功后sendto返回实际发送的字节数。如果返回SOCKET_ERROR用WSAGetLastError()看错误码。常见的有WSAEHOSTUNREACH目标不可达和WSAENETUNREACH网络不可达这两个通常意味着路由问题不是代码问题。3.3 接收应答并计算 RTT接收用recvfrom缓冲区要足够大因为包含 IP 头。一般 65535 字节的缓冲区肯定够char recvbuf[65535]; struct sockaddr_in from; int fromlen sizeof(from); int recvlen recvfrom(sock, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)from, fromlen); if (recvlen SOCKET_ERROR) { int err WSAGetLastError(); if (err WSAETIMEDOUT) { printf(请求超时\n); } else { printf(recvfrom 错误: %d\n, err); } continue; } // 跳过 IP 头通常 20 字节但 ihl 字段可能大于 5 int ipHeaderLen (recvbuf[0] 0x0F) * 4; ICMPHeader *reply (ICMPHeader *)(recvbuf ipHeaderLen); if (reply-type ! 0 || reply-id ! (unsigned short)GetCurrentProcessId()) { continue; // 不是自己的 Echo Reply丢弃 } // 计算 RTT DWORD rtt GetTickCount() - sendTime; printf(来自 %s 的回复: 字节%d 时间%lu ms TTL%d\n, inet_ntoa(from.sin_addr), recvlen - ipHeaderLen, rtt, recvbuf[8]);逻辑说明recvbuf[0] 0x0F取 IP 头长度字段IHL乘以 4 得到字节数。这样即使 IP 头带选项也能正确跳过。reply-type ! 0过滤掉非 Echo Reply 的 ICMP 包。reply-id比对进程 ID防止串包。TTL 在 IP 头的第 9 个字节偏移 8直接取recvbuf[8]。RTT 用GetTickCount()计算精度毫秒级课设够用如果要微秒级改用QueryPerformanceCounter。注意inet_ntoa返回的是静态缓冲区多次调用会覆盖如果要保存多个地址需要拷贝。另外recvlen - ipHeaderLen才是 ICMP 报文长度不要直接拿recvlen当 ICMP 长度。3.4 统计输出与命令行参数解析一个完整的 Ping 程序最后要输出统计信息发送几个包、收到几个、丢包率、最小/最大/平均 RTT。这些用几个全局变量累加即可。命令行参数解析可以用argc/argv手动判断也可以支持-n指定次数、-l指定数据长度、-w指定超时。课设一般要求支持ping host和ping -n 4 host两种形式。int count 4; // 默认发 4 个包 if (argc 3 strcmp(argv[1], -n) 0) { count atoi(argv[2]); argv 2; argc - 2; } if (argc 2) { printf(用法: ping [-n 次数] 目标地址\n); return 1; } char *host argv[1];参数说明atoi不检查错误如果用户输入-n abc会得到 0最好用strtol并检查。argv 2是一种简化写法把参数指针往后移后面统一用argv[1]取目标。这种写法在课设里够用但生产代码里建议用getopt。统计输出示例正在 Ping www.example.com [93.184.216.34] 具有 32 字节的数据: 来自 93.184.216.34 的回复: 字节32 时间12ms TTL56 来自 93.184.216.34 的回复: 字节32 时间11ms TTL56 请求超时。 来自 93.184.216.34 的回复: 字节32 时间13ms TTL56 93.184.216.34 的 Ping 统计信息: 数据包: 已发送 4已接收 3丢失 1 (25% 丢失) 往返行程的估计时间(以毫秒为单位): 最短 11ms最长 13ms平均 12ms这个输出格式和 Windows 自带 ping 基本一致课设答辩时比较讨巧。4. 避坑与排查那些让课设卡三天的典型问题4.1 坑一socket() 返回 10013程序直接退出现象编译通过运行后打印socket() failed: 10013或者直接闪退。原因Windows 下创建SOCK_RAW需要管理员权限普通用户被拒绝。解决右键可执行文件“以管理员身份运行”或者在 Visual Studio 里把调试配置的“链接器→清单文件→UAC 执行级别”改成requireAdministrator。如果用的是 Code::Blocks 或 Dev-C需要手动写 manifest 文件并嵌入。这个坑在热词“windows11 已关闭 windowsdefender ip 地址无法 ping 通”里也有类似体现——权限和防火墙是两回事但都会让 ping 失败。4.2 坑二能收到包但 RTT 显示为 0 或负数现象GetTickCount()相减得到 0 或者一个很大的数。原因GetTickCount()返回 DWORD如果发送时间在接收时间之后多线程或时间回绕相减会得到一个巨大的正数。另外如果发送后没有立即记录时间而是先做其他操作再记录RTT 会偏小。解决发送前记录sendTime GetTickCount()收到后立即rtt GetTickCount() - sendTime。如果担心 49.7 天回绕用GetTickCount64()。课设运行时间短一般不会回绕但养成好习惯没坏处。4.3 坑三ping 域名通、ping IP 不通或者反过来现象ping www.baidu.com正常ping 8.8.8.8超时或者反过来。原因前者通常是 DNS 解析成功但 ICMP 被中间设备拦截后者是 DNS 配置问题。还有一种情况是getaddrinfo返回了 IPv6 地址而你的SOCK_RAW只处理 IPv4导致发送失败。解决在hints里强制AF_INET并且打印解析到的 IP 地址确认。如果 IP 能通但域名不通检查nslookup是否正常。热词里“centos7 无法 ping 通百度”“虚拟机 ping 不通百度”多半是网络配置或防火墙问题和代码无关但课设答辩时老师可能会问提前用ipconfig /all和route print确认本机网络状态。4.4 坑四校验和算错本机通外网不通现象ping 127.0.0.1正常ping 192.168.1.1超时。原因回环接口不校验 ICMP 校验和所以校验和错了也能收到回复但真实网卡和中间路由器会校验错了直接丢弃。解决用 Wireshark 抓包对比你的程序发出的 ICMP 包和系统 ping 发出的包看校验和字段是否一致。常见错误包括计算前没清零校验和字段、字节序没转换、奇数字节处理遗漏。我一般会在发送前打印校验和值和 Wireshark 里的值比对一次就能定位。4.5 坑五recvfrom 收到自己的请求包现象程序打印的回复类型是 8 而不是 0或者 RTT 异常小。原因SOCK_RAW在某些配置下会收到本机发出的所有 ICMP 包包括自己的 Echo Request。解决检查reply-type 0且reply-id GetCurrentProcessId()。两个条件缺一不可。另外如果绑定了具体网卡也可能收到其他程序的 ICMP 包所以标识符比对是必须的。5. 进阶技巧用 Wireshark 验证与跨平台移植的取舍课设做完能跑只是及格线真正让老师眼前一亮的是你能证明“我的包和系统 ping 的包在字节层面一致”。我一般会同时开 Wireshark 抓回环口或真实网卡过滤icmp然后对比三样东西类型/代码、校验和、数据部分。如果校验和一致、数据模式一致说明你的构造逻辑没问题。Wireshark 里还能看到 TTL 和 IP 头的 DSCP 字段这些是系统 ping 默认填的你的程序可以不填但要知道差异在哪。跨平台方面Winsock 和 Linux 的 socket API 有三大差异一是初始化Windows 要WSAStartupLinux 不用二是错误码Windows 用WSAGetLastError()Linux 用errno三是SO_RCVTIMEO的参数类型不同。如果课设只要求 Windows不用考虑移植但如果想放到 GitHub 上当作品集建议用宏区分#ifdef _WIN32 WSADATA wsaData; WSAStartup(MAKEWORD(2,2), wsaData); #define CLOSE_SOCKET closesocket #define GET_ERR WSAGetLastError() #else #define CLOSE_SOCKET close #define GET_ERR errno #endif这样核心逻辑不用改只替换初始化和错误处理部分。Linux 下SOCK_RAW同样需要 root 权限或者设置CAP_NET_RAW能力。另外 Linux 下recvfrom收到的包是否包含 IP 头取决于是否设置了IP_HDRINCL默认包含和 Windows 一致。最后一个技巧把 RTT 统计做成直方图输出或者把每次 ping 的结果写入 CSV 文件方便写课设报告时画图。这个工作量不大但报告里有一张 RTT 变化曲线比纯文字描述有说服力得多。我当年做课设时就是靠一张 Wireshark 对比截图和一张 RTT 折线图答辩多拿了 5 分。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/9/30 9:42:06

同样的配置,为什么你的服务器更慢?5 个被忽略的调优细节

导读:同样是 4 核 8G 的配置,别人的服务器跑得飞快,你的却动不动卡顿、超时、报错?作为专注 IDC 云服务器与机房运维的多多云,我们在处理工单时发现:90% 的 “配置不够” 其实是配置没调好。这篇文章不聊玄…

2026/9/30 9:37:05

校园网网络系统集成课程设计:三层架构与VLAN/ACL配置详解

简介:这是一份面向计算机网络相关专业学生的《网络系统集成课程设计报告书》Word文档,以某高校校园网为实际场景,完整呈现从需求分析、网络拓扑规划到设备选型的全过程。内容涵盖学校网络现状调研、建设目标设定、千兆主干设计、VLAN划分与三…

2026/9/30 10:47:40

西安24小时自助健身房系统软件开发实战:从零搭建到部署全指南

西安24小时自助健身房系统软件开发实战:从零搭建到部署全指南 随着全民健身意识的提升和“夜经济”的兴起,西安作为西北地区的核心城市,24小时自助健身房的需求日益增长。相比传统健身房,24小时自助模式节省了大量人力成本&#x…

2026/9/30 10:47:40

任务接单平台搭建:自由接单、保证金管理逻辑拆解

任务接单平台搭建:自由接单、保证金管理逻辑拆解同城任务、兼职接单、线上服务类平台,核心商业化与风控能力由两大模块支撑:自由接单流转机制与保证金风控体系。区别于传统派单模式,自由接单主打服务商自主抢单、按需履约&#xf…

2026/9/30 10:47:40

机房搬迁与网络割接实战方案:从物理搬迁到业务割接全流程解析

简介:这份文档面向数据中心管理员与IT基础设施运维人员,聚焦机房整体搬迁与网络设备割接两大核心场景,提供从前期准备到业务切换的一揽子技术指导。内容涵盖搬迁目标、前提条件、职责分工与物理搬迁流程,并针对数据中心核心、服务…

2026/9/30 10:47:40

胶层发生迁移,污染纸袋印刷图案?

本文将讨论胶层迁移对纸袋印刷污染的影响。胶层迁移是一个常见现象,尤其在纸袋的生产和使用阶段。随着胶水与油墨相互作用的增强,图案的清晰度可能下降,颜色变得模糊、图案也可能失真。所以,弄清楚胶层在印刷过程中的物理化学变化…

2026/9/30 10:47:40

RustDesk自建中继服务器:从零搭建稳定远程控制方案

这两年我陆续给身边的同事朋友搭了不少远程控制方案,从商业软件到开源工具都折腾过一圈。最后自己日常在用的,反而是一套看起来最不起眼的组合:RustDesk 加上一台便宜的公网云服务器,自建中继节点,稳定远程控制家里的内…

2026/9/30 10:37:15

Windows驱动开发入门:从零搭建KMDF环境到WinDbg调试实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/29 11:07:23

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/9/29 21:48:03

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/29 7:00:49

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 0:01:22

MATLAB+Yalmip+CPLEX实战:综合能源系统优化调度全流程解析

做综合能源系统优化调度这活儿,最痛苦的不是建模本身,而是模型写完之后不知道该怎么求解。看论文里轻飘飘一句“采用Yalmip调用CPLEX求解”,自己上手时却往往卡在环境配置、变量声明、约束写法和求解状态判读上,一耗就是两三天。这…

2026/9/30 0:01:22

I3C比I2C快10倍?RK3576实战:速率、DTS配置与混合总线避坑指南

I3C 比 I2C 快 10 倍?这句话在嵌入式群里传了很久,每次都能吵出一堆截图。前段时间我正好在 RK3576 上调板级 I3C 接口,从控制器寄存器一路摸到 Linux DTS 配置,踩了不少坑,也把这笔速度账彻底算明白了。本文就用 RK35…

2026/9/30 0:01:22

字符串转对象:JSON.parse、new Function与URLSearchParams

“字符串转对象”这几个字,我在技术群里见过的问法至少有十几种:有人拿着一串{a:1,b:2}说 JSON.parse 直接报错,有人要从 URL 里抠出参数,还有人只是想把abc变成能挂属性的东西。js 这门语言里,字符串和对象之间的转换…

2026/9/29 3:53:39

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/29 9:46:12

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/30 10:28:53

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑