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\n", 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] = { "\0" };
    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:\n", szDestIP, DEF_PACKET_SIZE);
    while (TRUE)
    {
        objPing.Ping(szDestIP, &reply);
        printf("Reply from %s: bytes=%d time=%ldms TTL=%ld\n", 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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM