• 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] 

  • 相关阅读:
    openssh升级到openssh-7.5p1踩坑
    office online server部署和简单操作
    aspnetmvc和aspnetcoremvc的一些区别
    office web app server部署和简单操作
    PHP之cURL
    认识PHP的全局变量
    认识Linux系统/etc/hosts
    git学习——stash命令(4)
    Linux netstat命令
    phpstorm+xdebug
  • 原文地址:https://www.cnblogs.com/snser/p/4078940.html
Copyright © 2020-2023  润新知