(1)了解探测远程主机是否可达的方法。
(2)了解windows网络编程的基本步骤。
(3)了解TCP/IP协议及ICMP协议。
(4)掌握ping命令的思想。
(5)掌握Windows Socket API的使用方法。
原理
ICMP:互联网控制报文协议,提供一个特殊的报文在互联网的结点之间进行监测和控制,测试目的主机是否可达,因此可模拟ICMP的工作方式来编制程序。
首先通过生成一个ICMP“回应请求”,然后将其定向至打算查询的目标主机,远程主机会拦截这个请求,然后生成一条回应答复消息,再通过网络回传给我们。若是不能抵达目标主机,则会生成相应的错误消息。
具体步骤如下:
1)创建类型为SOCK_RAW的一个套接字,同时设定协议IPPROTO_ICMP。
2)创建并初始化ICMP头。
3)调用sendto将ICMP请求发给远程主机。
4)调用recvfrom以接收ICMP响应。
其流程图如下:
// test ping.cpp : Defines the entry point for the console application.
////#include "winPing.h"
#include <winsock2.h>
#include <iostream>
#pragma comment( lib, "ws2_32.lib")
#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0
#define ICMP_MIN 8
#define DEF_PACKET_SIZE 32
#define DEF_PACKET_NUMBER 4
#define MAX_PACKET 1024
const char *Ipdest;
int datasize;
static int icmpcount=0;
using namespace std;
//IP头(20字节)
struct IPHEAD
{
unsigned int headlength:4;
unsigned int version:4;
unsigned char tos;
unsigned short totallength;
unsigned short ip_id;
unsigned short frag_and_flags;
unsigned char ttl;
unsigned char protocal;
unsigned short ip_checksum;
unsigned int sourceIP;
unsigned int destIP;
};
struct ICMPHEAD
{
unsigned char type;
unsigned char code;
unsigned short icmp_checksum;
unsigned short icmp_id;
unsigned short seq;
unsigned long timestamp;
};
void Fill_icmp_data(char * icmp_data, int datasize)
{
ICMPHEAD *icmphead=NULL;
char *datapart=NULL;
icmphead = (ICMPHEAD*)icmp_data;
icmphead->type=ICMP_ECHO;
//ICMP_ECHO=8
icmphead->code = 0;
icmphead->icmp_checksum = 0;
icmphead->icmp_id = (unsigned short)GetCurrentProcessId();
datapart = icmp_data + sizeof(ICMPHEAD);//具体内容的首地址指针//初始化数据包内容部分
for(int i=0;i<(datasize-sizeof(ICMPHEAD));i++,datapart++)
*datapart=0;
}
//DecodeICMPHEAD函数:接受数据时从IP头中获取ICMP消息
void DecodeICMPHEAD(char *recvbuf, int bread,sockaddr_in *from)
{
IPHEAD *iphead=NULL;
ICMPHEAD *icmphead=NULL;
DWORD tick;
unsigned short iphdrlen;
iphead = (IPHEAD *)recvbuf;
//32位字的个数乘以4即字节个数
iphdrlen = (iphead->headlength) * 4 ;
//获取操作系统启动至今所经过的时间(ms)
tick=GetTickCount();
if (bread < (iphdrlen + ICMP_MIN))
{
cout<<"Too few bytes from: "<<inet_ntoa(from->sin_addr);
cout<<endl;
}
icmphead = (ICMPHEAD*)(recvbuf + iphdrlen);
//ICMP消息始于IP头之后//若ICMP消息类型并非查询,则输出相应信息类型
if (icmphead->type!=ICMP_ECHOREPLY){
cout<<"nonecho type "<<int(icmphead->type)<<" received";
cout<<endl;
}
//确认收到的应答ICMP消息是否是对发送出去的消息的回应//若其表示与当前进程不符,则输出出错信息
if (icmphead->icmp_id != (unsigned short)GetCurrentProcessId())
{
cout<<"It's someone else's packet!"<<endl;
return;
}
//输出收到信息的内容:主机地址,icmp消息序号,回应时间,存活时间
cout<<"Reply from "<<inet_ntoa(from->sin_addr);
cout<<" bytes="<<bread-iphdrlen;
cout<<" time: "<<GetTickCount()-icmphead->timestamp<<" ms ";
cout<<" seq="<<icmphead->seq;
cout<<endl;
icmpcount++;
}
//Checksum函数:用于求ICMP首部校验和
unsigned short Checksum(unsigned short *buffer, int size)
{
unsigned long cksum=0;//设校验和初值为0
while(size >1)
{
cksum+=*buffer++;//求各个16位数字之和
size -=sizeof(unsigned short);
}
if(size)
{
cksum += *(unsigned char*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);//高位与低位相加
cksum += (cksum >>16);//加上进位
return (unsigned short)(~cksum);//取反得到校验和
}
//Usage函数:表示程序的功能
void Usage(const char *progname)
{
cout<<"Usage:"<<progname<<" <host>"<<endl;
cout<<" host remote machine to Ping"<<endl;
cout<<endl;
}
//ValidateArgs函数:用于给目的地址和数据包大小赋值
void ValidateArgs(int argc,const char **argv)
{
datasize=DEF_PACKET_SIZE;//数据包大小为DEF_PACKET_SIZE=32
if(argc<2)
{
Usage(argv[0]);//只输入一个参数:程序名
Ipdest="192.168.10.210";//默认目的地址
}
else if(argc==2)
Ipdest=argv[1];//第二个参数为目的地址
}
int testPing(string strDevice)
{
if(strDevice.size()<1)
strDevice ="192.168.10.210";
const char *argv=strDevice.c_str();
WSADATA wsaData;
SOCKET sockRaw=INVALID_SOCKET;
sockaddr_in dest,from;
int destlen=sizeof(dest),fromlen=sizeof(from),bwrote,bread;
char *icmp_data=NULL,*recvbuf=NULL;
struct hostent *hp=NULL;
unsigned short seq=0;
if (WSAStartup(MAKEWORD(2,2),&wsaData)!= 0)
{
cout<<"WSAStartup failed:"<<GetLastError();
cout<<endl;
return -1;
}
int argc =1;
ValidateArgs(argc,&argv);
sockRaw = WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED);
if(sockRaw == INVALID_SOCKET)
{
cout<<"WSASocket() failed:"<<GetLastError();
cout<<endl;
return -1;
}
//设定发送数据时最长等待时间timeout=1000ms
int timeout=1000;
bread=setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(timeout));
if(bread==SOCKET_ERROR)
{
cout<<"setsockopt(SO_RCVTIMEO)failed:"<<WSAGetLastError();
cout<<endl;
return -1;
}
//设定接收数据时最长等待时间timeout=1000ms
timeout=1000;
bread=setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,sizeof(timeout));
if(bread==SOCKET_ERROR)
{
cout<<"setsockopt(SO_SNDTIMEO)failed:"<<WSAGetLastError();
cout<<endl;
return -1;
}
//解析目的地址的名称
dest.sin_family=AF_INET;
if(dest.sin_addr.s_addr=inet_addr(Ipdest))
{
//inet_addr将IP地址转换成网络地址
if((hp=gethostbyname(Ipdest))!=NULL)
{
//gethostbyname主机信息
memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length); //复制内存
dest.sin_family=hp->h_addrtype;
cout<<"dest.sin_addr="<<inet_ntoa(dest.sin_addr);
cout<<endl<<'\n';
}
else
{
cout<<"gethostbyname()failed:"<<WSAGetLastError();
cout<<endl;
return -1;
}
}
//建立一个ICMP数据包
datasize+=sizeof(ICMPHEAD);
//为要发送的数据申请内存空间,最大值为1024
icmp_data=new char[MAX_PACKET];
//为要接收的数据申请内存空间,最大值为1024
recvbuf=new char[MAX_PACKET];
//分配内存失败,输出提示信息
if(!icmp_data)
{
cout<<"new char[] failed:"<<WSAGetLastError();
cout<<endl;
return -1;
}
//将ICMP信息所在空间清零
memset(icmp_data,0,MAX_PACKET);
//设置ICMP信息头部
Fill_icmp_data(icmp_data,datasize);
//发送和接收数据,循环四次
for(int i=0;i<4;i++)
{
//ICMP信息封装
((ICMPHEAD*)icmp_data)->icmp_checksum=0;
((ICMPHEAD*)icmp_data)->timestamp =GetTickCount();
((ICMPHEAD*)icmp_data)->seq=seq++;
((ICMPHEAD*)icmp_data)->icmp_checksum =Checksum((unsigned short*)icmp_data,datasize);
//发送数据
bwrote = sendto(sockRaw,icmp_data,datasize,0,(sockaddr*)&dest,destlen);
if (bwrote == SOCKET_ERROR)
{
if (WSAGetLastError() == WSAETIMEDOUT)
{
cout<<"Request timed out."<<endl;
continue;
}
else
{
cout<<"sendto( )failed:"<<WSAGetLastError();
cout<<endl;
return -1;
}
}
//显示实际发送的字节数
cout<<"Pinging "<<inet_ntoa(dest.sin_addr)<<" with ";
cout<<bwrote<<" bytes of data:";
cout<<endl;
//接收数据
bread=recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(sockaddr*)&from,&fromlen);
if (bread == SOCKET_ERROR)
{
if (WSAGetLastError() == WSAETIMEDOUT)
{
cout<<"Request timed out."<<endl;
continue;
}
cout<<"recvfrom() failed: "<<WSAGetLastError();
cout<<endl;
return -1;
}
//打开接收到的数据,从中提取ICMP头信息
DecodeICMPHEAD(recvbuf,bread,&from);
//等待1s钟
Sleep(1000);
}
//输出发送的数据包数、接收的数据包数和丢失的数据包数
cout<<endl;
cout<<"Ping statistics for "<<inet_ntoa(dest.sin_addr)<<":"<<endl;
cout<<" Packets:sent=4,";
cout<<"Received="<<icmpcount;
cout<<",Lost="<<DEF_PACKET_NUMBER-icmpcount;
cout<<"("<<((float)(DEF_PACKET_NUMBER-icmpcount)/DEF_PACKET_NUMBER)*100;
cout<<"%loss)";
cout<<endl;
//释放资源,关闭winsock
if(sockRaw!=INVALID_SOCKET)
closesocket(sockRaw);
delete[] recvbuf;
delete[] icmp_data;
WSACleanup();
return 0;
}