当前位置:文档之家› 计算机网络程序设计(c语言课程设计)

计算机网络程序设计(c语言课程设计)

ping程序设计ping命令是使用频率极高的一个网络测试命令,用以测试从一个主机到另一个主机间的网络上否可达。

windows自带的ping命令具有强大的功能,它有很多选项用于实现不同的测试目的。

本章模仿windows的ping命令,用c语言实现了一个简单的命令。

本章着重讲述ping命令的实现原理和c语言的网络编程方法。

读者可以在本章的基础上,对本章实现的ping命令进行扩展,开发出功能更强大、更完善的ping命令,并进一步掌握网络编程的方法。

9.1 设计目的本章通过设计Ping程序,讲解Ping程序的实现原理,并初步讲解了c语言网络编程技术。

本章涉及很多网络编程函数和编程技巧。

包括库文件的导入;winsock的初始化、注销;socket 的创建、关闭;设置socket选项;根据主机名获取IP地址;从堆中分配一定数量的空间、释放从堆中分配的空间;获取当前进程ID号;数据报的发送;数据报的接等。

通过本程序的训练,使读者对网络编程有一定的了解,掌握Ping程序的设计方法,掌握网络编程的方法和技巧,从而编写出功能更强大的程序。

9.2功能描述本章用 c 语言实现的ping命令,能用于测试一个主机到另一个主机间的联通情况,程序还提供了几个选项以实现不同的功能。

(1)实现ping功能。

程序能实现基本的ping操作,发送ICMP回显请求报文,接收显应答报文。

(2)能记录路由。

程序提供了“-r”选项,用以记录从源主机到目的主机的路由。

(3)能输出指定条数的记录。

程序提供了“-n”选项,用以输出指定条数的记录。

(4)能按照指定大小输出每条记录。

程序提供了“datasize”选项,用以指定输出的数据报的大小。

(5)能输出用户帮助。

程序提供了用户帮助,显示程序提供的选项以及选项格式等。

9.3 总体设计9.3.1 功能模块设计1. 功能模块图本系统共有 4 个模块,分别是初始化模块、功能控制模块、数据控制模块、数据报解读模块和ping测试模块,如图9.1所示。

各模块功能描述如下。

图9.1 系统模块图(1) 初始化模块。

改模块用于初始化各个全局变量,为全局变量赋初始值;初始化,加载库。

(2)功能控制模块。

改模块是被其它模块调用,其功能包括获取参数、计算校验和填充数据报文、释放占用资源和显示用户帮助。

(3)数据报解读模块。

改模块用于解读接收到的报文和选项。

(4)测试模块。

改模块是本程序的核心模块,调用其他模块实现其功能,主要是实现的功能。

2.系统流程图系统执行的流程图9.2所示。

程序首先调用IniPing()函数初始化各全局变量,然后GetArgments()函数获取用户输入的参数,检查用户输入的参数,如果参数不正确或者没有输入参数,则显示用户帮助信息(User help ),并结束程序;如果参数正确,则对指定目的地执行Ping命令,如果Ping通,则显示Ping结果并释放占用资源,如果没有Ping通,则报告错误信息,并释放占用资源。

图9.2 系统流程图3.参数获取(GetArgments()函数)流程图获取的参数包括“-r”(记录路由)、“-n”(记录条数程序,任意的整数)和datasize(数据报大小)。

程序首先判断每一个参数的第一字符,如果第一个字符是“-”(短横线),则认为是“-r”或者“-n”中的一个,然后作进一步判断。

如果该参数的第二个字符是数字,则判断该参数为记录的条数,如果该参数的第二个字符是“r”,则判断该参数为“-r”,用于记录路由;如果参数的第一个字符是数字,则认为参数是IP地址;或者datasize,然后作进一步的判断。

如果该参数中不存在非数字的字符,则判断该参数为datasize;如果存在非数字的字符,则判断该参数为IP地址;其他情况则判断为主机名。

参数获取的流程如图9.3所示。

图9.3 参数获取流程图4.ping()函数流程图ping()函数是本程序的核心部分它调用其他模块的函数来实现,其主要步骤包括创建接字,设置路由选项(如果需要的话)、设置接收和发送超时值、名字解析(如果需要的话)、分配内存、创建ICMP报文、发送ICMP请求报文、接收ICMP应答报文和解读ICMP报文。

其执行流程如图9.4所示。

图9.4 Ping 函数流程图9.3.2数据结构设计本程序定义了3个结构体:-iphdr、-icmphdr、和-ipotionhdr,分别用于存放IP报头信息、ICM P报头信息和IP路由选项信息。

