ping程序的实现

最近在看些Windows下网络编程问题。看到原始套接字的使用,于是看了Ping程序的功能实现。

大部分人用ping命令只是作为查看另一个系统的网络连接是否正常的一种简单方法。这里我介绍下在Windows下实现ping程序的两种方法。

一是使用原始套接字的方法:

代码如下:

  1. #include <Winsock2.h>
  2. #pragma comment(lib, "ws2_32.lib")
  3. #pragma comment(lib, "Iphlpapi.lib")
  4. typedef struct icmp_hdr
  5. {
  6.     unsigned char icmp_type;
  7.     unsigned char icmp_code;
  8.     unsigned short icmp_checksum;
  9.     //回显头
  10.     unsigned short icmp_id;
  11.     unsigned short icmp_sequence;
  12.     unsigned long icmp_timestamp;
  13. }ICMP_HDR,*PICMP_HDR;
  14. typedef struct _IPHeader        // 20字节的IP头
  15. {
  16.     UCHAR     iphVerLen;      // 版本号和头长度(各占4位)
  17.     UCHAR     ipTOS;          // 服务类型 
  18.     USHORT    ipLength;       // 封包总长度,即整个IP报的长度
  19.     USHORT    ipID;           // 封包标识,惟一标识发送的每一个数据报
  20.     USHORT    ipFlags;        // 标志
  21.     UCHAR     ipTTL;          // 生存时间,就是TTL
  22.     UCHAR     ipProtocol;     // 协议,可能是TCP、UDP、ICMP等
  23.     USHORT    ipChecksum;     // 校验和
  24.     ULONG     ipSource;       // 源IP地址
  25.     ULONG     ipDestination;  // 目标IP地址
  26. } IPHeader, *PIPHeader; 
  27. USHORT checksum(USHORT* buffer, int size)
  28. {
  29.     unsigned long cksum = 0;
  30.     while(size > 1 )
  31.     {
  32.         cksum += *buffer++;
  33.         size -=sizeof(USHORT);
  34.     }
  35.     if(size)
  36.     {
  37.         cksum += *(UCHAR*)buffer;
  38.     }
  39.     cksum = (cksum>>16)+(cksum & 0xffff);
  40.     cksum += (cksum >> 16);
  41.     return (USHORT)(~cksum);
  42. }
  43. BOOL SetTimeout(SOCKET s, int nTime, BOOL bRecv)
  44. {
  45.     int ret = ::setsockopt(s, SOL_SOCKET, 
  46.         bRecv ? SO_RCVTIMEO : SO_SNDTIMEO, (char*)&nTime, sizeof(nTime));
  47.     return ret != SOCKET_ERROR;
  48. }
  49. int main(int argc, char* argv[])
  50. {
  51.     if ( argc < 2)
  52.     {
  53.         printf("Usage: Ping IP address./n");
  54.         return -1;
  55.     }
  56.     char szDestIp[20];
  57.     strcpy_s(szDestIp, argv[1]);
  58.     //初始化套接字
  59.     WSADATA wsaData;
  60.     int wsaset = WSAStartup(0x101, &wsaData);
  61.     SOCKET sRaw = ::socket(AF_INET, SOCK_RAW,IPPROTO_ICMP);
  62.     int a = ::GetLastError();
  63.     SetTimeout(sRaw, 1000, TRUE);
  64.     SOCKADDR_IN dest;
  65.     dest.sin_family = AF_INET;
  66.     dest.sin_port = htons(0);
  67.     dest.sin_addr.S_un.S_addr = inet_addr(szDestIp);
  68.     //创建ICMP封包
  69.     char buff[sizeof(ICMP_HDR)+32];
  70.     ICMP_HDR* pIcmp = (ICMP_HDR*)buff;
  71.     //填写ICMP包
  72.     pIcmp->icmp_type = 8;
  73.     pIcmp->icmp_code = 0;
  74.     pIcmp->icmp_id = (USHORT)::GetCurrentProcessId();
  75.     pIcmp->icmp_checksum = 0;
  76.     pIcmp->icmp_sequence = 0;
  77.     //填充数据
  78.     memset(&buff[sizeof(ICMP_HDR)],'E', 32);
  79.     //开始发送和接收ICMP封包
  80.     USHORT nSeq = 0;
  81.     char recvBuf[1024];
  82.     SOCKADDR_IN from;
  83.     int nLen = sizeof(from);
  84.     while( TRUE )
  85.     {
  86.         static int nCount = 0;
  87.         int nRet;
  88.         if(nCount++ == 4)
  89.             break;
  90.         pIcmp->icmp_checksum = 0;
  91.         pIcmp->icmp_timestamp = ::GetTickCount();
  92.         pIcmp->icmp_sequence = nSeq++;
  93.         pIcmp->icmp_checksum = checksum((USHORT*)buff, sizeof(ICMP_HDR) + 32);
  94.         //发送
  95.         nRet = ::sendto(sRaw, buff, sizeof(ICMP_HDR) + 32, 0, (SOCKADDR *)&dest, sizeof(dest));
  96.         if(nRet == SOCKET_ERROR)
  97.         {
  98.             printf(" sendto() failed: %d /n", ::WSAGetLastError());
  99.             return -1;
  100.         }
  101.         nRet = ::recvfrom(sRaw, recvBuf, 1024, 0, (sockaddr*)&from, &nLen);
  102.         if(nRet == SOCKET_ERROR)
  103.         {
  104.             if(::WSAGetLastError() == WSAETIMEDOUT)
  105.             {
  106.                 printf(" timed out/n");
  107.                 continue;
  108.             }
  109.             printf(" recvfrom() failed: %d/n", ::WSAGetLastError());
  110.             return -1;
  111.         }
  112.         // 下面开始解析接收到的ICMP封包
  113.         int nTick = ::GetTickCount();
  114.         if(nRet < sizeof(IPHeader) + sizeof(ICMP_HDR))
  115.         {
  116.             printf(" Too few bytes from %s /n", ::inet_ntoa(from.sin_addr));
  117.         }
  118.         // 接收到的数据中包含IP头,IP头大小为20个字节,所以加20得到ICMP头
  119.         ICMP_HDR* pRecvIcmp = (ICMP_HDR*)(recvBuf + 20); // (ICMP_HDR*)(recvBuf + sizeof(IPHeader));
  120.         if(pRecvIcmp->icmp_type != 0)   // 回显
  121.         {
  122.             printf(" nonecho type %d recvd /n", pRecvIcmp->icmp_type);
  123.             return -1;
  124.         }
  125.         if(pRecvIcmp->icmp_id != ::GetCurrentProcessId())
  126.         {
  127.             printf(" someone else's packet! /n");
  128.             return -1;
  129.         }
  130.         printf(" %d bytes from %s:", nRet, inet_ntoa(from.sin_addr));
  131.         printf(" icmp_seq = %d. ", pRecvIcmp->icmp_sequence);
  132.         printf(" time: %d ms", nTick  - pRecvIcmp->icmp_timestamp);
  133.         printf(" /n");
  134.         ::Sleep(1000);
  135.     }
  136.     system("pause");
  137.     return 0;
  138. }

