C实现Windows下的ping功能


转:http://blog.csdn.net/FeeLang/article/details/5102807


一、数据结构

首先根据IP数据包格式(图下图)定义IP数据包头的数据结构

IP数据包

  1. typedef struct tagIPHDR // IP数据包头部  
  2. {  
  3.     u_char VIHL;        // 版本号(4)+头长度(4)  
  4.     u_char TOS;         // 服务类型(8)  
  5.     short TotLen;       // 总长度(16)  
  6.     short ID;           // 标识(16)  
  7.     short FlagOff;      // 标志(3)+片偏移(13)  
  8.     u_char TTL;         // 生存时间TTL(8)  
  9.     u_short CheckSum;   // 头部校验和(16)  
  10.     in_addr iaSrc;      // 源IP地址(32)  
  11.     in_addr iaDst;      // 目标IP地址(32)     
  12. } IPHDR, *PIPHDR;  

然后根据ICMP回送请求与应答报文格式定义ICMP的数据结构

ICMP回送请求与应答

  1. typedef struct tagICMPHDR   // ICMP回送请求与应带ICMP报文  
  2. {  
  3.     u_char Type;        // 类型(8)  
  4.     u_char Code;        // 代码(8)  
  5.     u_short Checksum;   // 校验和(16)  
  6.     u_short ID;         // 标识符(16)  
  7.     u_short Seq;        // 序号(16)  
  8.     char Data;          // 任选数据  
  9. } ICMPHDR, *PICMPHDR;  

 

然后分别定义请求回送的数据长度

  1. #define REQ_DATASIZE 32  

请求回送的数据结构

  1. typedef struct tagECHOREQUEST  
  2. {  
  3.     ICMPHDR icmpHdr;  
  4.     DWORD dwTime;  
  5.     char cData[REQ_DATASIZE];  
  6. } ECHOREQUEST, *PECHOREQUEST;  

ICMP回送应答的数据结构

  1. typedef struct tagECHOREPLY  
  2. {  
  3.     IPHDR ipHdr;  
  4.     ECHOREQUEST echoRequest;  
  5.     char cFiller[256];  
  6. } ECHOREPLY, *PECHOREPLY;  

 

二、函数实现

(1)SendEchoRequest

函数功能是发送回送请求数据包,首先定义三个静态变量

    static ECHOREQUEST echoReq;    // 回送请求数据结构
    static nId = 1;        // 标识符
    static nSeq = 1;    // 序号

然后填写回送请求信息

    echoReq.icmpHdr.Type = ICMP_ECHOREQ; // 类型
    echoReq.icmpHdr.Code = 0;             // 代码
    echoReq.icmpHdr.Checksum = 0;         // 校验和
    echoReq.icmpHdr.ID = nId++;             // 标识符

    echoReq.icmpHdr.Seq = nSeq++;         // 序号

填写要发送的数据
    for (i = 0; i < REQ_DATASIZE; i++)
    {
        echoReq.cData[i] = ' ' + i;
    }

保存发送时间
    echoReq.dwTime = GetTickCount();

 数据存入包中并计算校验和
    echoReq.icmpHdr.Checksum = in_chsum((u_short*)&echoReq, sizeof(ECHOREQUEST));

发送回送请求
    nRet = sendto(s,
        (LPSTR)&echoReq,
        sizeof(ECHOREQUEST),
        0,
        (LPSOCKADDR)lpstToAddr,
        sizeof(SOCKADDR_IN));

(2)RecvEchoReply

函数功能为接收回送应答数据

 

 
DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL)
{
    ECHOREPLY echoReply;    // 回送应答数据结构
    int nRet;
    int nAddrLen = sizeof(sockaddr_in);
    // 接受回送应答
    nRet = recvfrom(s,
                    (LPSTR)&echoReply,
                    sizeof(ECHOREPLY),
                    0,
                    (LPSOCKADDR)lpsaFrom,
                    &nAddrLen);
    // 检查返回的值
    if (nRet == SOCKET_ERROR)
    {
        ReportError("recvfrom()");
    }
    *pTTL = echoReply.ipHdr.TTL;    // 取得TTL值
    return (echoReply.echoRequest.dwTime);    // 返回所用时间
}
(3)WaitForEchoReply

函数功能:等待套接子s是否有数据可读
int WaitForEchoReply(SOCKET s)
{
    timeval Timeout;
    fd_set readfds;
    readfds.fd_count = 1;
    readfds.fd_array[0] = s;
    Timeout.tv_sec = 5;
    Timeout.tv_usec = 0;
    return (select(1, &readfds, NULL, NULL, &Timeout));
}

 

(3)in_chsum

函数功能计算校验和

