转自:C++实现ping功能,致敬原创!
ping的过程是向目的IP发送一个type=8的ICMP响应请求报文,目标主机收到这个报文之后,会向源IP(发送方,我)回复一个type=0的ICMP响应应答报文。那上面的字节、往访时间、TTL之类的信息又是从哪来的呢?这取决于IP和ICMP的头部。
1. IP头部:
头部内容有点多,我们关心的只有以下几个:
IHL:首部长度。因为IP的头部不是定长的,所以需要这个信息进行IP包的解析,从而找到Data字段的起始点。另外注意这个IHL是以4个字节为单位的,所以首部实际长度是IHL*4字节。
Time to Live:生存时间,这个就是TTL了。
Data:这部分是IP包的数据,也就是ICMP的报文内容。
2. ICMP响应请求/应答报文头部:
Type:类型,type=8表示响应请求报文,type=0表示响应应答报文。
Code:代码,与type组合,表示具体的信息,参考这里。
Checksum:检验和,这个是整个ICMP报文的检验和,包括Type、Code、...、Data。
Identifier:标识符,这个一般填入本进程的标识符。
Sequence Number:序号
Data:数据部分
上面是标准的ICMP报文,一般而言,统计ping的往返时间的做法是,在ICMP报文的Data区域写入4个字节的时间戳。在收到应答报文时,取出这个时间戳与当前的时间对比即可。
三、Ping程序实现步骤- 创建类型为SOCK_RAW的一个套接字,同时设定协议IPPROTO_ICMP。
- 创建并初始化ICMP头。
- 调用sendto或WSASendto,将ICMP请求发给远程主机。
- 调用recvfrom或WSARecvfrom,以接收任何ICMP响应。
//--------------------------ping.h--------------------------方法声明部分


1 #pragma once 2 3 //在默认windows.h会包含winsock.h,当你包含winsock2.h就会冲突,因此在包含windows.h前需要定义一个宏,#define WIN32_LEAN_AND_MEAN ;去除winsock.h 4 //要么将#include <winsock2.h>放在#include<windows.h>前面或者直接去掉#include<windows.h> 5 6 #include <winsock2.h> 7 #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib 8 9 #define DEF_PACKET_SIZE 32 10 #define ECHO_REQUEST 8 11 #define ECHO_REPLY 0 12 13 struct IPHeader 14 { 15 BYTE m_byVerHLen; //4位版本+4位首部长度 16 BYTE m_byTOS; //服务类型 17 USHORT m_usTotalLen; //总长度 18 USHORT m_usID; //标识 19 USHORT m_usFlagFragOffset; //3位标志+13位片偏移 20 BYTE m_byTTL; //TTL 21 BYTE m_byProtocol; //协议 22 USHORT m_usHChecksum; //首部检验和 23 ULONG m_ulSrcIP; //源IP地址 24 ULONG m_ulDestIP; //目的IP地址 25 }; 26 27 struct ICMPHeader 28 { 29 BYTE m_byType; //类型 30 BYTE m_byCode; //代码 31 USHORT m_usChecksum; //检验和 32 USHORT m_usID; //标识符 33 USHORT m_usSeq; //序号 34 ULONG m_ulTimeStamp; //时间戳(非标准ICMP头部) 35 }; 36 37 struct PingReply 38 { 39 USHORT m_usSeq; 40 DWORD m_dwRoundTripTime; 41 DWORD m_dwBytes; 42 DWORD m_dwTTL; 43 }; 44 45 class CPing 46 { 47 public: 48 CPing(); 49 ~CPing(); 50 BOOL Ping(DWORD dwDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = 2000); 51 BOOL Ping(char *szDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = 2000); 52 private: 53 BOOL PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout); 54 USHORT CalCheckSum(USHORT *pBuffer, int nSize); 55 ULONG GetTickCountCalibrate(); 56 private: 57 SOCKET m_sockRaw; 58 WSAEVENT m_event; 59 USHORT m_usCurrentProcID; 60 char *m_szICMPData; 61 BOOL m_bIsInitSucc; 62 private: 63 static USHORT s_usPacketSeq; 64 };
//--------------------------ping.cpp--------------------------实现部分


