zoukankan      html  css  js  c++  java
  • C++实现ping功能<转>

    今天接到需求要实现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个字节的时间戳。

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

    Ping程序实现步骤

    1. 创建类型为SOCK_RAW的一个套接字,同时设定协议IPPROTO_ICMP。
    2. 创建并初始化ICMP头。
    3. 调用sendto或WSASendto,将ICMP请求发给远程主机。
    4. 调用recvfrom或WSARecvfrom,以接收任何ICMP响应。

    ping.h

    #pragma once
    
    //在默认windows.h会包含winsock.h,当你包含winsock2.h就会冲突,因此在包含windows.h前需要定义一个宏,#define WIN32_LEAN_AND_MEAN ;去除winsock.h
    //要么将#include <winsock2.h>放在#include<windows.h>前面或者直接去掉#include<windows.h>
    
    #include <winsock2.h>
    #pragma comment(lib, "WS2_32")    // 链接到WS2_32.lib
    
    #define DEF_PACKET_SIZE 32
    #define ECHO_REQUEST 8
    #define ECHO_REPLY 0
    
    struct IPHeader
    {
        BYTE m_byVerHLen; //4位版本+4位首部长度
        BYTE m_byTOS; //服务类型
        USHORT m_usTotalLen; //总长度
        USHORT m_usID; //标识
        USHORT m_usFlagFragOffset; //3位标志+13位片偏移
        BYTE m_byTTL; //TTL
        BYTE m_byProtocol; //协议
        USHORT m_usHChecksum; //首部检验和
        ULONG m_ulSrcIP; //源IP地址
        ULONG m_ulDestIP; //目的IP地址
    };
    
    struct ICMPHeader
    {
        BYTE m_byType; //类型
        BYTE m_byCode; //代码
        USHORT m_usChecksum; //检验和 
        USHORT m_usID; //标识符
        USHORT m_usSeq; //序号
        ULONG m_ulTimeStamp; //时间戳(非标准ICMP头部)
    };
    
    struct PingReply
    {
        USHORT m_usSeq;
        DWORD m_dwRoundTripTime;
        DWORD m_dwBytes;
        DWORD m_dwTTL;
    };
    
    class CPing
    {
    public:
        CPing();
        ~CPing();
        BOOL Ping(DWORD dwDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = 2000);
        BOOL Ping(char *szDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = 2000);
    private:
        BOOL PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout);
        USHORT CalCheckSum(USHORT *pBuffer, int nSize);
        ULONG GetTickCountCalibrate();
    private:
        SOCKET m_sockRaw;
        WSAEVENT m_event;
        USHORT m_usCurrentProcID;
        char *m_szICMPData;
        BOOL m_bIsInitSucc;
    private:
        static USHORT s_usPacketSeq;
    };

    ping.cpp

    #include "ping.h"
    #include <iostream>
    USHORT CPing::s_usPacketSeq = 0;
    
    CPing::CPing() :m_szICMPData(NULL),m_bIsInitSucc(FALSE)
    {
        WSADATA WSAData;
        //WSAStartup(MAKEWORD(2, 2), &WSAData);
        if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0)
        {
            /*如果初始化不成功则报错,GetLastError()返回发生的错误信息*/
            printf("WSAStartup() failed: %d
    ", GetLastError());
            return;
        }
        m_event = WSACreateEvent();
        m_usCurrentProcID = (USHORT)GetCurrentProcessId();
        //setsockopt(m_sockRaw);
        /*if ((m_sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0)) != SOCKET_ERROR)
        {
            WSAEventSelect(m_sockRaw, m_event, FD_READ);
            m_bIsInitSucc = TRUE;
    
            m_szICMPData = (char*)malloc(DEF_PACKET_SIZE + sizeof(ICMPHeader));
    
            if (m_szICMPData == NULL)
            {
                m_bIsInitSucc = FALSE;
            }
        }*/
        m_sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0);
        if (m_sockRaw == INVALID_SOCKET)
        {
            std::cerr << "WSASocket() failed:" << WSAGetLastError ()<< std::endl;  //10013 以一种访问权限不允许的方式做了一个访问套接字的尝试。
        }
        else
        {
            WSAEventSelect(m_sockRaw, m_event, FD_READ);
            m_bIsInitSucc = TRUE;
    
            m_szICMPData = (char*)malloc(DEF_PACKET_SIZE + sizeof(ICMPHeader));
    
            if (m_szICMPData == NULL)
            {
                m_bIsInitSucc = FALSE;
            }
        }
    }
    
    CPing::~CPing()
    {
        WSACleanup();
    
        if (NULL != m_szICMPData)
        {
            free(m_szICMPData);
            m_szICMPData = NULL;
        }
    }
    
    BOOL CPing::Ping(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout)
    {
        return PingCore(dwDestIP, pPingReply, dwTimeout);
    }
    
    BOOL CPing::Ping(char *szDestIP, PingReply *pPingReply, DWORD dwTimeout)
    {
        if (NULL != szDestIP)
        {
            return PingCore(inet_addr(szDestIP), pPingReply, dwTimeout);
        }
        return FALSE;
    }
    
    BOOL CPing::PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout)
    {
        //判断初始化是否成功
        if (!m_bIsInitSucc)
        {
            return FALSE;
        }
    
        //配置SOCKET
        sockaddr_in sockaddrDest;
        sockaddrDest.sin_family = AF_INET;
        sockaddrDest.sin_addr.s_addr = dwDestIP;
        int nSockaddrDestSize = sizeof(sockaddrDest);
    
        //构建ICMP包
        int nICMPDataSize = DEF_PACKET_SIZE + sizeof(ICMPHeader);
        ULONG ulSendTimestamp = GetTickCountCalibrate();
        USHORT usSeq = ++s_usPacketSeq;
        memset(m_szICMPData, 0, nICMPDataSize);
        ICMPHeader *pICMPHeader = (ICMPHeader*)m_szICMPData;
        pICMPHeader->m_byType = ECHO_REQUEST;
        pICMPHeader->m_byCode = 0;
        pICMPHeader->m_usID = m_usCurrentProcID;
        pICMPHeader->m_usSeq = usSeq;
        pICMPHeader->m_ulTimeStamp = ulSendTimestamp;
        pICMPHeader->m_usChecksum = CalCheckSum((USHORT*)m_szICMPData, nICMPDataSize);
    
        //发送ICMP报文
        if (sendto(m_sockRaw, m_szICMPData, nICMPDataSize, 0, (struct sockaddr*)&sockaddrDest, nSockaddrDestSize) == SOCKET_ERROR)
        {
            return FALSE;
        }
    
        //判断是否需要接收相应报文
        if (pPingReply == NULL)
        {
            return TRUE;
        }
    
        char recvbuf[256] = { "" };
        while (TRUE)
        {
            //接收响应报文
            if (WSAWaitForMultipleEvents(1, &m_event, FALSE, 100, FALSE) != WSA_WAIT_TIMEOUT)
            {
                WSANETWORKEVENTS netEvent;
                WSAEnumNetworkEvents(m_sockRaw, m_event, &netEvent);
    
                if (netEvent.lNetworkEvents & FD_READ)
                {
                    ULONG nRecvTimestamp = GetTickCountCalibrate();
                    int nPacketSize = recvfrom(m_sockRaw, recvbuf, 256, 0, (struct sockaddr*)&sockaddrDest, &nSockaddrDestSize);
                    if (nPacketSize != SOCKET_ERROR)
                    {
                        IPHeader *pIPHeader = (IPHeader*)recvbuf;
                        USHORT usIPHeaderLen = (USHORT)((pIPHeader->m_byVerHLen & 0x0f) * 4);
                        ICMPHeader *pICMPHeader = (ICMPHeader*)(recvbuf + usIPHeaderLen);
    
                        if (pICMPHeader->m_usID == m_usCurrentProcID //是当前进程发出的报文
                            && pICMPHeader->m_byType == ECHO_REPLY //是ICMP响应报文
                            && pICMPHeader->m_usSeq == usSeq //是本次请求报文的响应报文
                            )
                        {
                            pPingReply->m_usSeq = usSeq;
                            pPingReply->m_dwRoundTripTime = nRecvTimestamp - pICMPHeader->m_ulTimeStamp;
                            pPingReply->m_dwBytes = nPacketSize - usIPHeaderLen - sizeof(ICMPHeader);
                            pPingReply->m_dwTTL = pIPHeader->m_byTTL;
                            return TRUE;
                        }
                    }
                }
            }
            //超时
            if (GetTickCountCalibrate() - ulSendTimestamp >= dwTimeout)
            {
                return FALSE;
            }
        }
    }
    
    USHORT CPing::CalCheckSum(USHORT *pBuffer, int nSize)
    {
        unsigned long ulCheckSum = 0;
        while (nSize > 1)
        {
            ulCheckSum += *pBuffer++;
            nSize -= sizeof(USHORT);
        }
        if (nSize)
        {
            ulCheckSum += *(UCHAR*)pBuffer;
        }
    
        ulCheckSum = (ulCheckSum >> 16) + (ulCheckSum & 0xffff);
        ulCheckSum += (ulCheckSum >> 16);
    
        return (USHORT)(~ulCheckSum);
    }
    
    ULONG CPing::GetTickCountCalibrate()
    {
        static ULONG s_ulFirstCallTick = 0;
        static LONGLONG s_ullFirstCallTickMS = 0;
    
        SYSTEMTIME systemtime;
        FILETIME filetime;
        GetLocalTime(&systemtime);
        SystemTimeToFileTime(&systemtime, &filetime);
        LARGE_INTEGER liCurrentTime;
        liCurrentTime.HighPart = filetime.dwHighDateTime;
        liCurrentTime.LowPart = filetime.dwLowDateTime;
        LONGLONG llCurrentTimeMS = liCurrentTime.QuadPart / 10000;
    
        if (s_ulFirstCallTick == 0)
        {
            s_ulFirstCallTick = GetTickCount();
        }
        if (s_ullFirstCallTickMS == 0)
        {
            s_ullFirstCallTickMS = llCurrentTimeMS;
        }
    
        return s_ulFirstCallTick + (ULONG)(llCurrentTimeMS - s_ullFirstCallTickMS);
    }

    main.cpp

    #include <winsock2.h>
    #include <stdio.h>
    #include "ping.h"
    
    int main(void)
    {
        CPing objPing;
    
        char *szDestIP = "127.0.0.1";
        PingReply reply;
    
        printf("Pinging %s with %d bytes of data:
    ", szDestIP, DEF_PACKET_SIZE);
        while (TRUE)
        {
            objPing.Ping(szDestIP, &reply);
            printf("Reply from %s: bytes=%d time=%ldms TTL=%ld
    ", szDestIP, reply.m_dwBytes, reply.m_dwRoundTripTime, reply.m_dwTTL);
            Sleep(500);
        }
    
        return 0;
    }

    结果:

    附录:如何计算检验和

    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位整型(截断)后返回

    其中也遇到了很多问题,头文件包含,WSAStartup函数初始化失败。。。

    主要参考:

    http://www.cnblogs.com/goagent/p/4078940.html

    http://blog.sina.com.cn/s/blog_5cf5e7c401014fvq.html

    http://blog.csdn.net/segen_jaa/article/details/7569727

    原贴地址:https://www.cnblogs.com/ranjiewen/p/5704627.html
  • 相关阅读:
    Day 03
    Day 03 作业
    Day 02 作业
    Day 02
    Day 01
    Day 10 面向对象基础
    Spring学习-- Bean 的作用域
    一、基本知识
    cloud-init使用技巧
    如何在KVM中管理存储池
  • 原文地址:https://www.cnblogs.com/wainiwann/p/8260619.html
Copyright © 2011-2022 走看看