自己写个ping程序玩玩---附带抓包
ping功能很常用, 在Windows/Linux上, 我们经常用ping功能来探测对方是否在线。 那么, ping到底是怎么实现的呢? 在本文中, 我们自己来写一个ping, 当然, 功能肯定没有Windows/Linux自带的ping那么强大。 但是, 自己写写, 可以理解更深刻。
说明, 为了简便起见, 程序中的众多异常, 我就不考虑了, 只聚焦主要功能。
好, 直接上代码:
#include <stdio.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") // IP数据包头结构(如果不清楚每个字段的含义, 请查看IP报文格式, 我就不对每个字段进行注释了) typedef struct iphdr { unsigned int headLen:4; unsigned int version:4; unsigned char tos; unsigned short totalLen; unsigned short ident; unsigned short fragAndFlags; unsigned char ttl; unsigned char proto; unsigned short checkSum; unsigned int sourceIP; unsigned int destIP; }IpHeader; // ICMP数据头结构 typedef struct ihdr { unsigned char iType; unsigned char iCode; unsigned short iCheckSum; unsigned short iID; unsigned short iSeq; unsigned long timeStamp; }IcmpHeader; // 计算ICMP包的校验和(发送前要用) unsigned short checkSum(unsigned short *buffer, int size) { unsigned long ckSum = 0; while(size > 1) { ckSum += *buffer++; size -= sizeof(unsigned short); } if(size) { ckSum += *(unsigned char*)buffer; } ckSum = (ckSum >> 16) + (ckSum & 0xffff); ckSum += (ckSum >>16); return unsigned short(~ckSum); } // 填充ICMP请求包的具体参数 void fillIcmpData(char *icmpData, int dataSize) { IcmpHeader *icmpHead = (IcmpHeader*)icmpData; icmpHead->iType = 8; // 8表示请求包 icmpHead->iCode = 0; icmpHead->iID = (unsigned short)GetCurrentThreadId(); icmpHead->iSeq = 0; icmpHead->timeStamp = GetTickCount(); char *datapart = icmpData + sizeof(IcmpHeader); memset(datapart, "x", dataSize - sizeof(IcmpHeader)); // 数据部分为xxx..., 实际上有32个x icmpHead->iCheckSum = checkSum((unsigned short*)icmpData, dataSize); // 千万要注意, 这个一定要放到最后 } // 对返回的IP数据包进行解析,定位到ICMP数据 int decodeResponse(char *buf, int bytes, struct sockaddr_in *from, int tid) { IpHeader *ipHead = (IpHeader *)buf; unsigned short ipHeadLen = ipHead->headLen * 4 ; if (bytes < ipHeadLen + 8) // ICMP数据不完整, 或者不包含ICMP数据 { return -1; } IcmpHeader *icmpHead = (IcmpHeader*)(buf + ipHeadLen); // 定位到ICMP包头的起始位置 if (icmpHead->iType != 0) // 0表示回应包 { return -2; } if (icmpHead->iID != (unsigned short)tid) // 理应相等 { return -3; } int time = GetTickCount() - (icmpHead->timeStamp); // 返回时间与发送时间的差值 if(time >= 0) { return time; } return -4; // 时间错误 } // ping操作 int ping(const char *ip, unsigned int timeout) { // 网络初始化 WSADATA wsaData; WSAStartup(MAKEWORD(1, 1), &wsaData); unsigned int sockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); // 注意, 第三个参数非常重要, 指定了是icmp setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)); // 设置套接字的接收超时选项 setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout)); // 设置套接字的发送超时选项 // 准备要发送的数据 int dataSize = sizeof(IcmpHeader) + 32; // 待会儿会有32个x char icmpData[1024] = {0}; fillIcmpData(icmpData, dataSize); unsigned long startTime = ((IcmpHeader *)icmpData)->timeStamp; // 远程通信端 struct sockaddr_in dest; memset(&dest, 0, sizeof(dest)); struct hostent *hp = gethostbyname(ip); memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length); dest.sin_family = hp->h_addrtype; // 发送数据 sendto(sockRaw, icmpData, dataSize, 0, (struct sockaddr*)&dest, sizeof(dest)); int iRet = -1; struct sockaddr_in from; int fromLen = sizeof(from); while(1) { // 接收数据 char recvBuf[1024] = {0}; int iRecv = recvfrom(sockRaw, recvBuf, 1024, 0, (struct sockaddr*)&from, &fromLen); int time = decodeResponse(recvBuf, iRecv, &from, GetCurrentThreadId()); if(time >= 0) { iRet = 0; // ping ok break; } else if( GetTickCount() - startTime >= timeout || GetTickCount() < startTime) { iRet = -1; // ping超时 break; } } // 释放网络 closesocket(sockRaw); WSACleanup(); return iRet; } // 主函数 int main() { // 请用合法的ip地址, 如果ip地址不合法(最好用程序校验一下), 可能产生程序错误。 char szIPs[][16] = { "192.168.1.1", // 我的网关 "192.168.1.100", // 我的PC (wifi接入) "192.168.1.101" // 我的手机 (wifi接入) }; int i = 0; int size = sizeof(szIPs) / sizeof(szIPs[0]); for(i = 0; i < size; i++) { int iRet = ping(szIPs[i], 3000); // 超时时间为3000ms if(0 == iRet) { printf("%s 在线 ", szIPs[i]); } else if(-1 == iRet) { printf("%s 超时 ", szIPs[i]); } else { printf("未知错误"); } } return 0; }
结果:
192.168.1.1 在线
192.168.1.100 在线
192.168.1.101 在线
在上述过程中, wireshark抓包结果为:
有一点值得注意, 当arp缓存表中有ip-mac映射值时, Windows/Linux和我上面的ping不再发arp. 否则需要发arp.
然后, 我把我手机的wifi关掉, 程序运行的结果为:
192.168.1.1 在线
192.168.1.100 在线
192.168.1.101 超时
折腾一下午, 总算是基本搞出来了。 吃饭去
声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。