这是使用了原始套接字,实现发送ICMP数据包,实现ping程序。

在Linux中的Ping使用了这种方式。

这里是一个比较完整的关于Ping程序介绍的文章:http://www.ibm.com/developerworks/cn/linux/network/ping/index.html

这是Windows下实现的Ping程序:http://blog.csdn.net/tancfjob/archive/2008/05/07/2408978.aspx

 

但是在使用Windows下实现Ping的时候发现使用原始套接字需要管理员权限,但是Windows下使用ping程序并不需要使用管理员权限。感谢shadowstar 提示了我使用IcmpSendEcho来实现功能。

下面是使用IcmpSendEcho的示例代码:

 

  1. #pragma comment(lib, "ws2_32.lib")
  2. #pragma comment(lib, "Iphlpapi.lib")
  3. int __cdecl main(int argc, char **argv)  {
  4.     // Declare and initialize variables
  5.     HANDLE hIcmpFile;
  6.     unsigned long ipaddr = INADDR_NONE;
  7.     DWORD dwRetVal = 0;
  8.     char SendData[] = "Data Buffer";
  9.     LPVOID ReplyBuffer = NULL;
  10.     DWORD ReplySize = 0;
  11.     // Validate the parameters
  12.     if (argc != 2) {
  13.         printf("usage: %s IP address/n", argv[0]);
  14.         return 1;
  15.     }
  16.     ipaddr = inet_addr(argv[1]);
  17.     if (ipaddr == INADDR_NONE) {
  18.         printf("usage: %s IP address/n", argv[0]);
  19.         return 1;
  20.     }
  21.     hIcmpFile = IcmpCreateFile();
  22.     if (hIcmpFile == INVALID_HANDLE_VALUE) {
  23.         printf("/tUnable to open handle./n");
  24.         printf("IcmpCreatefile returned error: %ld/n", GetLastError() );
  25.         return 1;
  26.     }    
  27.     ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData);
  28.     ReplyBuffer = (VOID*) malloc(ReplySize);
  29.     if (ReplyBuffer == NULL) {
  30.         printf("/tUnable to allocate memory/n");
  31.         return 1;
  32.     }    
  33.     dwRetVal = IcmpSendEcho(hIcmpFile, ipaddr, SendData, sizeof(SendData), 
  34.         NULL, ReplyBuffer, ReplySize, 1000);
  35.     if (dwRetVal != 0) {
  36.         PICMP_ECHO_REPLY pEchoReply = (PICMP_ECHO_REPLY)ReplyBuffer;
  37.         struct in_addr ReplyAddr;
  38.         ReplyAddr.S_un.S_addr = pEchoReply->Address;
  39.         printf("/tSent icmp message to %s/n", argv[1]);
  40.         if (dwRetVal > 1) {
  41.             printf("/tReceived %ld icmp message responses/n", dwRetVal);
  42.             printf("/tInformation from the first response:/n"); 
  43.         }    
  44.         else {    
  45.             printf("/tReceived %ld icmp message response/n", dwRetVal);
  46.             printf("/tInformation from this response:/n"); 
  47.         }    
  48.         printf("/t  Received from %s/n", inet_ntoa( ReplyAddr ) );
  49.         printf("/t  Status = %ld/n"
  50.             pEchoReply->Status);
  51.         printf("/t  Roundtrip time = %ld milliseconds/n"
  52.             pEchoReply->RoundTripTime);
  53.     }
  54.     else {
  55.         printf("/tCall to IcmpSendEcho failed./n");
  56.         printf("/tIcmpSendEcho returned error: %ld/n", GetLastError() );
  57.         return 1;
  58.     }
  59.     return 0;

此段代码需要ws2_32.lib和Iphlpapi.lib,包含头文件iphlpapi.h和icmpapi.h

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值