Socket编程之ping程序的实现

大家都知道,ping程序是基于ICMP回显请求和应答报文的,通过IP数据报的选项字段可以达到记录路由的效果,老实说,在刚刚拿到这个课题时,毫无头绪,根本不知道如何下手,因为之前根本就没有从事过Socket网络编程,但是这又是必须的,没办法,凭借之前对孙鑫老师MFC视频教程里的网络编程的基础知识,以及计算机网络、TCP/IP详解等课本的知识,最终结合网上的参考代码,加上自己的理解和调试,解决众多BUG问题,可以说是对网上的代码进行了一定层次的优化。下面附上代码,希望对大家有所帮助,也希望能够和我交流思想。

头文件部分:
/*导入库文件*/
#pragma comment( lib, "ws2_32.lib" )
/*加载头文件*/
#include //创建套接字头文件
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

/*定义常量*/
/*表示要记录路由*/
#define IP_RECORD_ROUTE  0x7
/*默认数据报大小*/
#define DEF_PACKET_SIZE  32 
/*最大的ICMP数据报大小*/
#define MAX_PACKET       1024    
/*最大IP头长度*/
#define MAX_IP_HDR_SIZE  60 
/*ICMP报文类型,回显请求*/      
#define ICMP_ECHO        8
/*ICMP报文类型,回显应答*/ 
#define ICMP_ECHOREPLY   0
/*最小的ICMP数据报大小*/
#define ICMP_MIN         8

/*自定义函数原型*/
void Init_Ping();
void UserHelp();
void GetArgments(int argc, char** argv); 
USHORT CheckSum(USHORT *buffer, int size);
void FillICMPData(char *icmp_data, int datasize);
void FreeRes();
void DecodeIPOptions(char *buf, int bytes);    //ip报文各字段解析
void DecodeICMPHeader(char *buf, int bytes, SOCKADDR_IN* from);  //icmp报文首部解析翻译
void PingTest(int timeout);   //ping命令测试程序
void PingInput(int &argc,char *argv[]);

/*IP报头字段数据结构*/
typedef struct _iphdr       //声明定义一个结构体,用来表示ip首部
{
    unsigned int   h_len:4;        /*IP报头长度 位域为4(虽然为int32位的,但只用4位)*/
    unsigned int   version:4;      /*IP的版本号  4*/
    unsigned char  tos;            /*服务类型 8*/
    unsigned short total_len;      /*数据报总长度 16*/
    unsigned short ident;          /*惟一的标识符 16*/
    unsigned short frag_flags;     /*分段标志 16*/
    unsigned char  ttl;            /*生存期  8*/
    unsigned char  proto;          /*协议类型(TCP、UDP等) 8*/
    unsigned short checksum;       /*校验和  16*/
    unsigned int   sourceIP;       /*源IP地址   32*/
    unsigned int   destIP;         /*目的IP地址 32*/
} IpHeader;   


/*ICMP报头字段数据结构*/
typedef struct _icmphdr      //同上,这里表示icmp报文的首部:用数据结构表示
{
    BYTE   i_type;                 /*ICMP报文类型   8位*/                  
    BYTE   i_code;                 /*该类型中的代码号  8位*/
    USHORT i_cksum;                /*校验和 16位*/ 
    USHORT i_id;                   /*惟一的标识符  16位*/
    USHORT i_seq;                  /*序列号   16位*/
    ULONG  timestamp;              /*时间戳   32位*/
} IcmpHeader;

/*IP选项头字段数据结构*/
typedef struct _ipoptionhdr    //表示ip首部中的可选字段
{
   
    unsigned char  code;           /*选项类型    8位*/
    unsigned char  len;            /*选项头长度   8位*/
    unsigned char  ptr;            /*地址偏移长度  8位*/
    unsigned long  addr[9];        /*记录的IP地址列表 32位*/   
} IpOptionHeader;

