技术指南

首页 / 技术指南

技术指南

C++如何实现Ping?

2020-01-09 20:16:22   来源:   点击:
    在C++中实现ping功能,并不难。但真正了解ping是需要花费一番功夫的。
 
    Ping功能是在ICMP基础上实现的。IP协议并不是一个可靠的协议,它不保证数据被送达,那么,保证数据送达的工作应该由其他的模块来完成。其中一个重要的模块就是ICMP(网络控制报文)协议。ICMP主要是用来实现IP系统间传递差错和管理报文,是任何IP实现必须和要求的组成部分。它是TCP/IP协议族的一个子协议,属于网络层协议。ICMP提供一致易懂的出错报告信息。发送的出错报文返回到发送原数据的设备,因为只有发送设备才是出错报文的逻辑接受者。发送设备随后可根据ICMP报文确定发生错误的类型,并确定如何才能更好地重发失败的数据包。当传送IP数据包发生错误--比如主机不可达,路由不可达等等,ICMP协议将会把错误信息封包,然后传送回给主机。给主机一个处理错误的机会,这也就是为什么说建立在IP层以上的协议是可能做到安全的原因。
 
    。换ip在程序中实现Ping功能时,常用的方法有三:
 
    一、调用system实现
 
    #include <windows.h>
 
    #include <stdio.h>
 
    #include <string.h>
 
    char YN(int k) {
 
    FILE *f;
 
    char fn[40];
 
    char ln[80];
 
    char yn;
 
    int n;
 
    yn='N';
 
    sprintf(fn,"d:\\ping%d.txt",k);
 
    f=fopen(fn,"r");
 
    if (NULL!=f) {
 
    n=0;
 
    while (1) {
 
    if (NULL==fgets(ln,80,f)) break;//
 
    if (strstr(ln,"ms ")) {
 
    yn='Y';
 
    break;//
 
    }
 
    n++;
 
    if (n>=4) break;//
 
    }
 
    fclose(f);
 
    }
 
    return yn;
 
    }
 
    void main(int argc,char **argv) {
 
    char cmdstr[256];
 
    int i;
 
    int IP[3];
 
    char c;
 
    if (argc<2) {
 
    USAGE:
 
    printf("Usage example:\n    %s 192.168.60.\nto test 192.168.60.1-254\n",argv[0]);
 
    return;
 
    }
 
    if (4==sscanf(argv[1],"%d.%d.%d%c",&IP[0],&IP[1],&IP[2],&c)) {
 
    if (0<=IP[0] && IP[0]<=255
 
    && 0<=IP[1] && IP[1]<=255
 
    && 0<=IP[2] && IP[2]<=255
 
    && '.'==c) {
 
    for (i=1;i<255;i++) {
 
    sprintf(cmdstr,"cmd /c ping %s%d -n 1 -w 1000 >d:\\ping%d.txt",argv[1],i,i);
 
    WinExec(cmdstr,SW_HIDE);
 
    }
 
    Sleep(3000);
 
    for (i=1;i<255;i++) {
 
    printf("%c %s%d\n",YN(i),argv[1],i);
 
    }
 
    Sleep(3000);
 
    WinExec("cmd /c del /q d:\\ping*.txt",SW_HIDE);
 
    } else goto USAGE;
 
    } else goto USAGE;
 
    }
 
    二、使用socket协议实现
 
    // MyPing.cpp : Defines the entry point for the console application.
 
    //
 
    #include "stdafx.h"
 
    #include <winsock2.h>
 
    #include <WS2tcpip.h>
 
    #include <stdio.h>
 
    #include <stdlib.h>
 
    #define ICMP_ECHO 8
 
    #define ICMP_ECHOREPLY 0
 
    #define ICMP_MIN 8 //Minimum 8-byte ICMP packet (header)
 
    #define DEF_PACKET_SIZE 32
 
    #define MAX_PACKET 1024
 
    #define MAX_IP_HDR_SIZE 60
 
    //IP header structure
 
    typedef struct _iphdr
 
    {
 
    unsigned int h_len:4;//Length of the header
 
    unsigned int version:4;//Version of IP
 
    unsigned char tos;//Type of service
 
    unsigned short total_len;//Total length of the packet
 
    unsigned short ident;//Unique identifier
 
    unsigned short frag_and_flags;//Flags
 
    unsigned char ttl;//Time to live
 
    unsigned char proto;//Protocol (TCP,UDP,etc.)
 
    unsigned short checksum;//IP checksum
 
    unsigned int sourceIP;
 
    unsigned int destIP;
 
    } IpHeader;
 
    //ICMP header structure
 
    typedef struct _icmphdr
 
    {
 
    BYTE i_type;
 
    BYTE i_code;//Type sub code
 
    USHORT i_cksum;
 
    USHORT i_id;
 
    USHORT i_seq;
 
    //This is not the standard header, but we reserve space for time
 
    ULONG timestamp;
 
    } IcmpHeader;
 
    //IP option header--use with socket option IP_OPTIONS
 
    typedef struct _ipoptionhdr
 
    {
 
    unsigned char code;//Option type
 
    unsigned char len;//Length of option hdr
 
    unsigned char ptr;//Offset into optons
 
    unsigned long addr[9];//List of IP addrs
 
    } IpOptionHeader;
 
    int datasize;
 
    char* lpdest;
 
    //Print usage information
 
    void usage()
 
    {
 
    printf("usage:MyPing -i:IP [data size]\n");
 
    printf("    -i:IP           remote machine to Ping\n");
 
    printf("    datasize     can be up to 1 KB\n");
 
    ExitProcess(1);
 
    }
 
    //Helper function to fill in various fields for our ICMP request
 
    void FillICMPData(char* icmp_data, int datasize)
 
    {
 
    IcmpHeader* icmp_hdr = (IcmpHeader*)icmp_data;
 
    icmp_hdr->i_type = ICMP_ECHO;//Request an ICMP echo
 
    icmp_hdr->i_code = 0;
 
    icmp_hdr->i_id = (USHORT)GetCurrentProcessId();
 
    icmp_hdr->i_cksum = 0;
 
    icmp_hdr->i_seq = 0;
 
    char* datapart = icmp_data + sizeof(IcmpHeader);
 
    //Place some junk in the buffer
 
    memset(datapart, 'E', datasize - sizeof(IcmpHeader));
 
    }
 
    //This function calculates the 16-bit one's complement sum
 
    //of the supplied buffer (ICMP) header
 
    USHORT checksum(USHORT* buffer, int size)
 
    {
 
    unsigned long cksum = 0;
 
    while (size > 1)
 
    {
 
    cksum += *buffer++;
 
    size -= sizeof(USHORT);
 
    }
 
    if (size)
 
    {
 
    cksum += *(UCHAR*)buffer;
 
    }
 
    cksum = (cksum>>16) + (cksum & 0xffff);
 
    cksum += (cksum>>16);
 
    return (USHORT)(~cksum);
 
    }
 
    //If the IP option header is present, find the IP options
 
    //within the IP header and print the record route option values
 
    void DecodeIPOptions(char* buf, int bytes)
 
    {
 
    IpOptionHeader* ipopt = (IpOptionHeader*)(buf + 20);
 
    printf("RR:    ");
 
    for (int i = 0; i < (ipopt->ptr / 4) - 1; i++)
 
    {
 
    IN_ADDR inaddr;
 
    inaddr.S_un.S_addr = ipopt->addr[i];
 
    if (i != 0)
 
    {
 
    printf("        ");
 
    }
 
    HOSTENT* host = gethostbyaddr((char*)&inaddr.S_un.S_addr,
 
    sizeof(inaddr.S_un.S_addr), AF_INET);
 
    if (host)
 
    {
 
    printf("(%-15s) %s\n", inet_ntoa(inaddr), host->h_name);
 
    }
 
    else
 
    {
 
    printf("(%-15s)\n", inet_ntoa(inaddr));
 
    }
 
    }
 
    return;
 
    }
 
    //The response is an IP packet. We must decode the IP header to
 
    //locate the ICMP data.
 
    void DecodeICMPHeader(char* buf, int bytes, struct sockaddr_in* from)
 
    {
 
    static int icmpcount = 0;
 
    IpHeader* iphdr = (IpHeader*)buf;
 
    //Number of 32-bit words * 4 = bytes
 
    unsigned short iphdrlen = iphdr->h_len * 4;
 
    DWORD tick = GetTickCount();
 
    if ((iphdrlen == MAX_IP_HDR_SIZE) && (!icmpcount))
 
    {
 
    DecodeIPOptions(buf, bytes);
 
    }
 
    if (bytes < iphdrlen + ICMP_MIN)
 
    {
 
    printf("Too few bytes from %s\n", inet_ntoa(from->sin_addr));
 
    }
 
    IcmpHeader* icmphdr = (IcmpHeader*)(buf + iphdrlen);
 
    if (icmphdr->i_type != ICMP_ECHOREPLY)
 
    {
 
    printf("nonecho type %d recvd\n", icmphdr->i_type);
 
    return;
 
    }
 
    //Make sure this is an ICMP reply to something we sent!
 
    if (icmphdr->i_id != (USHORT)GetCurrentProcessId())
 
    {
 
    printf("someone else's packet!\n");
 
    return;
 
    }
 
    printf("%d bytes from %s:", bytes, inet_ntoa(from->sin_addr));
 
    printf("  icmp_seq = %d. ", icmphdr->i_seq);
 
    printf("  time:%d ms", tick - icmphdr->timestamp);
 
    printf("\n");
 
    icmpcount++;
 
    return;
 
    }
 
    void ValidateArgs(int argc, _TCHAR** argv)
 
    {
 
    lpdest = NULL;
 
    datasize = DEF_PACKET_SIZE;
 
    for (int i = 1; i < argc; i++)
 
    {
 
    if ((argv[i][0] == '-') || (argv[i][0] == '/'))
 
    {
 
    switch (tolower(argv[i][1]))
 
    {
 
    case 'i':
 
    lpdest = argv[i] + 3;
 
    break;
 
    default:
 
    usage();
 
    break;
 
    }
 
    }
 
    else if (isdigit(argv[i][0]))
 
    {
 
    datasize = atoi(argv[i]);
 
    }
 
    }
 
    }
 
    int _tmain(int argc, _TCHAR* argv[])
 
    {
 
    WSADATA wsaData;
 
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
 
    {
 
    printf("WSAStartup() failed:%d\n", GetLastError());
 
    return -1;
 
    }
 
    ValidateArgs(argc, argv);
 
    SOCKET sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP,
 
    NULL, 0, WSA_FLAG_OVERLAPPED);
 
    if (sockRaw == INVALID_SOCKET)
 
    {
 
    printf("WSASocket() failed:%d\n", WSAGetLastError());
 
    return -1;
 
    }
 
    //Set the send/recv timeout values
 
    struct sockaddr_in from;
 
    int fromlen = sizeof(from);
 
    int timeout = 1000;
 
    int bread = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO,
 
    (char*)&timeout, sizeof(timeout));
 
    if (bread == SOCKET_ERROR)
 
    {
 
    printf("setsockopt(SO_RCVTIMEO) failed:%d\n", WSAGetLastError());
 
    return -1;
 
    }
 
    timeout = 1000;
 
    bread = setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO,(char*)&timeout, sizeof(timeout));
 
    if (bread == SOCKET_ERROR)
 
    {
 
    printf("setsockopt(SO_SNDTIMEO) failed:%d\n", WSAGetLastError());
 
    return -1;
 
    }
 
    struct sockaddr_in dest;
 
    memset(&dest, 0, sizeof(dest));
 
    //Resolve the endpoint's name if necessary
 
    dest.sin_family = AF_INET;
 
    if ((lpdest != NULL) && strlen(lpdest) != 0)
 
    {
 
    dest.sin_addr.s_addr = inet_addr(lpdest);
 
    }
 
    else
 
    {
 
    struct hostent* hp = gethostbyname(lpdest);
 
    if (hp != NULL)
 
    {
 
    memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length);
 
    dest.sin_family = hp->h_addrtype;
 
    printf("dest.sin_addr = %s\n", inet_ntoa(dest.sin_addr));
 
    }
 
    else
 
    {
 
    printf("gethostbyname() failed:%d\n", WSAGetLastError());
 
    return -1;
 
    }
 
    }
 
    //Create the ICMP packet
 
    datasize += sizeof(IcmpHeader);
 
    char* icmp_data = (char*)HeapAlloc(GetProcessHeap(),
 
    HEAP_ZERO_MEMORY, MAX_PACKET);
 
    if (!icmp_data)
 
    {
 
    printf("HeapAlloc() failed:%d\n", GetLastError());
 
    return -1;
 
    }
 
    memset(icmp_data, 0, MAX_PACKET);
 
    FillICMPData(icmp_data, datasize);
 
    char* recvbuf = (char*)HeapAlloc(GetProcessHeap(),   HEAP_ZERO_MEMORY, MAX_PACKET);
 
    //Start sending/receiving ICMP packets
 
    USHORT seq_no = 0;
 
    while (true)
 
    {
 
    static int nCount = 0;
 
    int bwrote;
 
    if (nCount++ == 4)
 
    {
 
    break;
 
    }
 
    ((IcmpHeader*)icmp_data)->i_cksum = 0;
 
    ((IcmpHeader*)icmp_data)->timestamp = GetTickCount();
 
    ((IcmpHeader*)icmp_data)->i_seq = seq_no++;
 
    ((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize);
 
    bwrote = sendto(sockRaw, icmp_data, datasize, 0,(struct sockaddr*)&dest, sizeof(dest));
 
    if (bwrote == SOCKET_ERROR)
 
    {
 
    if (WSAGetLastError() == WSAETIMEDOUT)
 
    {
 
    printf("timed out\n");
 
    continue;
 
    }
 
    printf("sendto() failed:%d\n", WSAGetLastError());
 
    return -1;
 
    }
 
    if (bwrote < datasize)
 
    {
 
    printf("Wrote %d bytes\n", bwrote);
 
    }
 
    bread = recvfrom(sockRaw, recvbuf, MAX_PACKET, 0,
 
    (struct sockaddr*)&from, &fromlen);
 
    if (bread == SOCKET_ERROR)
 
    {
 
    if (WSAGetLastError() == WSAETIMEDOUT)
 
    {
 
    printf("timed out\n");
 
    continue;
 
    }
 
    printf("revefrom() failed:%d\n", WSAGetLastError());
 
    return -1;
 
    }
 
    DecodeICMPHeader(recvbuf, bread, &from);
 
    Sleep(1000);
 
    }
 
    //Cleanup
 
    if (sockRaw != INVALID_SOCKET)
 
    {
 
    closesocket(sockRaw);
 
    }
 
    HeapFree(GetProcessHeap(), 0, recvbuf);
 
    HeapFree(GetProcessHeap(), 0, icmp_data);
 
    WSACleanup();
 
    return 0;
 
    }
 
    三、使用Win API调用ICMP.DLL实现、
 
    在这里需要注意的一点是,Windows平台实现ping功能,如果需要使用到IcmpParseReplies函数,需要涉及到两个动态库Icmp.dll和Iphlpapi.dll。IcmpParseReplies函数在Windows2000上面由Icmp.dll导出,而在 Windows XP及以后的版本是由Iphlpapi.dll导出的。通过调用LoadLibrary和GetProcAddress来检测IcmpParseReplies由哪个动态库导出。优先判断Iphlpapi.dll是否存在且有导出此函数,如果存在就使用此动态库,否则再判断Icmp.dll。
 
    另外随着IP6的使用,如果业务上涉及到这一块,也应该要留意。
 
    常用的函数有三个:IcmpCreateFile、IcmpSendEcho、IcmpCloseHandle。具体使用见下实例。
 
    void Ping(char *pIPAddr)
 
    {
 
    HANDLE iHwnd;
 
    iHwnd=IcmpCreateFile();
 
    IPAddr pAddr;
 
    pAddr=(IPAddr)inet_addr (pIPAddr);
 
    icmp_echo_reply pData;
 
    for(int i=1;i<=LoopSend;i++)
 
    {
 
    IcmpSendEcho(iHwnd,pAddr,NULL,0,NULL,(LPVOID)&pData,sizeof(icmp_echo_reply),0);
 
    if (pData.Status==0)
 
    {
 
    printf("Ping测试返回的结果: Time=%dms TTL=%d \n",(int)pData.RoundTripTime,(int)pData.Options.Ttl);
 
    }
 
    else
 
    {
 
    printf("Ping测试失败...\n");
 
    }
 
    }
 
    if (!IcmpCloseHandle(iHwnd)) printf("Close handle has Error!\n");
 
    }

    你可能会发现,用IcmpSendEcho 测试 127.0.0.1的时候,ICMP_ECHO_REPLY.RoundTripTime 会等于0
 
    其实,这个函数是没有出错的,即使用Ping 127.0.0.1也是可以的
 
    那么应该怎么去判断这个函数出错呢?
 
    用 ICMP_ECHO_REPLY.Status 来获得测试状态(记住,当Status为0的时候,函数是正确运行的)

    要了解Ping的原理,我们先来了解下ping命令的使用
 
    通过发送“网际消息控制协议 (ICMP)”回响请求消息来验证与另一台 TCP/IP 计算机的 IP 级连接。回响应答消息的接收情况将和往返过程的次数一起显示出来。Ping 是用于检测网络连接性、可到达性和名称解析的疑难问题的主要 TCP/IP 命令。

 C++如何实现Ping?
    语法
 
    ping [-t] [-a] [-n Count] [-l Size] [-f] [-i TTL] [-v TOS] [-r Count] [-s Count] [{-j HostList | -k HostList}] [-w Timeout] [TargetName]
 
    -t
 
    指定在中断前 ping 可以持续发送回响请求信息到目的地。要中断并显示统计信息,请按 CTRL-BREAK。要中断并退出 ping,请按 CTRL-C。
 
    -a
 
    指定对目的地 IP 地址进行反向名称解析。如果解析成功,ping 将显示相应的主机名。
 
    -n Count
 
    指定发送回响请求消息的次数。默认值为 4。
 
    -lSize
 
    指定发送的回响请求消息中“数据”字段的长度(以字节表示)。默认值为 32。size 的最大值是 65,527。
 
    -f
 
    指定发送的回响请求消息带有“不要拆分”标志(所在的 IP 标题设为 1)。回响请求消息不能由目的地路径上的路由器进行拆分。该参数可用于检测并解决“路径最大传输单位 (PMTU)”的故障。
 
    -i TTL
 
    指定发送回响请求消息的 IP 标题中的 TTL 字段值。其默认值是是主机的默认 TTL 值。对于 Windows XP 主机,该值一般是 128。TTL 的最大值是 255。
 
    -v TOS
 
    指定发送回响请求消息的 IP 标题中的“服务类型 (TOS)”字段值。默认值是 0。TOS 被指定为 0 到 255 的十进制数。
 
    -r Count
 
    指定 IP 标题中的“记录路由”选项用于记录由回响请求消息和相应的回响应答消息使用的路径。路径中的每个跃点都使用“记录路由”选项中的一个值。如果可能,可以指定一个等于或大于来源和目的地之间跃点数的 Count。Count 的最小值必须为 1,最大值为 9。
 
    -s Count
 
    指定 IP 标题中的“Internet 时间戳”选项用于记录每个跃点的回响请求消息和相应的回响应答消息的到达时间。Count 的最小值必须为 1,最大值为 4。
 
    -jPath
 
    指定回响请求消息使用带有 HostList 指定的中间目的地集的 IP 标题中的“稀疏资源路由”选项。可以由一个或多个具有松散源路由的路由器分隔连续中间的目的地。主机列表中的地址或名称的最大数为 9,主机列表是一系列由空格分开的 IP 地址(带点的十进制符号)。
 
    -k HostList
 
    指定回响请求消息使用带有 HostList 指定的中间目的地集的 IP 标题中的“严格来源路由”选项。使用严格来源路由,下一个中间目的地必须是直接可达的(必须是路由器接口上的邻居)。主机列表中的地址或名称的最大数为 9,主机列表是一系列由空格分开的 IP 地址(带点的十进制符号)。
 
    -w Timeout
 
    指定等待回响应答消息响应的时间(以微妙计),该回响应答消息响应接收到的指定回响请求消息。如果在超时时间内未接收到回响应答消息,将会显示“请求超时”的错误消息。默认的超时时间为 4000(4 秒 )。
 
    TargetName
 
    指定目的端,它既可以是 IP 地址,也可以是主机名。
 
    /?
 
    在命令提示符显示帮助。
 
    每个ICMP报文都有自己的格式,但它们开始的三个字段都是一样的:一个8位的报文类型(type)用来标识报文,一个8位的代码(code)用来 提供有关类型的进一步信息,一个16位的校验和(checksum)。(ICMP采用和IP相同的校验和算法,但ICMP校验和只覆盖ICMP报文)。这 里我们给出ICMP报文首部的数据结构:
 
    struct ICMPHEADER
 
    {
 
    BYTE   i_type;               // 类型
 
    BYTE   i_code;               // 代码
 
    USHORT i_cksum;              // 首部校验和
 
    USHORT i_id;                 // 标识
 
    USHORT i_seq;                // 序列号
 
    ULONG timestamp;             // 时间戳(选用)
 
    };
 
    下表表示了ICMP的报文类型及其含义:

上一篇:sqlserver数据库端口号配置
下一篇:IP切换,内外网切换,路由设置内外网同时连接

关闭
18076763811
动态IP