C++实现Ping

来源:互联网 发布:淘宝卖家钻石 编辑:程序博客网 时间:2024/06/08 20:00

转载于 http://www.cnblogs.com/goagent/p/4078940.html



这是一个老话题了,但是我刚学会...

 

我们的目的是实现这么个东西:

之所以用红框框一下是因为,从baidu.com到123.125.114.144的过程是DNS解析,我们暂时先实现ping的部分。

 

基础知识

ping的过程是向目的IP发送一个type=8的ICMP响应请求报文,目标主机收到这个报文之后,会向源IP(发送方,我)回复一个type=0的ICMP响应应答报文。

那上面的字节、往访时间、TTL之类的信息又是从哪来的呢?这取决于IP和ICMP的头部。

 

IP头部:

头部内容有点多,我们关心的只有以下几个:

IHL:首部长度。因为IP的头部不是定长的,所以需要这个信息进行IP包的解析,从而找到Data字段的起始点。

    另外注意这个IHL是以4个字节为单位的,所以首部实际长度是IHL*4字节。

Time to Live:生存时间,这个就是TTL了。

Data:这部分是IP包的数据,也就是ICMP的报文内容。

 

ICMP响应请求/应答报文头部:

Type:类型,type=8表示响应请求报文,type=0表示响应应答报文。

Code:代码,与type组合,表示具体的信息,参考这里。

Checksum:检验和,这个是整个ICMP报文的检验和,包括Type、Code、...、Data。

Identifier:标识符,这个一般填入本进程的标识符。

Sequence Number:序号

Data:数据部分

上面是标准的ICMP报文,一般而言,统计ping的往返时间的做法是,在ICMP报文的Data区域写入4个字节的时间戳。

在收到应答报文时,取出这个时间戳与当前的时间对比即可。

 

代码实现

复制代码
 1 #pragma once 2  3 #include <windows.h> 4  5 //这里需要导入库 Ws2_32.lib,在不同的IDE下可能不太一样  6 //#pragma comment(lib, "Ws2_32.lib") 7  8 #define DEF_PACKET_SIZE 32 9 #define ECHO_REQUEST 810 #define ECHO_REPLY 011 12 struct IPHeader13 {14     BYTE m_byVerHLen; //4位版本+4位首部长度15     BYTE m_byTOS; //服务类型16     USHORT m_usTotalLen; //总长度17     USHORT m_usID; //标识18     USHORT m_usFlagFragOffset; //3位标志+13位片偏移19     BYTE m_byTTL; //TTL20     BYTE m_byProtocol; //协议21     USHORT m_usHChecksum; //首部检验和22     ULONG m_ulSrcIP; //源IP地址23     ULONG m_ulDestIP; //目的IP地址24 };25 26 struct ICMPHeader27 { 28     BYTE m_byType; //类型29     BYTE m_byCode; //代码30     USHORT m_usChecksum; //检验和 31     USHORT m_usID; //标识符32     USHORT m_usSeq; //序号33     ULONG m_ulTimeStamp; //时间戳(非标准ICMP头部)34 };35 36 struct PingReply37 {38     USHORT m_usSeq;39     DWORD m_dwRoundTripTime;40     DWORD m_dwBytes;41     DWORD m_dwTTL;42 };43 44 class CPing45 {46 public:47     CPing();48     ~CPing();49     BOOL Ping(DWORD dwDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = 2000);50     BOOL Ping(char *szDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = 2000);51 private:52     BOOL PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout);53     USHORT CalCheckSum(USHORT *pBuffer, int nSize);54     ULONG GetTickCountCalibrate();55 private:56     SOCKET m_sockRaw; 57     WSAEVENT m_event;58     USHORT m_usCurrentProcID;59     char *m_szICMPData;60     BOOL m_bIsInitSucc;61 private:62     static USHORT s_usPacketSeq;63 };
