C语言编程大讲坛.doc_第1页
C语言编程大讲坛.doc_第2页
C语言编程大讲坛.doc_第3页
C语言编程大讲坛.doc_第4页
C语言编程大讲坛.doc_第5页
已阅读5页,还剩13页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第19课 Ping网络项目设计CHAPTER19第19课 Ping网络项目设计Ping命令是使用最为频繁的一个网络测试命令之一,它能够测试一个主机到另外一个主机间的网络是否连通。在微软的Windows系统内自带了一个Ping命令工具,它可以用于实现网络方面的多个连接。本课将介绍使用C语言开发一个类似Windows系统Ping工具的方法,并详细介绍其具体的实现流程,使读者体会C语言在网络编程领域中的应用。19.1 系统功能描述本课将通过一个简单的类似Windows系统的Ping工具实例,来说明C语言编写网络项目的基本方法。本实例的实现文件为“wangluo.c”,保存在“光盘:19”文件夹中。本实例系统的功能模块如下。1初始化模块用于初始化各个全局变量,为全局变量赋初始值,初始化Winsock,加载Winsock库。2控制模块此模块被其他的模块调用,实现获取参数、计算校验和、填充ICMP数据报文、释放占用资源和显示用户帮助。3数据解读模块用于解读接收到的ICMP报文和IP选项。4Ping测试模块此模块是本项目实例的核心模块,它可以调用其他的模块,最终实现Ping命令功能。上述模块的总体结构如图19-1所示。图19-1 项目功能模块结构19.2 系统总体设计经过19.1的系统构成功能分析后,即可根据各构成的功能模块进行相应的设计处理。本节将简要介绍此系统的总体设计过程。19.2.1 功能模块设计1系统运行流程此系统的运行流程如图19-2所示。图19-2 系统运行流程图在图19-2所示的运行流程中,将首先调用InitPing()函数来初始化各个全局变量,然后使用函数GetArgments来获取用户输入的参数,并对此参数进行检查。如果参数不正确,则显示帮助信息并结束程序;如果参数正确则执行Ping命令,如果Ping通则显示结果并释放所占用的资源,如果没有Ping通则显示错误信息,并释放所占用的资源。2GetArgments函数GetArgments函数用于获取用户输入的参数,在此获取的参数有如下3个。l -r:记录路由参数。l -n:记录条数。l Datasize:数据报大小。GetArgments函数的处理流程如下。l 判断上述参数的第一个字符,如果第一个字符是“-”,则认为是-r或-n中的一个,然后即可进行下一步判断。l 如果参数的第二个字符是数字,则判断此参数是记录的条数。l 如果第二个字符是“r”,则判断该参数是“-r”,用于记录路由。l 如果第一个参数是数字,则此参数是IP或Datasize,然后进行进一步判断。l 如果参数中不存在非数字字符,则此参数是Datasize;如果存在非数字字符,则此参数是IP地址。l 如果是其他情况,则为主机名。上述GetArgments函数的运行流程如图19-3所示。图19-3 GetArgments函数运行流程图3函数Ping处理函数Ping是本系统的核心,通过调用其他的函数来实现具体功能。函数Ping可以实现如下功能。l 创建套接字。l 设置路由选项。l 创建ICMP请求报文。l 接收ICMP应答报文。l 解读ICMP文件。19.2.2 数据结构设计实例中包含的数据结构如下。1IP报头结构体此处的IP报头结构体是_iphdr,具体代码如下。typedef struct _iphdr unsigned int h_len:4; unsigned int version:4; unsigned char tos; unsigned short total_len; unsigned short ident; unsigned short frag_flags; unsigned char ttl unsigned char proto; unsigned short checksum; unsigned int sourceIP; unsigned int destIP; IpHeader;在结构体_iphdr中,设置了需要的变量名,各变量的具体说明如下。l h_len:4:IP报头长度。l version:4:IP的版本号。l tos:服务的类型。l total_len:数据报总长度。l ident:唯一的标识符。l frag_flags:分段标志。l proto:协议类型(TCP、UDP等)。l checksum:校验和。l sourceIP:源IP地址。2ICMP报头结构体此处的ICMP报头结构体是_icmphdr,具体代码如下。typedef struct _icmphdr BYTE i_type;/*ICMP报文类型*/ BYTE i_code; /*该类型中的代码号*/ USHORT i_cksum; /*校验和*/ USHORT i_id; /*唯一的标识符*/ USHORT i_seq; /*序列号*/ ULONG timestamp; /*时间戳*/ IcmpHeader;i_type结构体表示ICMP报文类型,i_code表示该类型中的代码号,i_cksum表示校验和,颜色值可以根据需要而设置,i_id表示唯一的标识符,i_seq表示序列号,timestamp表示时间戳。3IP选项结构体此处的IP选项结构体是_ipoptionhdr,具体代码如下。typedef struct _ipoptionhdrunsigned char code; /*选项类型*/ unsigned char len; /*选项头长度*/ unsigned char ptr; /*地址偏移长度*/unsigned long addr9; /*记录的IP地址列表*/ IpOptionHeader;19.2.3 构成函数介绍此实例中各主要构成函数的基本信息如下。1函数InitPing函数InitPing用于初始化所需要的变量,具体格式如下。void InitPing()2函数UserHelp函数UserHelp用于显示用户的帮助信息,具体格式如下。void UserHelp()3函数GetArgments函数GetArgments用于获取用户提交的处理参数,具体格式如下。void GetArgments(int argc,char* argv)4函数CheckSum函数CheckSum用于计算校验和。首先把数据报头中的校验和字段设置为0,然后对首部中的每个16bit进行二进制反码求和,将结果存放在校验和字段中,具体格式如下。USHORT CheckSum(USHORT *buffer, int size)5函数FillICMPData函数FillICMPData用于填充ICMP数据报字段,其中参数“icmp_data”表示ICMP数据,“datasize”表示ICMP报文大小,具体格式如下。void FillICMPData(char *icmp_data, int datasize)6函数FreeRes函数FreeRes用于释放所占用的内存资源,具体格式如下。void FreeRes()7函数DecodeIPOptions函数DecodeIPOptions用于解读IP选项头,从中读取从源主机到目标主机经过的路由,并输出路由信息,具体格式如下。void DecodeIPOptions(char *buf, int bytes)8函数DecodeICMPHeader函数DecodeICMPHeader用于解读ICMP的报文信息,其中参数“buf”表示存放接收到的ICMP报文的缓冲区,“bytes”表示接收到的字节数,“from”表示发送ICMP回显应答的主机IP地址,具体格式如下。void DecodeICMPHeader(char *buf, int bytes, SOCKADDR_IN *from)9函数PingTest函数PingTest用于进行Ping操作处理,具体格式如下。void PingTest(int timeout)19.3 系统具体实现经过了前面的功能模块分析和系统总体设计后,用户就可以在此基础上进行程序设计了。本节将详细介绍此项目实例的具体实现过程。19.3.1 预处理程序预处理包括库文件导入、头文件加载、定义常量和全局变量,以及定义数据结构。本项目实例需要导入的库文件是“ws2_32.lib”,另外还需要加载头文件“winsock2.h”和“ws2tcpip.h”。注意:ws2_32.lib是调用WinSock2函数时需要链接的库文件,即调用winsock.dll时动态连接库,加入此文件后,就不必显示调用了。具体代码如下所示。/*导入库文件*/#pragma comment( lib, ws2_32.lib )/*加载头文件*/#include #include #include #include #include /*定义常量*/*表示要记录路由*/#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 InitPing();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);void DecodeICMPHeader(char *buf, int bytes, SOCKADDR_IN* from);void PingTest(int timeout);/*IP报头字段数据结构*/typedef struct _iphdr unsigned int h_len:4;/*IP报头长度*/ unsigned int version:4;/*IP的版本号*/ unsigned char tos;/*服务的类型*/ unsigned short total_len;/*数据报总长度*/ unsigned short ident; /*唯一的标识符*/ unsigned short frag_flags; /*分段标志*/ unsigned char ttl; /*生存期*/ unsigned char proto; /*协议类型(TCP、UDP等)*/ unsigned short checksum; /*校验和*/ unsigned int sourceIP; /*源IP地址*/ unsigned int destIP; /*目的IP地址*/ IpHeader;/*ICMP报头字段数据结构*/typedef struct _icmphdr BYTE i_type; /*ICMP报文类型*/ BYTE i_code; /*该类型中的代码号*/ USHORT i_cksum; /*校验和*/ USHORT i_id; /*唯一的标识符*/ USHORT i_seq; /*序列号*/ ULONG timestamp; /*时间戳*/ IcmpHeader;/*IP选项头字段数据结构*/typedef struct _ipoptionhdr unsigned char code; /*选项类型*/ unsigned char len; /*选项头长度*/ unsigned char ptr; /*地址偏移长度*/unsigned long addr9; /*记录的IP地址列表*/ IpOptionHeader;/*定义全局变量*/SOCKET m_socket;IpOptionHeader IpOption;SOCKADDR_IN DestAddr;SOCKADDR_IN SourceAddr;char *icmp_data;char *recvbuf;USHORT seq_no ;char *lpdest;int datasize;BOOL RecordFlag;double PacketNum;BOOL SucessFlag;19.3.2 初始化处理初始化需要处理多个全局变量,并通过WSAStartup函数来加载Winsock库。在此需要对icmp_data、recvbuf和lpdest都赋值为NULL,对seq_no赋值为0,对RecordFlag赋值为FALSE,对datasize赋值为DEF_PACKET_SIZE,此处表示默认的数据报大小为32。另外,还要对PacketNum赋值为5,5是默认记录,即默认发送5条ICMP回显请求;对SuccessFlag赋值为FALSE,在程序完全成功执行后才会赋值为True。函数WSAStartup实现对Winsock的加载,通过宏MAKEWORD来获取准备加载的Winsock版本。具体实现代码如下所示。/*初始化变量函数*/ void InitPing() WSADATA wsaData; icmp_data = NULL; seq_no = 0; recvbuf = NULL; RecordFlag = FALSE; lpdest = NULL; datasize = DEF_PACKET_SIZE; PacketNum = 5; SucessFlag = FALSE; /*Winsock初始化*/ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) /*如果初始化不成功则报错,GetLastError()返回发生的错误信息*/printf(WSAStartup() failed: %dn, GetLastError(); return ; m_socket = INVALID_SOCKET;19.3.3 控制模块此处控制模块的功能是为其他模块提供调用函数,它能够实现参数获取、校验处理、计算处理、ICMP数据填充、释放占用资源和显示用户帮助等功能。具体实现代码如下所示。/*显示信息函数*/void UserHelp()printf(UserHelp: ping -r data sizen); printf(-rrecord routen);printf(-nrecord amountn); printf(hostremote machine to pingn); printf(datasizecan be up to 1KBn); ExitProcess(-1); /*获取ping选项函数*/void GetArgments(int argc,char* argv) int i; int j; int exp; int len; int m;/*如果没有指定目的地地址和任何选项*/ if(argc = 1)printf(nPlease specify the destination IP address and the ping option as follow!n); UserHelp();for(i = 1; i =1;j-,exp+)/*根据argvij中的ASCII值计算要获取的记录条数(十进制数)*/PacketNum += (double)(argvij-48)*pow(10,exp);elseswitch (tolower(argvi1)/*选项指示要获取路由信息*/case r: RecordFlag = TRUE;break;/*没有按要求提供选项*/default:UserHelp();break; /*参数是数据报大小或者IP地址*/ else if (isdigit(argvi0)for(m=1;m 1) cksum += *buffer+; size -= sizeof(USHORT); if (size) cksum += *(UCHAR*)buffer; /*对每个16bit进行二进制反码求和*/ cksum = (cksum 16) + (cksum & 0xffff); cksum += (cksum 16); return (USHORT)(cksum);/*填充ICMP数据报字段函数*/void FillICMPData(char *icmp_data, int datasize) IcmpHeader *icmp_hdr = NULL; char *datapart = NULL; icmp_hdr = (IcmpHeader*)icmp_data; /*ICMP报文类型设置为回显请求*/ icmp_hdr-i_type = ICMP_ECHO; icmp_hdr-i_code = 0; /*获取当前进程IP作为标识符*/ icmp_hdr-i_id = (USHORT)GetCurrentProcessId(); icmp_hdr-i_cksum = 0; icmp_hdr-i_seq = 0;datapart = icmp_data + sizeof(IcmpHeader);/*以数字0填充剩余空间*/memset(datapart,0,datasize-sizeof(IcmpHeader);/*释放资源函数*/void FreeRes()/*关闭创建的套接字*/if (m_socket != INVALID_SOCKET) closesocket(m_socket); /*释放分配的内存*/ HeapFree(GetProcessHeap(), 0, recvbuf); HeapFree(GetProcessHeap(), 0, icmp_data); /*注销WSAStartup()调用*/ WSACleanup(); return ;19.3.4 数据报解读处理此处控制模块的功能是解读IP选项和ICMP报文,当主机接收到目的主机返回的ICMP回显应答后,将会调用ICMP解读函数来解读ICMP报文,并且ICMP解读函数将调用IP选项解读函数来实现IP路由输出。具体实现代码如下所示。/*解读IP选项头函数*/void DecodeIPOptions(char *buf, int bytes)IpOptionHeader *ipopt = NULL; IN_ADDR inaddr; int i; HOSTENT *host = NULL; /*获取路由信息的地址入口*/ ipopt = (IpOptionHeader *)(buf + 20); printf(RR: ); for(i = 0; i ptr / 4) - 1; i+) inaddr.S_un.S_addr = ipopt-addri; if (i != 0) printf( ); /*根据IP地址获取主机名*/ host = gethostbyaddr(char *)&inaddr.S_un.S_addr,sizeof(inaddr.S_un.S_addr), AF_INET); /*如果获取到了主机名,则输出主机名*/if (host) printf(%-15s) %sn, inet_ntoa(inaddr), host-h_name); /*否则输出IP地址*/else printf(%-15s)n, inet_ntoa(inaddr); return;/*解读ICMP报头函数*/void DecodeICMPHeader(char *buf, int bytes, SOCKADDR_IN *from)IpHeader *iphdr = NULL; IcmpHeader *icmphdr = NULL; unsigned short iphdrlen; DWORD tick; static int icmpcount = 0; iphdr = (IpHeader *)buf; /*计算IP报头的长度*/ iphdrlen = iphdr-h_len * 4; tick = GetTickCount(); /*如果IP报头的长度为最大长度(基本长度是20字节),则认为有IP选项,需要解读IP选项*/ if (iphdrlen = MAX_IP_HDR_SIZE) & (!icmpcount)/*解读IP选项,即路由信息*/ DecodeIPOptions(buf, bytes); if (bytes sin_addr); icmphdr = (IcmpHeader*)(buf + iphdrlen); /*如果收到的不是回显应答报文,则报错*/ if (icmphdr-i_type != ICMP_ECHOREPLY) printf(nonecho type %d recvdn, icmphdr-i_type); return; /*核实收到的ID号与发送的ID号是否一致*/ if (icmphdr-i_id != (USHORT)GetCurrentProcessId() printf(someone elses packet!n); return ; SucessFlag = TRUE; /*输出记录信息*/ printf(%d bytes from %s:, bytes, inet_ntoa(from-sin_addr); printf( icmp_seq = %d. , icmphdr-i_seq); printf( time: %d ms, tick - icmphdr-timestamp); printf(n); icmpcount+; return;19.3.5 Ping测试处理此模块是整个项目的核心,其功能是进行Ping操作处理。当整个项目初始化处理完成后,根据用户提交的参数,即可进行Ping处理。具体实现代码如下所示。/*ping函数*/void PingTest(int timeout) int ret;int readNum;int fromlen;struct hostent *hp = NULL;/*创建原始套接字,该套接字用于ICMP*/m_socket = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0,WSA_FLAG_OVERLAPPED); /*如果套接字创建不成功*/ if (m_socket = INVALID_SOCKET) printf(WSASocket() failed: %dn, WSAGetLastError(); return ; /*若要求记录路由选项*/ if (RecordFlag) /*IP选项每个字段用0初始化*/ ZeroMemory(&IpOption, sizeof(IpOption);/*为每个ICMP包设置路由选项*/ IpOption.code = IP_RECORD_ROUTE; IpOption.ptr= 4; IpOption.len= 39; ret = setsockopt(m_socket, IPPROTO_IP, IP_OPTIONS,(char *)&IpOption, sizeof(IpOption); if (ret = SOCKET_ERROR) printf(setsockopt(IP_OPTIONS) failed: %dn,WSAGetLastError(); /*设置接收的超时值*/ readNum = setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO,(char*)&timeout, sizeof(timeout); if(readNum = SOCKET_ERROR) printf(setsockopt(SO_RCVTIMEO) failed: %dn,WSAGetLastError(); return ; /*设置发送的超时值*/ timeout = 1000; readNum = setsockopt(m_socket, SOL_SOCKET, SO_SNDTIMEO,(char*)&timeout, sizeof(timeout); if (readNum = SOCKET_ERROR) printf(setsockopt(SO_SNDTIMEO) failed: %dn,WSAGetLastError(); return ; /*用0初始化目的地地址*/ memset(&DestAddr, 0, sizeof(DestAddr);/*设置地址族,这里表示使用IP地址族*/DestAddr.sin_family = AF_INET; if (DestAddr.sin_addr.s_addr = inet_addr(lpdest) = INADDR_NONE) /*名字解析,根据主机名获取IP地址*/if (hp = gethostbyname(lpdest) != NULL) /*将获取到的IP值赋给目的地地址中的相应字段*/memcpy(&(DestAddr.sin_addr), hp-h_addr, hp-h_length);/*将获取到的地址逐值赋给目的地地址中的相应字段*/ DestAddr.sin_family = hp-h_addrtype; printf(DestAddr.sin_addr = %sn, inet_ntoa(DestAddr.sin_addr); /*获取不成功*/else printf(gethostbyname() failed: %dn,WSAGetLastError(); return ; /*数据报文大小需要包含ICMP报头*/ datasize += sizeof(IcmpHeader); /*根据默认堆句柄,从堆中分配MAX_PACKET内存块,新分配内存的内容将被初始化为0*/ icmp_data =(char*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,MAX_PACKET); recvbuf =(char*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,MAX_PACKET); /*如果分配内存不成功*/ if (!icmp_data) printf(HeapAlloc() failed: %dn, GetLastError(); return ; /* 创建ICMP报文*/ memset(icmp_data,0,MAX_PACKET); FillICMPData(icmp_data,datasize); while(1) static int nCount = 0; int writeNum; /*超过指定的记录条数则退出*/ if (nCount+ = PacketNum) break; /*计算校验和前要把校验和字段设置为0*/ (IcmpHeader*)icmp_data)-i_cksum = 0; /*获取操作系统启动到现在所经过的毫秒数,设置时间戳*/ (IcmpHeader*)icmp_data)-timestamp = GetTickCount(); /*设置序列号*/ (IcmpHeader*)icmp_data)-i_seq = seq_no+; /*计算校验和*/ (IcmpHeader*)icmp_data)-i_ck

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论