2012年7月5日星期四

ping.cpp


#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "WS2_32") // 链接到WS2_32.lib

typedef struct _IPHeader  // 20字节的IP头
{
  UCHAR     iphVerLen;      // 版本号和头长度(各占4位)
  UCHAR     ipTOS;          // 服务类型 
  USHORT    ipLength;       // 封包总长度,即整个IP报的长度
  USHORT    ipID;           // 封包标识,惟一标识发送的每一个数据报
  USHORT    ipFlags;        // 标志
  UCHAR     ipTTL;          // 生存时间,就是TTL
  UCHAR     ipProtocol;     // 协议,可能是TCP、UDP、ICMP等
  USHORT    ipChecksum;     // 校验和
  ULONG     ipSource;       // 源IP地址
  ULONG     ipDestination;  // 目标IP地址
} IPHeader, *PIPHeader; 

typedef struct icmp_hdr
{
  unsigned char   icmp_type;// 消息类型
  unsigned char   icmp_code;// 代码
  unsigned short  icmp_checksum;// 校验和
  // 下面是回显头
  unsigned short  icmp_id;    // 用来惟一标识此请求的ID号,通常设置为进程ID
  unsigned short  icmp_sequence;// 序列号
  unsigned long   icmp_timestamp; // 时间戳
} ICMP_HDR, *PICMP_HDR;
//////////////////////////////////////////////////////////////////////////////////
// 校验和的计算
// 以16位的字为单位将缓冲区的内容相加,如果缓冲区长度为奇数,
// 则再加上一个字节。它们的和存入一个32位的双字中
USHORT checksum(USHORT * buff, int size)
{
  unsigned long cksum = 0;
  while(size>1)
  {
    cksum += *buff++;
    size -= sizeof(USHORT);
  }
  // 是奇数
  if(size)
  {
    cksum += *(UCHAR*)buff;
  }
  // 将32位的chsum高16位和低16位相加,然后取反
  cksum = (cksum >> 16) + (cksum & 0xffff);
  cksum += (cksum >> 16);
  return (USHORT)(~cksum);
}

class CInitSock  
{
public:
  CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
  { // 初始化WS2_32.dll
    WSADATA wsaData;
    WORD sockVersion = MAKEWORD(minorVer, majorVer);
    if(WSAStartup(sockVersion, &wsaData) != 0)
    {
      exit(0);
    }
  }
  ~CInitSock()
  { 
    WSACleanup(); 
  }
};

CInitSock theSock;//全局类,开始于main之前,结束于main之后。
/////////////////////////////////////////////////////////////////////////////////////
int main()
{
  SOCKET sRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);// 创建原始套节字

  int x = 100;// 设置接收超时
  setsockopt(sRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&x, sizeof(x));
  
  SOCKADDR_IN dest;// 设置目的地址
  dest.sin_family = AF_INET;
  dest.sin_port = htons(0);
  dest.sin_addr.S_un.S_addr = inet_addr("202.102.75.147");// 要Ping的IP地址 202.102.75.147

  char buff[sizeof(ICMP_HDR) /*+ 32*/] = {0}; //此处的32和下面的填充用的32,这两个数要一样。大小不超过65536  USHORT
  ICMP_HDR * pIcmp = (ICMP_HDR*)buff;
  pIcmp->icmp_type = 8;// 请求一个ICMP回显
  pIcmp->icmp_id = (USHORT)GetCurrentProcessId();//id不可以为0.
  //memset(&buff[sizeof(ICMP_HDR)], 'E', 32);// 填充数据部分,可以为任意
  
  for (int i = 0;i<4;i++) 
  {
    pIcmp->icmp_checksum = 0; //此行注视掉,只有第一个成功。所以不可以注视掉。
    pIcmp->icmp_timestamp = GetTickCount();
    pIcmp->icmp_sequence = i;
    pIcmp->icmp_checksum = checksum((USHORT*)buff, sizeof(ICMP_HDR) /*+ 32*/);//此处的32和下面的32加上,是发送64字节的。

    sendto(sRaw, buff, sizeof(ICMP_HDR) /*+ 32*/ , 0, (SOCKADDR *)&dest, sizeof(dest));//发送ICMP封包 

    char recvBuf[1024];//接收ICMP封包 
    SOCKADDR_IN from;
    int nLen = sizeof(from);
    int nRet = recvfrom(sRaw, recvBuf, 1024, 0, (sockaddr*)&from, &nLen);
    if(nRet == SOCKET_ERROR)
    {//处理失败。
      if(WSAGetLastError() == WSAETIMEDOUT)
      {
        printf(" timed out\n");
        continue;
      }
      printf(" recvfrom() failed: %d\n", ::WSAGetLastError());
      return -1;
    }

    printf("Reply from %s: bytes=%d", inet_ntoa(from.sin_addr), nRet);//显示信息。
    int nTick = GetTickCount();    
    ICMP_HDR * pRecvIcmp = (ICMP_HDR*)(recvBuf + 20); // 接收到的数据中包含IP头,所以加20得到ICMP头 sizeof(IPHeader) == 0,
    printf(" time=%dms", nTick - pRecvIcmp->icmp_timestamp); //等于零就是小于1.
    IPHeader * ih = (IPHeader *)recvBuf;
    printf(" TTL=%d\n", ih->ipTTL);
    Sleep(100);
  }

  return 0;
}
//made at 2011.12.27
//还有添加路由等功能的,没有做。

没有评论:

发表评论