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字段的起始点。

    另外注意这个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] 

转载于:https://www.cnblogs.com/snser/p/4078940.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的 C++ 实现 Ping 程序的示例代码: ```c++ #include <iostream> #include <cstdlib> #include <cstring> #include <unistd.h> #include <sys/time.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <netinet/ip_icmp.h> using namespace std; const int PACKET_SIZE = 4096; const int ICMP_HEAD_SIZE = 8; const int MAX_WAIT_TIME = 5; // 最大等待时间,单位秒 const int MAX_COUNT = 4; // 最大尝试次数 // 计算校验和 unsigned short CalcChecksum(unsigned short *addr, int len) { unsigned int sum = 0; while (len > 1) { sum += *addr++; len -= 2; } if (len == 1) { sum += *(unsigned char*)addr; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (unsigned short)(~sum); } // 发送 ICMP 报文 int SendIcmpPacket(int sock_fd, const struct sockaddr *dest_addr, int seq) { char packet[PACKET_SIZE]; memset(packet, 0, PACKET_SIZE); struct icmp *icmp_hdr = (struct icmp*)packet; icmp_hdr->icmp_type = ICMP_ECHO; icmp_hdr->icmp_code = 0; icmp_hdr->icmp_id = getpid(); icmp_hdr->icmp_seq = seq; memset(icmp_hdr->icmp_data, 0xa5, PACKET_SIZE - ICMP_HEAD_SIZE); icmp_hdr->icmp_cksum = CalcChecksum((unsigned short*)icmp_hdr, PACKET_SIZE); int ret = sendto(sock_fd, packet, PACKET_SIZE, 0, dest_addr, sizeof(struct sockaddr)); if (ret == -1) { cerr << "sendto error" << endl; return -1; } return 0; } // 接收 ICMP 报文 int RecvIcmpPacket(int sock_fd, struct timeval &tv, struct sockaddr *from_addr) { char packet[PACKET_SIZE]; socklen_t addr_len = sizeof(struct sockaddr); int ret = recvfrom(sock_fd, packet, PACKET_SIZE, 0, from_addr, &addr_len); if (ret == -1) { cerr << "recvfrom error" << endl; return -1; } struct iphdr *ip_hdr = (struct iphdr*)packet; int ip_hdr_len = ip_hdr->ihl * 4; struct icmp *icmp_hdr = (struct icmp*)(packet + ip_hdr_len); int icmp_hdr_len = ret - ip_hdr_len; if (icmp_hdr->icmp_type == ICMP_ECHOREPLY && icmp_hdr->icmp_id == getpid()) { tv.tv_sec = time(NULL) - tv.tv_sec; cout << icmp_hdr_len << " bytes from " << inet_ntoa(((struct sockaddr_in*)from_addr)->sin_addr) << ": icmp_seq=" << icmp_hdr->icmp_seq << " time=" << tv.tv_sec << "s" << endl; return 0; } return -1; } // Ping 操作 int Ping(const char *ip_addr) { int sock_fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (sock_fd < 0) { cerr << "socket error" << endl; return -1; } struct sockaddr_in dest_addr; memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.sin_family = AF_INET; dest_addr.sin_addr.s_addr = inet_addr(ip_addr); int seq = 0; int count = 0; while (count < MAX_COUNT) { if (SendIcmpPacket(sock_fd, (struct sockaddr*)&dest_addr, seq) == -1) { cerr << "SendIcmpPacket error" << endl; return -1; } struct timeval tv; gettimeofday(&tv, NULL); fd_set read_fds; FD_ZERO(&read_fds); FD_SET(sock_fd, &read_fds); struct timeval timeout; timeout.tv_sec = MAX_WAIT_TIME; timeout.tv_usec = 0; int ret = select(sock_fd + 1, &read_fds, NULL, NULL, &timeout); if (ret < 0) { cerr << "select error" << endl; return -1; } else if (ret == 0) { cout << "Request timeout for icmp_seq " << seq << endl; } else if (FD_ISSET(sock_fd, &read_fds)) { if (RecvIcmpPacket(sock_fd, tv, (struct sockaddr*)&dest_addr) == -1) { cerr << "RecvIcmpPacket error" << endl; return -1; } } seq++; count++; sleep(1); } close(sock_fd); return 0; } int main(int argc, char *argv[]) { if (argc != 2) { cerr << "Usage: " << argv[0] << " <ip address>" << endl; return -1; } if (Ping(argv[1]) == -1) { cerr << "Ping error" << endl; return -1; } return 0; } ``` 该程序使用了原始套接字进行 ICMP 报文的发送和接收,并采用了 select 函数进行超时等待。在主函数中,程序需要传入目标 IP 地址进行 Ping 操作。程序会尝试发送指定次数的 ICMP 报文,并等待回应,如果超时则输出“Request timeout”,如果收到回应则输出“icmp_seq=xx time=xx”,其中 xx 表示 ICMP 报文的序列号和延迟时间。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值