Ping的C++實現,ping實現
這是一個老話題了,但是我剛學會...
我們的目的是實現這麼個東西:

之所以用紅框框一下是因為,從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字段的起始點。
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位整型(截斷)後返回