u_short in_chsum(u_short *addr, int len)
{
    register int nLeft = len;
    register u_short *w = addr;
    register u_short answer;
    register int sum = 0;
    while (nLeft > 1)
    {
        sum += *w++;
        nLeft -= 2;
    }

    if (nLeft == 1)
    {
        u_short u = 0;
        *(u_char*)(&u) = *(u_char*)w;
        sum += u;
    }

    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    answer = ~sum;
    return (answer);
}

(4)main函数的实现

第一步:定义Winsock数据结构wsaData并新建版本号1.1

第二步:调用WSAStartup初始化wsaData

第三步:调用Ping函数

第四步:调用WSACleanup释放Winsock

void main(int argc, char **argv)
{
    WSADATA wsaData;
    WORD wVersionRequested = MAKEWORD(1, 1); // Winsock1.1
    int nRet;
    
    // 命令行参数检查
    if (argc != 2)
    {
        fprintf(stderr, "/nUsage: ping hostname/n");
        return;
    }

    // 初始化Winsock
    nRet = WSAStartup(wVersionRequested, &wsaData);
    if (nRet)
    {
        fprintf(stderr, "/nError initializing Winsock/n");
        return;
    }

    if (wsaData.wVersion != wVersionRequested)
    {
        fprintf(stderr, "/nWinsock version not supported/n");
        return;
    }
    // 调用ping函数
    Ping(argv[1]);
    //Ping("www.sina.com");
    // 释放Winsock
    WSACleanup();
}

(5)Ping

函数功能:实现ping功能

定义函数用到的数据

    SOCKET rawSocket;    // 原始套接字
    LPHOSTENT lpHost;    // 主机信息
    sockaddr_in saDest;    // 目的地址
    sockaddr_in saSrc;    // 源地址
    DWORD dwTimeSent;    // 发送时间
    DWORD dwElapsed;    // 延迟时间

然后创建一个原始套接字

创建一个原始套接口,协议为ICMP协议
    rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

根据用户输入的目的地址获取

lpHost = gethostbyname(pstrHost);

设置目标套接口地址
    saDest.sin_addr.s_addr = *((u_long FAR*)(lpHost->h_addr));
    saDest.sin_family = AF_INET;
    saDest.sin_port = 0;

 输出ping程序的提示信息
    printf("/nPinging %s [%s] with %d bytes of data:/n",
                pstrHost,
                inet_ntoa(saDest.sin_addr),
                REQ_DATASIZE);


发送ICMP回送请求
        SendEchoRequest(rawSocket, &saDest);
使用select()等待接收回送的数据

        WaitForEchoReply(rawSocket);
接收应答
        dwTimeSent = RecvEchoReply(rawSocket, &saSrc, &cTTL);
计算传输时间,并输出提示信息
        dwElapsed = GetTickCount() - dwTimeSent;

答应应答信息
       printf("/nReply from: %s: bytes=%d time=%ldms TTL=%d",
                inet_ntoa(saSrc.sin_addr),
                REQ_DATASIZE,
                dwElapsed,
                cTTL);
    }
    // 关闭套接字
    nRet = closesocket(rawSocket);