1.定义IP报头结构体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 chor proto;Unsigned short checksum;Unsigned int sourceIP;Unsigned int destIP;} IpHeader;h-len:4 : 表示IP报头长度,首部长度指的是首部占32bit字的数目,包括任何选项。

由于它是一个4bit 字段,因此首部最长为60个字节,不包括任何选项的IP报头是20个字节。

Version:4: 表示IP的版本号,这里表示Ipv4.。

Top: 表示服务的类型,可以表示最小时延,最大吞吐量,最高可靠性和最小费用。

Total –len: 整个IP数据报的总长度。

Ident: 唯一的标识符,标识主机发送的每一份数据报。

Frag-flags: 分段标志,表示过长的数据报是否要分段。

Ttl: 生存期,表示数据报可以经过的最多路由器数。

Proto: 协议类型(TCP、UDP等)。

Checksum: 校验和。

sourceIP: 源IP地址。

destIP: 目的IP地址。

2.定义ICMP报头结构体Typedef struct –icmphdr{BYTE i_type;BYTE i_code :USHORT i_cksum;USHORT i_id;USHORT i_seq;ULONG timestamp;} IcmpHeader;其中各字段表示意义如下。

I_tye : ICMP报文类型。

I_code : 该类型中的代码号,一种ICMP 报文的类型号和该类型中的代码号共同决定。

、I_cksum: 校验和。

I_seq: 序列号,序列号从0开始,每发送一次新的回显请求就加1. Timestamp: 时间。

3.定义IP 选项结构体Typedef struct _ipoptionhdr{Unsigned char code;Unsigned char len;Unsigned char ptr;Unsigned loang addr[9];} IcmpHeader;Code: 指明IP 选项类型,对于路由记录选项,它的值是7。

Len: 选项头长度。

Ptr: 地址指针字段,是一个基于1的指针,指向存放下一个IP地址的位置。

addr[9]: 记录的Ip地址列表,由于IP首部中选项的空间有限,所以可以记录的Ip地址最多是9个。

9.33函数功能描述1)IntPing()函数原型:void IntPing()IntPing()函数用于初始化ping 所需的全局变量,为各个变量赋初始值。

2)userHelp()函数原型:void userHelp()userHelp()函数用于显示用户帮助信息。

当程序检查到参数错误或者没有必要的参数(如主机IP地址或者主机名)时,则会调用此函数显示帮助信息。

3) GetArgments()函数原型:void GetArgments(int argc, char**argv)GetArgments()函数用于获取用户提交的参数。

其中argc 表示获取的参数个数,argv 用于存储获取的参数,这两个形参和主函数中的形参表示的意义一样的。

4)checkSum()函数原型:USHORT checkSum(USHORT *buffer,int size)checkSum()函数用于计算校验和。

计算过程是首先把数据报头中的校验和字段设置为0,然后对首部中每个16bit 进行二字段进制反码求和(整个首部看成是由一串16bit 的字组成),结果存在校验和字段中。

其中buffer 用于存放ICMP数据,size表示ICMP报文大小。

5)FillCMPData()函数原型:void FillCMPData()FillCMPData()函数用于填充ICMP数据报中各个字段。

其中icmp_data 表示ICMP数据,datasize 表示ICMP报文大小。

6) reeRes()函数原型:void reeRes()reeRes()函数用于释放占用的资源,包括关闭初始化socket 调用的函数的、关闭创建的socket和释放分配的内存等。

7)DecodeIPOptions()函数原型:void DecodeIPOptions()DecodeIPOptions()函数用于解读IP选项,从中读出从源主机到目的主机经过的路由,并输出路由信息。

Buf表示存放接收到的ICMP报文的缓冲区,bytes表示接收到的字节数。

8)DecodelICMPHeader()函数原型:void DecodelICMPHeader(char*buf,int bytes,SOCKADDR_IN*from) DecodelICMPHeader()函数用于解读ICMP报文信息。

Buf表示存放接收到的ICMP报文的缓冲区,bytes表示接收到的字节数,from 表示发送ICMP回显应答的主机IP地址。

9)PingTest()函数原型:void PingTest(int timeout)PingTest()函数用于进行Ping操作。

其中timeout表示设定的发送超时值。

