计算机网络课程设计_计算机网络课程设计题
计算机网络课程设计由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“计算机网络课程设计题”。
摘要
本课程设计主要是介绍通过用winsock技术来设计ping应用程序,包括winsock的背景和功能的介绍因为SOCKET是一种应用程序接口,所以也是目前的TCP/IP网络最为通用的API,也是在INTERNET上进行应用开发最为通用的API。SOCKET实际在计算机中提供了一个通信端口,可以通过这个端口与任何一个具有SOCKET接口的计算机通信。应用程序在网络上传输,接收的信息都通过这个SOCKET接口来实现。在应用开发中就像使用文件句柄一样,可以对SOCKET句柄进行读,写操作。本设计是通过用winsock的VC编程语言来实现ping应用程序的,其中包括ping命令的工作原理和一些相关功能,以及网际控制协议(ICMP)的数据报格式和IP数据报的格式,根据这些格式和相关的功能及原理设计出的ping命令的测试程序,程序通过分析ICMP报文的结构,在结构体中定义相应的字段来存储对应的信息。程序主要难点是定义对应的字段来存储报文的相应字段。程序通过send_packet();/*发送所有ICMP报文*/ recv_packet();/*接收所有ICMP报文*/函数来实现报文的发送和接收。另外,通过实验可以理解在网络中,报文的结构和网络传输协议。如: ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。这正好是网络中各层的相互关系。网络中数据通过ICMP数据报的数据的各个字段来判断路由,和选择虚电路。
目录
1.摘要„„„„„„„„„„„„„„„„„„„„„4 2.SOCKET简介„„„„„„„„„„„„„„„„„„6 3.基于WINDOWS SOCKET的应用开发介绍。„„„„„„6 4.Ping 命令的一些功能及工作原理„„„„„„„„„7 5.ICMP(Internet Control Meage,网际控制报文协议)数据报„„„„„„„„„„„„„„„„„„„„„„8 6.IP报头格式数据结构和ICMP数据结构的定义及相关说明„„„„„„„„„„„„„„„„„„„„„„„9 7.模拟Ping程序功能的代码„„„„„„„„„„„„13 8.程序运行的相关说明„„„„„„„„„„„„„„19 9.实验总结„„„„„„„„„„„„„„„„„„„20 10.参考文献„„„„„„„„„„„„„„„„„„„20
1.SOCKET简介
80年代初,美国政府的高级研究工程机构(ARPA)给加利福尼亚大学Berkeley分校提供了资金,让他们在UNIX操作系统下实现TCP/IP协议。在这个项目中,研究人员为TCP/IP网络通信开发了一个API(应用程序接口)。这个API称为Socket接口(套接字)。今天,SOCKET接口是TCP/IP网络最为通用的API,也是在INTERNET上进行应用开发最为通用的API。
90年代初,由Microsoft联合了其他几家公司共同制定了一套WINDOWS下的网络编程接口,即WindowsSockets规范。它是BerkeleySockets的重要扩充,主要是增加了一些异步函数,并增加了符合Windows消息驱动特性的网络事件异步选择机制。WINDOWSSOCKETS规范是一套开放的、支持多种协议的Windows下的网络编程接口。从1991年的1.0版到1995年的2.0.8版,经过不断完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成为Windows网络编程的事实上的标准。目前,在实际应用中的WINDOWSSOKCETS规范主要有1.1版和2.0版。两者的最重要区别是1.1版只支持TCP/IP协议,而2.0版可以支持多协议。2.0版有良好的向后兼容性,任何使用1.1版的源代码,二进制文件,应用程序都可以不加修改地在2.0规范下使用。
SOCKET实际在计算机中提供了一个通信端口,可以通过这个端口与任何一个具有SOCKET接口的计算机通信。应用程序在网络上传输,接收的信息都通过这个SOCKET接口来实现。在应用开发中就像使用文件句柄一样,可以对SOCKET句柄进行读,写操作。
2.基于WINDOWS SOCKET的应用开发介绍。
在WINDOWS95/98,WINDOWSNT进行WINSOCK开发使用的编程语言有很多,VC++,JAVA,DELPHI,VB等。其中VC时使用最普遍,和WINSOCK结合最紧密的。并且VC++对原来的WindowsSockets库函数进行了一系列封装,继而产生了CAsynSocket、CSocket、CSocketFile等类,它们封装着有关Socket的各种功能,是编程变得更加简单。但如果你是一个WINSOCK编程的初学者,那么建议你在一开始还是学习WINSOCK最基本的API函数进行编程,这样可以大大加深对WINSOCK的了解,对将来很有好处。在VC中进行WINSOCK的API编程开发,需要使用到下面三个文件:
1、WINSOCK.H: 这是WINSOCK API的头文件。
2、WSOCK32.LIB: WINSOCK API连接库文件。在使用中,一点要把它作为项目的非缺省的连接库包含到项目文件中去。
3、WINSOCK.DLL: WINSOCK的动态连接库,位于WINDOWS的安装目录下。DLL位于TCP/IP协议栈和应用程序之间。也就是说,WINSOCK管理与TCP/IP协议的接口。在一开始WINSOCK的应有开发时,你不必对TCP/IP协议有很深刻的了解。但是,如果想成为一个为网络编程的高手,就一定要对下层了解得十分清楚。
在网络编程中最常用的方案便是客户机/服务器模型。在这种方案中客户应用程序向服务器程序请求服务。一个服务程序通常在一个众所周知的地址监听对服务的请求,也就是说,服务进程一直处于休眠状态,直到一个客户对这个服务的地址提出了连接请求。在这个时刻,服务程序被“惊醒”并且为客户提供服务-对客户的请求作出适当的反应。虽然基于连接协议(流套接字)的服务是设计客户机/服务器应用程序时的标准,但有些服务也是可以通过无连接协议(数据报套接字)提供的。
3.Ping 命令的一些功能及工作原理
大部分人用ping命令只是作为查看另一个系统的网络连接是否正常的一种简单方法。如何用C语言编写一个模拟ping命令功能的程序?
ping命令的功能查看网络上另一个主机系统的网络连,是用来查看网络上另一个主机系统的网络连接是否正常的一个工具。
ping命令的工作原理是:向网络上的另一个主机系统发送ICMP报文,如果指定系统得到了报文,它将把报文一模一样地传回给发送者,这有点象潜水艇声纳系统中使用的发声装置。
例如,在Linux终端上执行ping localhost命令将会看到以下结果: 4
由上面的执行结果可以看到,ping命令执行后显示出被测试系统主机名和相应IP地址、返回给当前主机的ICMP报文顺序号、ttl生存时间和往返时间rtt(单位是毫秒,即千分之一秒)。要写一个模拟ping命令,这些信息有启示作用。
要真正了解ping命令实现原理,就要了解ping命令所使用到的TCP/IP协议。
4.ICMP(Internet Control Meage,网际控制报文协议)数据报
ICMP(Internet Control Meage,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方。ICMP协议是IP层的一个协议,但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。
ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。如下图所示
IP报头 ICMP报头 ICMP数据报
IP报头格式
由于IP层协议是一种点对点的协议,而非端对端的协议,它提供无连接的数据报服务,没有端口的概念,因此很少使用bind()和connect()函数,若有使用也只是用于设置IP地址。发送数据使用sendto()函数,接收数据使用recvfrom()函数。IP报头格式如下图: 图4.1 其中ping程序只使用以下数据:
IP报头长度IHL(Internet Header Length)――以4字节为一个单位来记录IP报头的长度,是上述IP数据结构的ip_hl变量。
生存时间TTL(Time To Live)――以秒为单位,指出IP数据报能在网络上停留的最长时间,其值由发送方设定,并在经过路由的每一个节点时减一,当该值为0时,数据报将被丢弃,是上述IP数据结构的ip_ttl变量。
ICMP报头格式
ICMP报文分为两种,一是错误报告报文,二是查询报文。每个ICMP报头均包含类型、编码和校验和这三项内容,长度为8位,8位和16位,其余选项则随ICMP的功能不同而不同。
Ping命令只使用众多ICMP报文中的两种:“请求回送”(ICMP_ECHO)和“请求回应”(ICMP_ECHOREPLY)。
这两种ICMP类型报头格式如下:
图4.2
5.IP报头格式数据结构和ICMP数据结构的定义及相关说明 在Linux中,IP报头格式数据结构定义如下: struct ip { #if __BYTE_ORDER == __LITTLE_ENDIAN Unsigned int ip_hl:4;/* header length */ Unsigned int ip_v:4;/* version */ #endif #if __BYTE_ORDER == __BIG_ENDIAN unsigned int ip_v:4;/* version */ unsigned int ip_hl:4;/* header length */ #endif u_int8_t ip_tos;/* type of service */ u_short ip_len;/* total length */ u_short ip_id;/* identification */ u_short ip_off;/* fragment offset field */ #define IP_RF 0x8000 /* reserved fragment flag */ #define IP_DF 0x4000 /* dont fragment flag */ #define IP_MF 0x2000 /* more fragments flag */ #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ u_int8_t ip_ttl;/* time to live */ u_int8_t ip_p;/* protocol */ u_short ip_sum;/* checksum */ struct in_addr ip_src, ip_dst;/* source and dest addre */ };
在Linux中ICMP数据结构()定义如下: #define ICMP_ECHO 0 #define ICMP_ECHOREPLY 8 struct icmp { u_int8_t icmp_type;/* type of meage, see below */ u_int8_t icmp_code;/* type sub code */ u_int16_t icmp_cksum;/* ones complement checksum of struct */ union { u_char ih_pptr;/* ICMP_PARAMPROB */ struct in_addr ih_gwaddr;/* gateway addre */ struct ih_idseq /* echo datagram */ { u_int16_t icd_id;u_int16_t icd_seq;} ih_idseq;u_int32_t ih_void;/* ICMP_UNREACH_NEEDFRAG--Path MTU Discovery(RFC1191)*/ struct ih_pmtu { u_int16_t ipm_void;u_int16_t ipm_nextmtu;} ih_pmtu;struct ih_rtradv { u_int8_t irt_num_addrs;u_int8_t irt_wpa;u_int16_t irt_lifetime;} ih_rtradv;} icmp_hun;#define icmp_pptr icmp_hun.ih_pptr #define icmp_gwaddr icmp_hun.ih_gwaddr #define icmp_id icmp_hun.ih_idseq.icd_id #define icmp_seq icmp_hun.ih_idseq.icd_seq #define icmp_void icmp_hun.ih_void #define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void #define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu #define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs #define icmp_wpa icmp_hun.ih_rtradv.irt_wpa #define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime union { struct { u_int32_t its_otime;u_int32_t its_rtime;u_int32_t its_ttime;} id_ts;struct { struct ip idi_ip;/* options and then 64 bits of data */ } id_ip;struct icmp_ra_addr id_radv;u_int32_t id_mask;u_int8_tid_data[1];} icmp_dun;#defineicmp_otimeicmp_dun.id_ts.its_otime #defineicmp_rtimeicmp_dun.id_ts;u_int8_t id_data[1];} icmp_dun;#define icmp_otime icmp_dun.id_ts.its_otime #define icmp_rtime icmp_dun.id_ts.its_rtime #define icmp_ttime icmp_dun.id_ts.its_ttime #define icmp_ip icmp_dun.id_ip.idi_ip #define icmp_radv icmp_dun.id_radv #define icmp_mask icmp_dun.id_mask #define icmp_data icmp_dun.id_data };
使用宏定义令表达更简洁,其中ICMP报头为8字节,数据报长度最大为64K字节。校验和算法――这一算法称为网际校验和算法,把被校验的数据16位进行累加,然后取反码,若数据字节长度为奇数,则数据尾部补一个字节的0以凑成偶数。
标识符――用于唯一标识ICMP报文, 为上述ICMP数据结构的icmp_id宏所指的变量。
顺序号――ping命令的icmp_seq便由这里读出,代表ICMP报文的发送顺序,为上述ICMP数据结构的icmp_seq宏所指的变量。
ICMP数据报
Ping命令中需要显示的信息,包括icmp_seq和ttl都已有实现的办法,但还缺rtt往返时间。为了实现这一功能,可利用ICMP数据报携带一个时间戳。使用以下函数生成时间戳:
#include int gettimeofday(struct timeval *tp,void *tzp)其中timeval结构如下: struct timeval { long tv_sec;long tv_usec;} 其中tv_sec为秒数,tv_usec微秒数。在发送和接收报文时由gettimeofday分别生成两个timeval结构,两者之差即为往返时间,即ICMP报文发送与接收的时间差,而timeval结构由ICMP数据报携带,tzp指针表示时区,一般都不使用,赋NULL值。
数据统计
系统自带的ping命令当它接送完所有ICMP报文后,会对所有发送和所有接收的ICMP报文进行统计,从而计算ICMP报文丢失的比率。为达此目的,定义两个全局变量:接收计数器和发送计数器,用于记录ICMP报文接受和发送数目。丢失数目=发送总数-接收总数,丢失比率=丢失数目/发送总数。
6.模拟Ping程序功能的代码 #define PACKET_SIZE 4096 #define MAX_WAIT_TIME 5 #define MAX_NO_PACKETS 3 char sendpacket[PACKET_SIZE];char recvpacket[PACKET_SIZE];int sockfd,datalen=56;int nsend=0,nreceived=0;struct sockaddr_in dest_addr;pid_t pid;struct sockaddr_in from;struct timeval tvrecv;void statistics(int signo);unsigned short cal_chksum(unsigned short *addr,int len);int pack(int pack_no);void send_packet(void);void recv_packet(void);int unpack(char *buf,int len);void tv_sub(struct timeval *out,struct timeval *in);void statistics(int signo){ printf(“n--------------------PING statistics-------------------n”);printf(“%d packets transmitted, %d received , %%%d lostn”,nsend,nreceived,(nsend-nreceived)/nsend*100);close(sockfd);exit(1);} /*校验和算法*/ unsigned short cal_chksum(unsigned short *addr,int len){ int nleft=len;int sum=0;unsigned short *w=addr;unsigned short answer=0;/*把ICMP报头二进制数据以2字节为单位累加起来*/ while(nleft>1){ sum+=*w++;nleft-=2;} /*若ICMP报头为奇数个字节,会剩下最后一字节。把最后一个字节视为一个2字节数据的高字节,这个2字节数据的低字节为0,继续累加*/ if(nleft==1){ *(unsigned char *)(&answer)=*(unsigned char *)w;sum+=answer;} sum=(sum>>16)+(sum&0xffff);sum+=(sum>>16);answer=~sum;return answer;} /*设置ICMP报头*/ int pack(int pack_no){ int i,packsize;struct icmp *icmp;struct timeval *tval;icmp=(struct icmp*)sendpacket;icmp->icmp_type=ICMP_ECHO;icmp->icmp_code=0;icmp->icmp_cksum=0;icmp->icmp_seq=pack_no;icmp->icmp_id=pid;packsize=8+datalen;tval=(struct timeval *)icmp->icmp_data;gettimeofday(tval,NULL);/*记录发送时间*/ icmp->icmp_cksum=cal_chksum((unsigned short *)icmp,packsize);/*校验算法*/ return packsize;} /*发送三个ICMP报文*/ void send_packet(){ int packetsize;while(nsend { nsend++;packetsize=pack(nsend);/*设置ICMP报头*/ if(sendto(sockfd,sendpacket,packetsize,0,(struct sockaddr *)&dest_addr,sizeof(dest_addr))
perror(“sendto error”);continue;} sleep(1);/*每隔一秒发送一个ICMP报文*/ } } /*接收所有ICMP报文*/ void recv_packet(){ int n,fromlen;extern int errno;signal(SIGALRM,statistics);fromlen=sizeof(from);while(nreceived { alarm(MAX_WAIT_TIME);if((n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(struct sockaddr *)&from,&fromlen))ip_hl
len-=iphdrlen;/*ICMP报头及ICMP数据报的总长度*/ if(lenicmp_type==ICMP_ECHOREPLY)&&(icmp->icmp_id==pid)){ tvsend=(struct timeval *)icmp->icmp_data;tv_sub(&tvrecv,tvsend);/*接收和发送的时间差*/ rtt=tvrecv.tv_sec*1000+tvrecv.tv_usec/1000;/*以毫秒为单位计算rtt,显示相关信息*/ printf(“%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3f msn”, len, inet_ntoa(from.sin_addr), icmp->icmp_seq, ip->ip_ttl, rtt);} else return-1;} main(int argc,char *argv[]){ struct hostent *host;struct protoent *protocol;unsigned long inaddr=0l;int waittime=MAX_WAIT_TIME;int size=50*1024;if(argc
{ perror(“getprotobyname”);exit(1);} /*生成使用ICMP的原始套接字,这种套接字只有root才能生成*/ if((sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto))h_addr,host->h_length);} else /*是ip地址*/ memcpy((char *)&dest_addr,(char *)&inaddr,host->h_length);/*获取main的进程id,用于设置ICMP的标志符*/ pid=getpid();printf(“PING %s(%s): %d bytes data in ICMP packets.n”,argv[1], inet_ntoa(dest_addr.sin_addr),datalen);send_packet();/*发送所有ICMP报文*/ recv_packet();/*接收所有ICMP报文*/ statistics(SIGALRM);/*进行统计*/ return 0;} /*两个timeval结构相减*/ void tv_sub(struct timeval *out,struct timeval *in){ if((out->tv_usec-=in->tv_usec)tv_sec;out->tv_usec+=1000000;} out->tv_sec-=in->tv_sec;
}
7.程序运行的相关说明
在Windows中不能运行这样的程序,运行提示错误。在Linux中,只有root用户才能利用socket()函数生成原始套接字,要让Linux的一般用户能执行以上程序,需进行如下的特别操作:
用root登陆,编译以上程序:gcc-o myping myping.c,其目的有二:一是编译,二是让myping属于root用户。
再执行chmod u+s myping,目的是把myping程序设成SUID的属性。退出root,用一般用户登陆,执行./myping www.daodoc.com,有以下执行结果:
8.实验总结
这个实验是用WinSocket来模拟Ping命令的功能。程序通过分析ICMP报文的结构,在结构体中定义相应的字段来存储对应的信息。程序主要难点是定义对应的字段来存储报文的相应字段。程序通过send_packet();/*发送所有ICMP报文*/ recv_packet();/*接收所有ICMP报文*/函数来实现报文的发送和接收。
通过分析报文结构来提取报文中各字段代表的意义,这正好是与ICMP报文中的字段的报文头不同的段代表不同的意思是一样。
通过实验可以理解在网络中,报文的结构和网络传输协议。如: ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。这正好是网络中各层的相互关系。网络中数据通过ICMP数据报的数据的各个字段来判断路由,和选择虚电路。
9.参考文献
http://www.daodoc.com/ http://www.daodoc.com/ 17