附:程序源代码

  1. // ping.h  
  2. // 在该头文件中定义了IP和ICMP协议头的结构  
  3. #pragma pack(1)  
  4. #define ICMP_ECHOREPLY 0  
  5. #define ICMP_ECHOREQ 8  
  6. typedef struct tagIPHDR // IP数据包头部  
  7. {  
  8.     u_char VIHL;        // 版本号(4)+头长度(4)  
  9.     u_char TOS;         // 服务类型(8)  
  10.     short TotLen;       // 总长度(16)  
  11.     short ID;           // 标识(16)  
  12.     short FlagOff;      // 标志(3)+片偏移(13)  
  13.     u_char TTL;         // 生存时间TTL(8)  
  14.     u_short CheckSum;   // 头部校验和(16)  
  15.     in_addr iaSrc;      // 源IP地址(32)  
  16.     in_addr iaDst;      // 目标IP地址(32)     
  17. } IPHDR, *PIPHDR;  
  18. typedef struct tagICMPHDR   // ICMP回送请求与应带ICMP报文  
  19. {  
  20.     u_char Type;        // 类型(8)  
  21.     u_char Code;        // 代码(8)  
  22.     u_short Checksum;   // 校验和(16)  
  23.     u_short ID;         // 标识符(16)  
  24.     u_short Seq;        // 序号(16)  
  25.     char Data;          // 任选数据  
  26. } ICMPHDR, *PICMPHDR;  
  27. // 请求回送的数据长度  
  28. #define REQ_DATASIZE 32  
  29. // ICMP回送请求的数据结构  
  30. typedef struct tagECHOREQUEST  
  31. {  
  32.     ICMPHDR icmpHdr;  
  33.     DWORD dwTime;  
  34.     char cData[REQ_DATASIZE];  
  35. } ECHOREQUEST, *PECHOREQUEST;  
  36. // ICMP回送应答  
  37. typedef struct tagECHOREPLY  
  38. {  
  39.     IPHDR ipHdr;  
  40.     ECHOREQUEST echoRequest;  
  41.     char cFiller[256];  
  42. } ECHOREPLY, *PECHOREPLY;  
  43. #pragma pack()  

 

  1. // ping.cpp  
  2. // 实现简易ping功能  
  3. #include <stdio.h>  
  4. #include <stdlib.h>  
  5. #include <winsock.h>  
  6. #include "ping.h"  
  7. void Ping(LPCSTR pstrHost);  
  8. void ReportError(LPCSTR pstrFrom);  
  9. int WaitForEchoReply(SOCKET s);  
  10. u_short in_chsum(u_short *addr, int len);  
  11. // ICMP 回送请求和应答函数声明  
  12. int SendEchoRequest(SOCKET, LPSOCKADDR_IN);  
  13. DWORD RecvEchoReply(SOCKET, LPSOCKADDR_IN, u_char *);  
  14. // 主程序  
  15. void main(int argc, char **argv)  
  16. {  
  17.     WSADATA wsaData;  
  18.     WORD wVersionRequested = MAKEWORD(1, 1); // Winsock1.1  
  19.     int nRet;  
  20.       
  21.     // 命令行参数检查  
  22.     if (argc != 2)  
  23.     {  
  24.         fprintf(stderr, "/nUsage: ping hostname/n");  
  25.         return;  
  26.     }  
  27.     // 初始化Winsock  
  28.     nRet = WSAStartup(wVersionRequested, &wsaData);  
  29.     if (nRet)  
  30.     {  
  31.         fprintf(stderr, "/nError initializing Winsock/n");  
  32.         return;  
  33.     }  
  34.     if (wsaData.wVersion != wVersionRequested)  
  35.     {  
  36.         fprintf(stderr, "/nWinsock version not supported/n");  
  37.         return;  
  38.     }  
  39.     // 调用ping函数  
  40.     Ping(argv[1]);  
  41.     //Ping("www.sina.com");  
  42.     // 释放Winsock  
  43.     WSACleanup();  
  44. }  
  45. void Ping(LPCSTR pstrHost)  
  46. {  
  47.     SOCKET rawSocket;   // 原始套接字  
  48.     LPHOSTENT lpHost;   // 主机信息  
  49.     sockaddr_in saDest; // 目的地址  
  50.     sockaddr_in saSrc;  // 源地址  
  51.     DWORD dwTimeSent;   // 发送时间  
  52.     DWORD dwElapsed;    // 延迟时间  
  53.     u_char cTTL;          
  54.     int nLoop;  
  55.     int nRet;  
  56.     // 创建一个原始套接口  
  57.     rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);  
  58.     if (rawSocket == SOCKET_ERROR)  
  59.     {  
  60.         ReportError("socket()");  
  61.         return;  
  62.     }  
  63.     lpHost = gethostbyname(pstrHost);  
  64.     if (lpHost == NULL)  
  65.     {  
  66.         fprintf(stderr, "/nHost not found: %s/n", pstrHost);  
  67.         return;  
  68.     }  
  69.     // 设置目标套接口地址  
  70.     saDest.sin_addr.s_addr = *((u_long FAR*)(lpHost->h_addr));  
  71.     saDest.sin_family = AF_INET;  
  72.     saDest.sin_port = 0;  
  73.     // 输出ping程序的提示信息  
  74.     printf("/nPinging %s [%s] with %d bytes of data:/n",  
  75.                 pstrHost,  
  76.                 inet_ntoa(saDest.sin_addr),  
  77.                 REQ_DATASIZE);  
  78.     // 控制ping执行的次数  
  79.     for (nLoop = 0; nLoop < 4; nLoop++)  
  80.     {  
  81.         // 发送ICMP回送请求  
  82.         SendEchoRequest(rawSocket, &saDest);  
  83.         // 使用select()等待接收回送的数据  
  84.         nRet = WaitForEchoReply(rawSocket);  
  85.         if (nRet == SOCKET_ERROR)  
  86.         {  
  87.             ReportError("select()");  
  88.             break;  
  89.         }  
  90.         if (!nRet)  
  91.         {  
  92.             printf("/nTimeOut/n");  
  93.             break;  
  94.         }  
  95.         // 接收应答  
  96.         dwTimeSent = RecvEchoReply(rawSocket, &saSrc, &cTTL);  
  97.         // 计算传输时间,并输出提示信息  
  98.         dwElapsed = GetTickCount() - dwTimeSent;  
  99.         printf("/nReply from: %s: bytes=%d time=%ldms TTL=%d",  
  100.                 inet_ntoa(saSrc.sin_addr),  
  101.                 REQ_DATASIZE,  
  102.                 dwElapsed,  
  103.                 cTTL);  
  104.     }  
  105.     printf("/n");  
  106.     // 关闭套接字  
  107.     nRet = closesocket(rawSocket);  
  108.     if (nRet == SOCKET_ERROR)  
  109.     {  
  110.         ReportError("closesocket()");  
  111.     }  
  112. }  
  113. int SendEchoRequest(SOCKET s, LPSOCKADDR_IN lpstToAddr)  
  114. {  
  115.     static ECHOREQUEST echoReq; // 回送请求数据结构  
  116.     static nId = 1;     // 标识符  
  117.     static nSeq = 1;    // 序号  
  118.     int nRet;  
  119.     int i;  
  120.     // 填写回送请求信息  
  121.     echoReq.icmpHdr.Type = ICMP_ECHOREQ; // 类型  
  122.     echoReq.icmpHdr.Code = 0;            // 代码  
  123.     echoReq.icmpHdr.Checksum = 0;        // 校验和  
  124.     echoReq.icmpHdr.ID = nId++;          // 标识符  
  125.     echoReq.icmpHdr.Seq = nSeq++;        // 序号  
  126.     // 填写要发送的数据  
  127.     for (i = 0; i < REQ_DATASIZE; i++)  
  128.     {  
  129.         echoReq.cData[i] = ' ' + i;  
  130.     }  
  131.     // 保存发送时间  
  132.     echoReq.dwTime = GetTickCount();  
  133.     // 数据存入包中并计算校验和  
  134.     echoReq.icmpHdr.Checksum = in_chsum((u_short*)&echoReq, sizeof(ECHOREQUEST));  
  135.     // 发送回送请求  
  136.     nRet = sendto(s,  
  137.         (LPSTR)&echoReq,  
  138.         sizeof(ECHOREQUEST),  
  139.         0,  
  140.         (LPSOCKADDR)lpstToAddr,  
  141.         sizeof(SOCKADDR_IN));  
  142.     if (nRet == SOCKET_ERROR)  
  143.     {  
  144.         ReportError("sendto()");  
  145.     }  
  146.     return nRet;  
  147. }  
  148. DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL)  
  149. {  
  150.     ECHOREPLY echoReply;    // 回送应答数据结构  
  151.     int nRet;  
  152.     int nAddrLen = sizeof(sockaddr_in);  
  153.     // 接受回送应答  
  154.     nRet = recvfrom(s,  
  155.                     (LPSTR)&echoReply,  
  156.                     sizeof(ECHOREPLY),  
  157.                     0,  
  158.                     (LPSOCKADDR)lpsaFrom,  
  159.                     &nAddrLen);  
  160.     // 检查返回的值  
  161.     if (nRet == SOCKET_ERROR)  
  162.     {  
  163.         ReportError("recvfrom()");  
  164.     }  
  165.     *pTTL = echoReply.ipHdr.TTL;    // 取得TTL值  
  166.     return (echoReply.echoRequest.dwTime);  // 返回所用时间  
  167. }  
  168. void ReportError(LPCSTR pstrFrom)  
  169. {  
  170.     fprintf(stderr, "/n %d error: /n", WSAGetLastError());  
  171. }  
  172. // 等待套接子s是否有数据可读  
  173. int WaitForEchoReply(SOCKET s)  
  174. {  
  175.     timeval Timeout;  
  176.     fd_set readfds;  
  177.     readfds.fd_count = 1;  
  178.     readfds.fd_array[0] = s;  
  179.     Timeout.tv_sec = 5;  
  180.     Timeout.tv_usec = 0;  
  181.     return (select(1, &readfds, NULL, NULL, &Timeout));  
  182. }  
  183. u_short in_chsum(u_short *addr, int len)  
  184. {  
  185.     register int nLeft = len;  
  186.     register u_short *w = addr;  
  187.     register u_short answer;  
  188.     register int sum = 0;  
  189.     while (nLeft > 1)  
  190.     {  
  191.         sum += *w++;  
  192.         nLeft -= 2;  
  193.     }  
  194.     if (nLeft == 1)  
  195.     {  
  196.         u_short u = 0;  
  197.         *(u_char*)(&u) = *(u_char*)w;  
  198.         sum += u;  
  199.     }  
  200.     sum = (sum >> 16) + (sum & 0xffff);  
  201.     sum += (sum >> 16);  
  202.     answer = ~sum;  
  203.     return (answer);  
  204. }  


  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值