复制代码
复制代码
  1 #include "ping.h"  2   3 USHORT CPing::s_usPacketSeq = 0;  4   5 CPing::CPing() :   6     m_szICMPData(NULL),   7     m_bIsInitSucc(FALSE)  8 {  9     WSADATA WSAData; 10     WSAStartup(MAKEWORD(1, 1), &WSAData); 11  12     m_event = WSACreateEvent(); 13     m_usCurrentProcID = (USHORT)GetCurrentProcessId(); 14  15     if ((m_sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0)) != SOCKET_ERROR) 16     { 17         WSAEventSelect(m_sockRaw, m_event, FD_READ); 18         m_bIsInitSucc = TRUE; 19  20         m_szICMPData = (char*)malloc(DEF_PACKET_SIZE + sizeof(ICMPHeader)); 21  22         if (m_szICMPData == NULL) 23         { 24             m_bIsInitSucc = FALSE; 25         } 26     } 27 } 28  29 CPing::~CPing() 30 { 31     WSACleanup(); 32  33     if (NULL != m_szICMPData) 34     { 35         free(m_szICMPData); 36         m_szICMPData = NULL; 37     } 38 } 39  40 BOOL CPing::Ping(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout) 41 {   42     return PingCore(dwDestIP, pPingReply, dwTimeout); 43 } 44  45 BOOL CPing::Ping(char *szDestIP, PingReply *pPingReply, DWORD dwTimeout) 46 {   47     if (NULL != szDestIP) 48     { 49         return PingCore(inet_addr(szDestIP), pPingReply, dwTimeout); 50     } 51     return FALSE;     52 } 53  54 BOOL CPing::PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout) 55 { 56     //判断初始化是否成功 57     if (!m_bIsInitSucc) 58     { 59         return FALSE; 60     } 61  62     //配置SOCKET 63     sockaddr_in sockaddrDest;  64     sockaddrDest.sin_family = AF_INET;  65     sockaddrDest.sin_addr.s_addr = dwDestIP; 66     int nSockaddrDestSize = sizeof(sockaddrDest); 67  68     //构建ICMP包 69     int nICMPDataSize = DEF_PACKET_SIZE + sizeof(ICMPHeader); 70     ULONG ulSendTimestamp = GetTickCountCalibrate(); 71     USHORT usSeq = ++s_usPacketSeq;     72     memset(m_szICMPData, 0, nICMPDataSize); 73     ICMPHeader *pICMPHeader = (ICMPHeader*)m_szICMPData; 74     pICMPHeader->m_byType = ECHO_REQUEST;  75     pICMPHeader->m_byCode = 0;  76     pICMPHeader->m_usID = m_usCurrentProcID;     77     pICMPHeader->m_usSeq = usSeq; 78     pICMPHeader->m_ulTimeStamp = ulSendTimestamp; 79     pICMPHeader->m_usChecksum = CalCheckSum((USHORT*)m_szICMPData, nICMPDataSize); 80  81     //发送ICMP报文 82     if (sendto(m_sockRaw, m_szICMPData, nICMPDataSize, 0, (struct sockaddr*)&sockaddrDest, nSockaddrDestSize) == SOCKET_ERROR) 83     { 84         return FALSE; 85     } 86      87     //判断是否需要接收相应报文 88     if (pPingReply == NULL) 89     { 90         return TRUE; 91     } 92  93     char recvbuf[256] = {"\0"}; 94     while (TRUE) 95     { 96         //接收响应报文 97         if (WSAWaitForMultipleEvents(1, &m_event, FALSE, 100, FALSE) != WSA_WAIT_TIMEOUT) 98         { 99             WSANETWORKEVENTS netEvent;100             WSAEnumNetworkEvents(m_sockRaw, m_event, &netEvent);101 102             if (netEvent.lNetworkEvents & FD_READ)103             {104                 ULONG nRecvTimestamp = GetTickCountCalibrate();105                 int nPacketSize = recvfrom(m_sockRaw, recvbuf, 256, 0, (struct sockaddr*)&sockaddrDest, &nSockaddrDestSize);106                 if (nPacketSize != SOCKET_ERROR)107                 {108                     IPHeader *pIPHeader = (IPHeader*)recvbuf;109                     USHORT usIPHeaderLen = (USHORT)((pIPHeader->m_byVerHLen & 0x0f) * 4);110                     ICMPHeader *pICMPHeader = (ICMPHeader*)(recvbuf + usIPHeaderLen);111 112                     if (pICMPHeader->m_usID == m_usCurrentProcID //是当前进程发出的报文113                         && pICMPHeader->m_byType == ECHO_REPLY //是ICMP响应报文114                         && pICMPHeader->m_usSeq == usSeq //是本次请求报文的响应报文115                         )116                     {117                         pPingReply->m_usSeq = usSeq;118                         pPingReply->m_dwRoundTripTime = nRecvTimestamp - pICMPHeader->m_ulTimeStamp;119                         pPingReply->m_dwBytes = nPacketSize - usIPHeaderLen - sizeof(ICMPHeader);120                         pPingReply->m_dwTTL = pIPHeader->m_byTTL;121                         return TRUE;122                     }123                 }124             }125         }126         //超时127         if (GetTickCountCalibrate() - ulSendTimestamp >= dwTimeout)128         {129             return FALSE;130         }131     }132 }133 134 USHORT CPing::CalCheckSum(USHORT *pBuffer, int nSize)135 {136     unsigned long ulCheckSum=0; 137     while(nSize > 1) 138     { 139         ulCheckSum += *pBuffer++; 140         nSize -= sizeof(USHORT); 141     }142     if(nSize ) 143     { 144         ulCheckSum += *(UCHAR*)pBuffer; 145     } 146 147     ulCheckSum = (ulCheckSum >> 16) + (ulCheckSum & 0xffff); 148     ulCheckSum += (ulCheckSum >>16); 149 150     return (USHORT)(~ulCheckSum); 151 }152 153 ULONG CPing::GetTickCountCalibrate()154 {155     static ULONG s_ulFirstCallTick = 0;156     static LONGLONG s_ullFirstCallTickMS = 0;157 158     SYSTEMTIME systemtime;159     FILETIME filetime;160     GetLocalTime(&systemtime);    161     SystemTimeToFileTime(&systemtime, &filetime);162     LARGE_INTEGER liCurrentTime;163     liCurrentTime.HighPart = filetime.dwHighDateTime;164     liCurrentTime.LowPart = filetime.dwLowDateTime;165     LONGLONG llCurrentTimeMS = liCurrentTime.QuadPart / 10000;166 167     if (s_ulFirstCallTick == 0)168     {169         s_ulFirstCallTick = GetTickCount();170     }171     if (s_ullFirstCallTickMS == 0)172     {173         s_ullFirstCallTickMS = llCurrentTimeMS;174     }175 176     return s_ulFirstCallTick + (ULONG)(llCurrentTimeMS - s_ullFirstCallTickMS);177 }
复制代码
复制代码
 1 #include <windows.h> 2 #include <stdio.h> 3 #include "ping.h" 4  5 int main(void)  6 { 7     CPing objPing;   8      9     char *szDestIP = "123.125.114.144";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=%ld 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位整型(截断)后返回

0 0
原创粉丝点击