SOCKET m_socket;                 
IpOptionHeader IpOption;         
SOCKADDR_IN DestAddr;    
SOCKADDR_IN SourceAddr;  
char *icmp_data;   
char *recvbuf;      
USHORT seq_no ;        //typedef unsigned short USHORT;
char *lpdest;           //lpdest表示目的地址的指针
int datasize;     
bool RecordFlag;     
double PacketNum;    
bool InputTrue;
int Rcount;
源文件部分:
#include"ping.h"  

/*初始化变量*/   
void Init_Ping()          
{
  WSADATA wsaData;         
  icmp_data = NULL;
  seq_no = 0;           
  recvbuf = NULL;           
  RecordFlag = FALSE;            //记录标志为false
  lpdest = NULL;                  
  datasize = DEF_PACKET_SIZE;   
  PacketNum = 4;            
  InputTrue = FALSE;	
  Rcount=9;

  if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)  //加载套接字库,进行套接字库的版本协商,确定哪一个版本的套接字库
    {
        cout<<"WSAStartup() failed: "< [data size]"<调用用户使用帮助
		return ;
    }
    for(i = 1; i < argc; i++)    //参数的个数>1,即不止一个"ping"字符串
    {
        len = strlen(argv[i]);  
        if (argv[i][0] == '-')        //是否为选项字段
        {
            if(isdigit(argv[i][1]))
    		{
                PacketNum = 0;                   
                for(j=len-1,exp=0;j>=1;j--,exp++)        //将输入的记录条数转为10进制数
                    PacketNum += ((double)(argv[i][j]-48))*pow(10,exp);   
    		}
        	else
    		{
                switch (tolower(argv[i][1]))       //转为小写字母
        		{
                    case 'r':                //表明要记录路由
                        RecordFlag = TRUE;      
            			break;
                	default:                 //输入不符合规范
						InputTrue=true;
            			UserHelp();
						return ;
        		}
    		}
        }
        /*参数是数据报大小或者IP地址*/
        else if (isdigit(argv[i][0]))   //判断是否为数字
    	{
            for(m=1;m 1) 
    {
        cksum += *buffer++;
        size -= sizeof(USHORT);    //无符号短整型占两个字节,即16位
    }
    if (size) 
    {
        cksum += *(UCHAR*)buffer;
    }
	while(cksum>>16)
            cksum = (cksum>>16) + (cksum & 0xffff);  //两次就够了,就能保证高16位为全0	
    return (USHORT)(~cksum);                         //最后的二进制反码和再取反赋值给检验和字段
}

/*填充ICMP数据报字段*/
void FillICMPData(char *icmp_data, int datasize)
{
    IcmpHeader *icmp_hdr = NULL;
    char      *datapart = NULL;
    icmp_hdr = (IcmpHeader*)icmp_data;
    icmp_hdr->i_type = ICMP_ECHO;            //为数字8,代表回显请求 	
    icmp_hdr->i_code = 0;                  
    icmp_hdr->i_id = (USHORT)GetCurrentProcessId();  //获取当前进程的PID作为标识符        
	icmp_hdr->i_cksum = 0;  
    icmp_hdr->i_seq = 0;	
    datapart = icmp_data + sizeof(IcmpHeader);           //icmp数据包的数据部分
    memset(datapart,'0',datasize-sizeof(IcmpHeader));    //字符填充或者是0比特填充,此处0比特填充
}

/*释放资源*/
void FreeRes()
{
    if (m_socket != INVALID_SOCKET)      //只要套接字不等于无效的套接字,就关闭套接字,以释放为套接字分配的资源
        closesocket(m_socket);       
    HeapFree(GetProcessHeap(), 0, recvbuf);     //释放堆分配的内存     
    HeapFree(GetProcessHeap(), 0, icmp_data);
    WSACleanup();           //终止对winsocket套接字库的使用
    return ;
}

/*解读IP选项头*/
void DecodeIPOptions(char *buf, int bytes)   
{
    IpOptionHeader *ipopt = NULL;
    IN_ADDR inaddr;
    int i;
    HOSTENT *host = NULL;
    /*获取路由信息的地址入口*/
    ipopt = (IpOptionHeader *)(buf + 19);   //因为如果设置了ip首部选项字段的话,buf是指向ip数据报首字节的
                                            //移动20字节,不久指向了ip option吗
    cout<<"路由: ";       
	int temp=(Rcount<=(ipopt->ptr/4))?Rcount:(ipopt->ptr/4);
    for(i = 0; i < temp; i++)                                        //ptr是地址偏移长度,4字节一移动,因为ip地址为4个字节
    {
        inaddr.S_un.S_addr = ipopt->addr[i];        //选项字段的ip地址赋给了地址结构体变量
        if (i != 0)
            cout<<"      ";
        /*根据IP地址获取主机名*/
        host = gethostbyaddr((char *)&inaddr.S_un.S_addr,sizeof(inaddr.S_un.S_addr), AF_INET);
        if (host)     //获取成功
            cout<<"("<h_name;//inet_ntoa通过接收in_addr结构体返回点分十进制的ip地址 host结构体存储了主机的信息
        else          //获取失败
            cout<<"("<";
		cout<h_len * 4;         //ip的首部长度字段乘以4个字节就是ip首部的总长度
    tick = GetTickCount();          

	/*如果IP报头的长度为最大长度(基本长度是20字节),则认为有IP选项,需要解读IP选项*/
    if ((iphdrlen == MAX_IP_HDR_SIZE) && (!icmpcount))   //ip首部的最大长度为60字节,15*4=60
                                            
        DecodeIPOptions(buf, bytes);    //解读ip首部选项字段(路由信息)

    if (bytes < iphdrlen + ICMP_MIN)    /*如果读取的数据太小 iphdrlen=20 icmp_min=8*/
    {
        cout<<"Too few bytes from "<sin_addr)<i_type != ICMP_ECHOREPLY)       
    {
        printf("non-echo type of %d is recvd!!!\n",icmphdr->i_type);     
        return;
    }
    /*核实收到的ID号和发送的是否一致*/
    if (icmphdr->i_id != (USHORT)GetCurrentProcessId())      //GetCurrentProcessId返回当前进程的pid
    {
        cout<<"someone else's packet!"<sin_addr)<<":";   //字节数和ip地址
    cout<<" icmp_seq = "<i_seq<<". ";                    //序号
    cout<<" time: "<timestamp<<" ms"<h_addr, hp->h_length);
            /*将获取到的地址族值赋给目的地地址中的相应字段*/
            DestAddr.sin_family = hp->h_addrtype;
            cout<<"DestAddr.sin_addr = "<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);
        /*基于UDP开始发送ICMP请求*/                                //设定目的套接字的地址信息,地址结构体的长度
        writeNum = sendto(m_socket, icmp_data, datasize, 0,(struct sockaddr*)&DestAddr, sizeof(DestAddr));
                                           //设置0那个位置的值,将会影响sendto函数调用的行为
		/*如果发送不成功*/
        if (writeNum == SOCKET_ERROR)
        {
            if (WSAGetLastError() == WSAETIMEDOUT)   //发送超时
            {
                cout<<"timed out"<0)
	{
		cout<";
		Init_Ping();  
		PingInput(argc,argv);
		GetArgments(argc, argv); 
		if(InputTrue)
			continue;        
		if(!lpdest)
		{
			cout<<"没有指定目的地址,请重新输入!!!"<

程序运行测试:

测试路由功能:

Socket编程之ping程序的实现_第1张图片

测试路由和ping功能:

Socket编程之ping程序的实现_第2张图片

补充:部分网络环境可能禁止 记录路由功能,同时,我们的程序是 基于Visual C++ 6.0开发环境的,其他开发环境需要做相应头文件包括的更改;程序的运行 需要提供 "管理员权限" ;否则会报 1013 的错误。

//写的不好的话,还请大家多给点建议。

你可能感兴趣的:(课程设计)