C++实现Ping

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

 

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

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

 

基础知识

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

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

 

IP头部:

C++实现Ping

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

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 8

10 #define ECHO_REPLY 0

11 

12 struct IPHeader

13 {

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; //TTL

20     BYTE m_byProtocol; //协议

21     USHORT m_usHChecksum; //首部检验和

22     ULONG m_ulSrcIP; //源IP地址

23     ULONG m_ulDestIP; //目的IP地址

24 };

25 

26 struct ICMPHeader

27 { 

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 PingReply

37 {

38     USHORT m_usSeq;

39     DWORD m_dwRoundTripTime;

40     DWORD m_dwBytes;

41     DWORD m_dwTTL;

42 };

43 

44 class CPing

45 {

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 };
View Code [ping.h]
  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 }
View Code [ping.cpp]
 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位整型(截断)后返回

 

[转载请保留本文地址:http://www.cnblogs.com/goagent/p/4078940.html] 

你可能感兴趣的:(ping)