简介这是一份面向网络编程初学者与系统开发者的C语言实战资料围绕ICMP协议与原始套接字讲解如何从零实现一个具备基本诊断能力的Ping程序帮助读者理解网络连通性检测的底层原理。压缩包共2个文件包含1个cpp源码与1个txt说明文档整体约2KB源码可直接编译运行文档则补充了实现思路与参考链接便于对照学习。资源重点覆盖ICMP回显请求与应答报文的构造、原始套接字的创建、校验和计算、数据包收发以及往返时间统计等关键环节并涉及网络不可达、权限不足等常见错误的处理思路。目前已有414人学习适合希望深入套接字编程、提升故障排查能力的中级开发者参考实践。1. 从一份 .rar 说起为什么网络排障老手都自己写过 Ping手里这份用C语言实现Ping程序功能.rar解压后核心就是一个ip.cpp加一份www.pudn.com.txt说明。它干的事很朴素不调用系统自带的 ping 命令而是用原始套接字自己构造 ICMP 回显请求发出去、收回来、算 RTT、统计丢包。很多人第一次看到会觉得多此一举——系统 ping 不香吗但真做过网络诊断、抓过包、被内核协议栈的“黑匣子”行为坑过的人往往都会自己撸一个。原因很简单只有自己拼过 ICMP 头部、自己算过校验和你才真正知道 ping 那一行timexx ms背后发生了什么。这份资源适合三类人正在学 C 语言网络编程、想找一个能跑通的原始套接字实战的人做运维或后端、需要把连通性探测嵌进自己程序里的人以及准备面试、被问到“ICMP 和 TCP/UDP 区别”“原始套接字权限”时想有底气的人。它不依赖第三方库Linux 下gcc直接编Windows 下走 Winsock属于那种“代码不长但知识点密度极高”的练手件。下面我按自己拆包复现的顺序把它讲透。2. ICMP 报文结构与原始套接字先把地基打对2.1 为什么必须用 SOCK_RAW 而不是普通 socket普通SOCK_STREAMTCP和SOCK_DGRAMUDP都由内核帮你封装好传输层头部你只管塞数据。但 ICMP 不是传输层协议它是 IP 层的一部分内核没有给你留一个“ICMP 专用 socket 类型”。要自己指定 ICMP 的类型、代码、校验和唯一的路子就是SOCK_RAW配合IPPROTO_ICMP。这意味着两件事第一你得自己填 ICMP 头部第二在 Linux 上这需要 root 权限或CAP_NET_RAW能力否则socket()直接返回 -1errno是EPERM。这是新手第一个翻车点后面避坑章节细说。常见做法是int sockfd socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (sockfd 0) { perror(socket); // 权限不足时这里会打印 Operation not permitted return -1; }AF_INET指定 IPv4SOCK_RAW表示原始套接字IPPROTO_ICMP告诉内核“我关心的是 ICMP 协议”。注意这里第三个参数不是 0写 0 在某些系统上会拿到一个能收所有协议的原始套接字反而更容易收到无关报文干扰解析。2.2 ICMP 头部字段逐个拆ICMP 回显请求类型 8和回显应答类型 0共用同一套头部结构共 8 字节后面跟可变长度的数据区。字段定义如下字段长度回显请求取值回显应答取值说明Type1 字节80报文类型Code1 字节00回显类报文固定为 0Checksum2 字节计算得出计算得出覆盖整个 ICMP 报文Identifier2 字节进程标识原样返回用来匹配请求与应答Sequence2 字节递增序号原样返回判断丢包与乱序Data可变任意填充原样返回常放时间戳用于算 RTTIdentifier 这一项很关键。同一台机器上可能同时有好几个程序在发 ICMP内核把应答分发给哪个原始套接字靠的就是 Identifier 和源地址匹配。我一般用getpid() 0xFFFF作为标识既唯一又省事。Sequence 从 1 开始递增每发一个加一收到应答时比对序号就能知道是哪个请求的回应。2.3 校验和算法那个经典的 16 位反码求和ICMP 校验和是绕不过去的坎算错了对方直接丢弃你只会看到“一直超时”非常玄学。算法本身是标准的 16 位反码求和把报文按 16 位分组累加进位回卷最后取反。unsigned short checksum(void *buf, int len) { unsigned short *p buf; unsigned int sum 0; while (len 1) { sum *p; // 逐 16 位累加 len - 2; } if (len 1) { // 奇数长度补一个字节 sum *(unsigned char *)p; } sum (sum 16) (sum 0xFFFF); // 进位回卷 sum (sum 16); // 再卷一次防止二次进位 return (unsigned short)(~sum); // 取反 }逻辑说明sum用 32 位是为了容纳累加过程中的进位(sum 16) (sum 0xFFFF)把高 16 位的进位加回低 16 位再执行一次是因为第一次回卷可能又产生进位。参数buf指向 ICMP 报文起始地址len是整个 ICMP 报文长度头部 8 字节加数据区。计算前必须先把 Checksum 字段置 0否则算出来是错的——这是第二个高频翻车点。3. 从发包到收包一次完整 Ping 的代码落地3.1 构造并发送 ICMP 回显请求把头部结构体定义好之后填充、算校验和、sendto三步走。结构体我习惯用紧凑定义struct icmp_hdr { unsigned char type; unsigned char code; unsigned short checksum; unsigned short id; unsigned short seq; }; struct icmp_pkt { struct icmp_hdr hdr; struct timeval tv; // 数据区放发送时间戳用于算 RTT };发送逻辑struct icmp_pkt pkt; memset(pkt, 0, sizeof(pkt)); pkt.hdr.type 8; // 回显请求 pkt.hdr.code 0; pkt.hdr.id getpid() 0xFFFF; // 进程标识 pkt.hdr.seq seq; // 递增序号 gettimeofday(pkt.tv, NULL); // 记录发送时刻 pkt.hdr.checksum 0; // 先清零 pkt.hdr.checksum checksum(pkt, sizeof(pkt)); // 再计算 struct sockaddr_in dst; memset(dst, 0, sizeof(dst)); dst.sin_family AF_INET; inet_pton(AF_INET, target_ip, dst.sin_addr); int n sendto(sockfd, pkt, sizeof(pkt), 0, (struct sockaddr *)dst, sizeof(dst));参数说明sendto的第四个参数 flags 填 0 即可目标端口对 ICMP 无意义sockaddr_in里的sin_port保持 0。sizeof(pkt)是 ICMP 报文总长包含 8 字节头部加timeval数据区。注意gettimeofday放在算校验和之前因为时间戳也是报文内容的一部分必须参与校验和计算。3.2 接收应答并过滤无关报文原始套接字有个特性它会收到内核递交给它的所有 ICMP 报文包括别人触发的、目标不可达的、甚至本机其他程序产生的。所以recvfrom回来之后不能无脑当成自己的应答必须逐层过滤。char recvbuf[1024]; struct sockaddr_in from; socklen_t fromlen sizeof(from); int n recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)from, fromlen); if (n 0) { perror(recvfrom); return -1; } struct iphdr *ip (struct iphdr *)recvbuf; // 原始套接字含 IP 头 int ip_hlen ip-ihl * 4; // IP 头长度按 4 字节为单位 struct icmp_hdr *icmp (struct icmp_hdr *)(recvbuf ip_hlen); if (icmp-type ! 0) return 0; // 只要回显应答 if (icmp-id ! (getpid() 0xFFFF)) return 0; // 只认自己的标识逻辑说明Linux 原始套接字收到的数据默认包含 IP 头部所以要先跳过ihl * 4字节再解析 ICMP。ihl字段单位是 4 字节最小值 5即 20 字节带选项时会更长所以不能写死 20。过滤条件两个类型必须是 0回显应答标识必须匹配本进程。少了任何一条你就可能把别人的应答算进自己的 RTT数据全乱。3.3 计算 RTT 与统计丢包率收到合法应答后从数据区取出原始发送时间戳和当前时间相减就是 RTT。统计部分维护三个量已发送数、已接收数、RTT 累加和。struct timeval now, *sent; gettimeofday(now, NULL); sent (struct timeval *)((char *)icmp 8); // 跳过 8 字节 ICMP 头 long rtt_us (now.tv_sec - sent-tv_sec) * 1000000L (now.tv_usec - sent-tv_usec); double rtt_ms rtt_us / 1000.0; sent_count; recv_count; rtt_sum rtt_ms; if (rtt_ms rtt_min) rtt_min rtt_ms; if (rtt_ms rtt_max) rtt_max rtt_ms; printf(%d bytes from %s: icmp_seq%d ttl%d time%.3f ms\n, n - ip_hlen, inet_ntoa(from.sin_addr), icmp-seq, ip-ttl, rtt_ms);参数说明rtt_us用微秒累加避免浮点误差ttl从 IP 头取能粗略反映经过的路由跳数。丢包率在循环结束后用(sent_count - recv_count) / sent_count * 100%算。这里有个细节recvfrom返回的n是包含 IP 头的总长度打印“字节数”时要减掉ip_hlen否则显示的数字会比标准 ping 大 20容易被误认为报文异常。4. 避坑与排查那些让你怀疑人生的报错4.1 socket 返回 -1errno 是 EPERM现象程序一启动就打印socket: Operation not permitted直接退出。原因Linux 从内核层面限制原始套接字普通用户无权创建SOCK_RAW。解决用sudo运行或者给可执行文件加能力sudo setcap cap_net_rawep ./myping后者更优雅不用每次提权。注意setcap对文件系统有要求某些挂载选项下不生效那就老老实实 sudo。4.2 一直超时但系统 ping 是通的现象自己写的程序发出去石沉大海终端里系统ping同一个地址却正常。原因八成是校验和算错了或者计算校验和之前没把 Checksum 字段清零。对方主机收到校验和错误的 ICMP 报文会直接静默丢弃你这边只能看到超时。解决先用tcpdump -i any icmp抓包看报文有没有发出去、对方有没有回再单独写个测试用例验证checksum函数对已知报文的输出。我一般会拿一个手工算好的样例比对确认算法无误再往下走。4.3 收到的应答 seq 对不上、RTT 忽大忽小现象打印出来的icmp_seq乱序或者 RTT 出现几百毫秒的异常值。原因原始套接字会收到本机其他 ICMP 流量过滤不严就会混进来另外多线程或多次运行同一程序时Identifier 如果都用固定值会互相干扰。解决Identifier 用getpid()保证进程间唯一过滤时同时校验 id 和源地址如果程序内部多线程发包再叠加一个线程号或随机数。RTT 异常值多半是混入了别人的应答过滤严了就正常。4.4 在 Windows 上编译报错找不到 iphdr现象代码从 Linux 搬到 Windowsstruct iphdr、struct icmp_hdr全部未定义。原因这些是 Linux 内核头文件里的结构Windows Winsock 不提供。解决Windows 下要自己定义这些结构体并且用WSAStartup初始化原始套接字创建方式也略有不同需要管理员权限。如果只是学习 ICMP 原理建议先在 Linux 下跑通要跨平台就把协议结构体抽出来自己定义别依赖系统头。4.5 目标不可达时程序卡死现象ping 一个不存在的地址程序一直阻塞在recvfrom不返回。原因recvfrom默认是阻塞模式没有应答就永远等下去。解决用setsockopt设置接收超时SO_RCVTIMEO超时后recvfrom返回 -1 且errno为EAGAIN或EWOULDBLOCK此时判定为丢包继续发下一个。超时时间一般设 1 到 2 秒和系统 ping 的默认行为对齐。struct timeval tv {2, 0}; // 2 秒超时 setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, tv, sizeof(tv));5. 进阶技巧把玩具 Ping 改成能进工具箱的探测件跑通基础版之后这份代码还有不少可挖的空间。第一个方向是批量探测与并发。单线程一个一个 ping 效率太低可以把 socket 设成非阻塞用select或poll同时管理多个目标一次轮询里把该发的发出去、该收的收回来。这样扫一个 C 段254 个地址也就几秒钟比 shell 里循环调系统 ping 快一个数量级。实现要点是维护一张“已发送未应答”的表按 Identifier 加 Sequence 做键收到应答就查表匹配、算 RTT、标记完成。第二个方向是把结果结构化输出。系统 ping 的输出是给人看的嵌进监控系统还得正则解析。我一般会加一个--json开关把每个目标的sent、recv、loss、min/avg/max打成 JSON 一行直接喂给日志采集或告警管道。改动不大就是在统计结构体上套一层序列化。第三个方向是区分错误类型。基础版只统计“收到/没收到”但 ICMP 的类型 3目标不可达、类型 11超时携带了更具体的原因比如网络不可达、主机不可达、端口不可达。把这些类型解析出来排障时能直接告诉你“是路由问题还是对方防火墙挡了”比单纯一句“超时”有用得多。解析时注意这些差错报文的数据区里会嵌入原始 IP 头和前 8 字节 ICMP要按 RFC 规定偏移去取。验证自己实现是否正确有个笨但有效的办法同一时刻开两个终端一个跑你的程序一个跑tcpdump -i any -n icmp对比发出的报文数量、类型、校验和字段。如果 tcpdump 里能看到你的请求和对方的应答而你的程序却没打印那问题一定在接收过滤或解析环节而不是网络。这个对照法我用了很多年比盯着代码空想快得多。最后说个习惯。从那以后我每次写涉及报文构造的代码都会先拿一个已知正确的报文做单元测试校验和、长度、字段偏移全部对一遍再往下写业务逻辑。ICMP 这种协议错一个字节就是全盘静默没有后悔药可吃。希望这份拆解能帮你少走几个弯路把这份 .rar 里的东西真正变成自己的。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
TSMaster实战:汽车总线报文分析与图形化显示全流程 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/28 1:52:03
基于人脸表情识别的课堂行为检测系统:从毕设源码到工程实践 简介:这是一套面向计算机相关专业毕业设计与课程实战的Python人脸表情识别课堂行为检测系统,适合正在准备毕设、期末大作业或需要项目练手的学生。项目经导师指导并通过评审,代码完整可运行,对新手友好,能帮助读者快速… · 2026/9/28 1:52:03
无人机目标检测数据集处理:VOC转YOLO格式与训练避坑指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/28 1:52:03
Python搭建QQ聊天机器人极简教程 随着QQ粉丝群管理需求的不断增长,简单的群管工具难以满足复杂的信息响应和自动化需求。现有的自动回复机器人虽然功能强大,但其高昂的年费成为不少用户的顾虑。因此,通过搭建一个自定义机器人来实现自动回复,成为解决这一问题的有效途径。
基于此需求,本文介绍了使用go-c… · 2026/9/28 2:14:08
Python整理百度云盘文件大量重复无用文件 百度云盘容量有限,当文件数量逐渐增多,空间很容易被填满。删除重复文件可以帮助释放大量空间。通过获取云盘缓存目录并使用Python脚本来整理数据,可以高效识别重复文件并避免手动操作的繁琐。
此方法基于 sqlite3 和 pandas 进行数据处理,简单快捷。 文章目录 云盘数据整理… · 2026/9/28 2:14:07
Python实现将图片转化为具有视觉震撼效果的字符图 字符画是一种将图片转化为字符的艺术表现形式,它通过字符的密度和排列来模拟图片的色彩和形状效果。这种技术不仅在视觉上充满了创造力,还在文字处理领域展示了字符的丰富表现力。通过Python,可以将图片转换为字符画,生成具有视觉冲击力的字符艺术。
本文将通过具体步骤和… · 2026/9/28 2:13:48
Python实现将目录下的图片合并成PDF文件 在图像处理和文档管理中,经常需要将一系列图片文件合并为PDF格式,以便于传输、存档和阅读。Python凭借其丰富的第三方库,为图像处理和PDF操作提供了便捷的解决方案。
本文将详细介绍如何通过Python脚本,将目录中的所有图片合并为一个PDF文件,内容包括从基础环境配置到代码… · 2026/9/28 2:13:48
Python实现文件移动到指定文件夹 在编程过程中,经常需要对文件进行整理和管理,将不同类型的文件分类存放在指定文件夹中。Python提供了强大的文件操作模块,使得文件的移动操作变得简单高效。这篇教程将详细讲解如何使用Python实现将文件移动到指定文件夹的功能,帮助理解并掌握文件操作的基本方法和常见应用… · 2026/9/28 2:13:47
【PyQt】PyQT6制作一个Django项目启动器 在现代的桌面和Web应用开发中,Python以其简单高效的特点获得了广泛的应用。通过集成PyQt和Django框架,将桌面应用的便捷操作与Django项目的后端处理相结合,不仅能够提升用户体验,更能显著提高开发的便利性和效率。
本文将聚焦于如何构建一个基于PyQt的Django项目启动器,实… · 2026/9/28 2:13:40
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01
制作网页比较方便的软件怎么选?一文搞懂避坑指南 制作网页比较方便的软件怎么选?一文搞懂避坑指南 很多老板一上来就问:做个网站多少钱?但我反问他:你的域名买了吗?服务器租了吗?他一脸懵。这就是典型的“域名服务器搞不懂”。别急,今天咱们不聊虚的,直接 一文搞懂 那些让你头秃的技术名词。… · 2026/9/28 0:00:06
婚恋网站实战案例:避开3个高价坑,省钱50%还能跑赢流量 婚恋网站实战案例:避开3个高价坑,省钱50%还能跑赢流量 找婚恋网站建站公司,最怕的就是被坑高价。很多同行跟我吐槽,报价单上写得模棱两可,功能栏里全是“高级定制”、“专属UI”,结果落地全是套壳。今天不聊虚的,直接甩几个我经手的 实战案例… · 2026/9/28 0:00:19
济南做网站多少钱:3个案例拆解,防黑源码下载全攻略 济南做网站多少钱:3个案例拆解,防黑源码下载全攻略 上周济南一个做建材的老板找我,脸都绿了。他的官网首页弹出了赌博广告,后台被植入了挖矿脚本。他慌得问我:“网站被黑挂马不知道怎么办?能不能直接找之前的外包公司要源码下载,看看哪里被动了手脚?… · 2026/9/28 0:00:25