9.4程序实现9.1.4 源码分析1. 程序预处理/*导入库文件*/#pragma comment( lib, "ws2_32.lib" )/*加载头文件*/#include <winsock2.h>#include <ws2tcpip.h>#include <stdio.h>#include <stdlib.h>#include <math.h>/*定义常量*//*表示要记录路由*/#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 addr[9]; /*记录的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;2.初始化模块/*初始化变量函数*/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: %d\n", GetLastError());return ;}m_socket = INV ALID_SOCKET;}3.功能控制模块/*显示信息函数*/void UserHelp(){printf("UserHelp: ping -r <host> [data size]\n");printf(" -r record route\n");printf(" -n record amount\n");printf(" host remote machine to ping\n");printf(" datasize can be up to 1KB\n");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 < argc; i++){if (argv[i][0] == '-'){/*选项指示要获取记录的条数*/if(isdigit(argv[i][1])){PacketNum = 0;for(j=len-1,exp=0;j>=1;j--,exp++)/*根据argv[i][j]中的ASCII值计算要获取的记录条数(十进制数)*/PacketNum += ((double)(argv[i][j]-48))*pow(10,exp);}else{switch (tolower(argv[i][1])){/*选项指示要获取路由信息*/case 'r':RecordFlag = TRUE;break;/*没有按要求提供选项*/default:UserHelp();break;}}}/*参数是数据报大小或者IP地址*/else if (isdigit(argv[i][0])){for(m=1;m<len;m++){if(!(isdigit(argv[i][m]))){/*是IP地址*/lpdest = argv[i];break;}/*是数据报大小*/else if(m==len-1)datasize = atoi(argv[i]);}}/*参数是主机名*/else}}/*求校验和函数*/USHORT CheckSum(USHORT *buffer, int size){unsigned long cksum=0;while (size > 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 != INV ALID_SOCKET)closesocket(m_socket);/*释放分配的内存*/HeapFree(GetProcessHeap(), 0, recvbuf);HeapFree(GetProcessHeap(), 0, icmp_data);/*注销WSAStartup()调用*/WSACleanup();return ;}4.数据报解读模块/*解读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 < (ipopt->ptr / 4) - 1; i++){inaddr.S_un.S_addr = ipopt->addr[i];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) %s\n", inet_ntoa(inaddr), host->h_name);/*否则输出IP地址*/elseprintf("(%-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 < iphdrlen + ICMP_MIN){printf("Too few bytes from %s\n",inet_ntoa(from->sin_addr));}icmphdr = (IcmpHeader*)(buf + iphdrlen);/*如果收到的不是回显应答报文则报错*/if (icmphdr->i_type != ICMP_ECHOREPLY){printf("nonecho type %d recvd\n", icmphdr->i_type);return;}/*核实收到的ID号和发送的是否一致*/if (icmphdr->i_id != (USHORT)GetCurrentProcessId()){printf("someone else's 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;}5.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 == INV ALID_SOCKET){printf("WSASocket() failed: %d\n", 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: %d\n",WSAGetLastError());}}/*设置接收的超时值*/readNum = setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO,(char*)&timeout, sizeof(timeout));if(readNum == SOCKET_ERROR){printf("setsockopt(SO_RCVTIMEO) failed: %d\n",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: %d\n",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 = %s\n", inet_ntoa(DestAddr.sin_addr));}/*获取不成功*/else{printf("gethostbyname() failed: %d\n",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: %d\n", 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_cksum = CheckSum((USHORT*)icmp_data,datasize);/*开始发送ICMP请求*/writeNum = sendto(m_socket, icmp_data, datasize, 0,(struct sockaddr*)&DestAddr, sizeof(DestAddr));/*如果发送不成功*/if (writeNum == SOCKET_ERROR){/*如果是由于超时不成功*/if (WSAGetLastError() == WSAETIMEDOUT){printf("timed out\n");continue;}/*其他发送不成功原因*/printf("sendto() failed: %d\n", WSAGetLastError());return ;}/*开始接收ICMP应答*/fromlen = sizeof(SourceAddr);readNum = recvfrom(m_socket, recvbuf, MAX_PACKET, 0,(struct sockaddr*)&SourceAddr, &fromlen);/*如果接收不成功*/if (readNum == SOCKET_ERROR){/*如果是由于超时不成功*/if (WSAGetLastError() == WSAETIMEDOUT){printf("timed out\n");continue;}/*其他接收不成功原因*/printf("recvfrom() failed: %d\n", WSAGetLastError());return ;}/*解读接收到的ICMP数据报*/DecodeICMPHeader(recvbuf, readNum, &SourceAddr);}}6.主函数int main(int argc, char* argv[]){InitPing();GetArgments(argc, argv);PingTest(1000);/*延迟1秒*/Sleep(1000);if(SucessFlag)printf("\nPing end, you have got %.0f records!\n",PacketNum);elseprintf("Ping end, no record!");FreeRes();getchar();return 0;}。

相关主题