1 #include "ping.h" 2 #include <iostream> 3 USHORT CPing::s_usPacketSeq = 0; 4 5 CPing::CPing() :m_szICMPData(NULL),m_bIsInitSucc(FALSE) 6 { 7 WSADATA WSAData; 8 //WSAStartup(MAKEWORD(2, 2), &WSAData); 9 if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0) 10 { 11 /*如果初始化不成功则报错,GetLastError()返回发生的错误信息*/ 12 printf("WSAStartup() failed: %d\n", GetLastError()); 13 return; 14 } 15 m_event = WSACreateEvent(); 16 m_usCurrentProcID = (USHORT)GetCurrentProcessId(); 17 //setsockopt(m_sockRaw); 18 /*if ((m_sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0)) != SOCKET_ERROR) 19 { 20 WSAEventSelect(m_sockRaw, m_event, FD_READ); 21 m_bIsInitSucc = TRUE; 22 23 m_szICMPData = (char*)malloc(DEF_PACKET_SIZE + sizeof(ICMPHeader)); 24 25 if (m_szICMPData == NULL) 26 { 27 m_bIsInitSucc = FALSE; 28 } 29 }*/ 30 m_sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0); 31 if (m_sockRaw == INVALID_SOCKET) 32 { 33 std::cerr << "WSASocket() failed:" << WSAGetLastError ()<< std::endl; //10013 以一种访问权限不允许的方式做了一个访问套接字的尝试。 34 } 35 else 36 { 37 WSAEventSelect(m_sockRaw, m_event, FD_READ); 38 m_bIsInitSucc = TRUE; 39 40 m_szICMPData = (char*)malloc(DEF_PACKET_SIZE + sizeof(ICMPHeader)); 41 42 if (m_szICMPData == NULL) 43 { 44 m_bIsInitSucc = FALSE; 45 } 46 } 47 } 48 49 CPing::~CPing() 50 { 51 WSACleanup(); 52 53 if (NULL != m_szICMPData) 54 { 55 free(m_szICMPData); 56 m_szICMPData = NULL; 57 } 58 } 59 60 BOOL CPing::Ping(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout) 61 { 62 return PingCore(dwDestIP, pPingReply, dwTimeout); 63 } 64 65 BOOL CPing::Ping(char *szDestIP, PingReply *pPingReply, DWORD dwTimeout) 66 { 67 if (NULL != szDestIP) 68 { 69 return PingCore(inet_addr(szDestIP), pPingReply, dwTimeout); 70 } 71 return FALSE; 72 } 73 74 BOOL CPing::PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout) 75 { 76 //判断初始化是否成功 77 if (!m_bIsInitSucc) 78 { 79 return FALSE; 80 } 81 82 //配置SOCKET 83 sockaddr_in sockaddrDest; 84 sockaddrDest.sin_family = AF_INET; 85 sockaddrDest.sin_addr.s_addr = dwDestIP; 86 int nSockaddrDestSize = sizeof(sockaddrDest); 87 88 //构建ICMP包 89 int nICMPDataSize = DEF_PACKET_SIZE + sizeof(ICMPHeader); 90 ULONG ulSendTimestamp = GetTickCountCalibrate(); 91 USHORT usSeq = ++s_usPacketSeq; 92 memset(m_szICMPData, 0, nICMPDataSize); 93 ICMPHeader *pICMPHeader = (ICMPHeader*)m_szICMPData; 94 pICMPHeader->m_byType = ECHO_REQUEST; 95 pICMPHeader->m_byCode = 0; 96 pICMPHeader->m_usID = m_usCurrentProcID; 97 pICMPHeader->m_usSeq = usSeq; 98 pICMPHeader->m_ulTimeStamp = ulSendTimestamp; 99 pICMPHeader->m_usChecksum = CalCheckSum((USHORT*)m_szICMPData, nICMPDataSize); 100 101 //发送ICMP报文 102 if (sendto(m_sockRaw, m_szICMPData, nICMPDataSize, 0, (struct sockaddr*)&sockaddrDest, nSockaddrDestSize) == SOCKET_ERROR) 103 { 104 return FALSE; 105 } 106 107 //判断是否需要接收相应报文 108 if (pPingReply == NULL) 109 { 110 return TRUE; 111 } 112 113 char recvbuf[256] = { "\0" }; 114 while (TRUE) 115 { 116 //接收响应报文 117 if (WSAWaitForMultipleEvents(1, &m_event, FALSE, 100, FALSE) != WSA_WAIT_TIMEOUT) 118 { 119 WSANETWORKEVENTS netEvent; 120 WSAEnumNetworkEvents(m_sockRaw, m_event, &netEvent); 121 122 if (netEvent.lNetworkEvents & FD_READ) 123 { 124 ULONG nRecvTimestamp = GetTickCountCalibrate(); 125 int nPacketSize = recvfrom(m_sockRaw, recvbuf, 256, 0, (struct sockaddr*)&sockaddrDest, &nSockaddrDestSize); 126 if (nPacketSize != SOCKET_ERROR) 127 { 128 IPHeader *pIPHeader = (IPHeader*)recvbuf; 129 USHORT usIPHeaderLen = (USHORT)((pIPHeader->m_byVerHLen & 0x0f) * 4); 130 ICMPHeader *pICMPHeader = (ICMPHeader*)(recvbuf + usIPHeaderLen); 131 132 if (pICMPHeader->m_usID == m_usCurrentProcID //是当前进程发出的报文 133 && pICMPHeader->m_byType == ECHO_REPLY //是ICMP响应报文 134 && pICMPHeader->m_usSeq == usSeq //是本次请求报文的响应报文 135 ) 136 { 137 pPingReply->m_usSeq = usSeq; 138 pPingReply->m_dwRoundTripTime = nRecvTimestamp - pICMPHeader->m_ulTimeStamp; 139 pPingReply->m_dwBytes = nPacketSize - usIPHeaderLen - sizeof(ICMPHeader); 140 pPingReply->m_dwTTL = pIPHeader->m_byTTL; 141 return TRUE; 142 } 143 } 144 } 145 } 146 //超时 147 if (GetTickCountCalibrate() - ulSendTimestamp >= dwTimeout) 148 { 149 return FALSE; 150 } 151 } 152 } 153 154 USHORT CPing::CalCheckSum(USHORT *pBuffer, int nSize) 155 { 156 unsigned long ulCheckSum = 0; 157 while (nSize > 1) 158 { 159 ulCheckSum += *pBuffer++; 160 nSize -= sizeof(USHORT); 161 } 162 if (nSize) 163 { 164 ulCheckSum += *(UCHAR*)pBuffer; 165 } 166 167 ulCheckSum = (ulCheckSum >> 16) + (ulCheckSum & 0xffff); 168 ulCheckSum += (ulCheckSum >> 16); 169 170 return (USHORT)(~ulCheckSum); 171 } 172 173 ULONG CPing::GetTickCountCalibrate() 174 { 175 static ULONG s_ulFirstCallTick = 0; 176 static LONGLONG s_ullFirstCallTickMS = 0; 177 178 SYSTEMTIME systemtime; 179 FILETIME filetime; 180 GetLocalTime(&systemtime); 181 SystemTimeToFileTime(&systemtime, &filetime); 182 LARGE_INTEGER liCurrentTime; 183 liCurrentTime.HighPart = filetime.dwHighDateTime; 184 liCurrentTime.LowPart = filetime.dwLowDateTime; 185 LONGLONG llCurrentTimeMS = liCurrentTime.QuadPart / 10000; 186 187 if (s_ulFirstCallTick == 0) 188 { 189 s_ulFirstCallTick = GetTickCount(); 190 } 191 if (s_ullFirstCallTickMS == 0) 192 { 193 s_ullFirstCallTickMS = llCurrentTimeMS; 194 } 195 196 return s_ulFirstCallTick + (ULONG)(llCurrentTimeMS - s_ullFirstCallTickMS); 197 }
//-------------------------main.cpp--------------------------测试代码
1 #include <winsock2.h> 2 #include <stdio.h> 3 #include "ping.h" 4 5 int main(void) 6 { 7 CPing objPing; 8 9 char *szDestIP = "127.0.0.1"; 10 PingReply reply; 11 12 printf("Pinging %s with %d bytes of data:\n", szDestIP, DEF_PACKET_SIZE); 13 while (TRUE) 14 { 15 objPing.Ping(szDestIP, &reply); 16 printf("Reply from %s: bytes=%d time=%ldms TTL=%ld\n", szDestIP, reply.m_dwBytes, reply.m_dwRoundTripTime, reply.m_dwTTL); 17 Sleep(500); 18 } 19 20 return 0; 21 }
结果如下图所示:
ICMP中检验和的计算算法为:
1、将检验和字段置为0
2、把需校验的数据看成以16位为单位的数字组成,依次进行二进制反码求和
3、把得到的结果存入检验和字段中
所谓二进制反码求和,就是:
1、将源数据转成反码
2、0+0=0 0+1=1 1+1=0进1
3、若最高位相加后产生进位,则最后得到的结果要加1
在实际实现的过程中,比较常见的代码写法是:
1、将检验和字段置为0
2、把需校验的数据看成以16位为单位的数字组成,依次进行求和,并存到32位的整型中
3、把求和结果中的高16位(进位)加到低16位上,如果还有进位,重复第3步[实际上,这一步最多会执行2次]
4、将这个32位的整型按位取反,并强制转换为16位整型(截断)后返回
其中也遇到了很多问题,头文件包含,WSAStartup函数初始化失败。。。
主要参考:
angerhorse 1 月前