




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
计算机网络课程设计指导书(11级计算机、网络工程专业)湖南科技大学计算机科学与工程学院2014年1月一、课程设计目的1.加深对计算机网络通信系统的工作原理的理解通过编写计算机程序实现、模拟网络的某些功能,使学生理解并掌握计算机网络的基本工作原理及工作过程。2.实现应用进程跨越网络的通信了解系统调用和应用编程接口基本知识,理解应用程序和操作系统之间传递控制权的机制,掌握套接字的创建和运用,通过socket系统调用实现跨网通信。3.提高网络编程和应用的能力提高实际编程能力和灵活运用所学知识解决问题的能力。培养调查研究、查阅技术文献、资料、手册以及编写技术文档的能力,理论应用于实践的能力。二、课程设计要求仔细分析每一个实验的具体内容、步骤和要求,按设计要求完成任务。2•使用C和C++语言,用基础SOCKET编程方法实现设计功能。程序设计需要完整的程序流程图、说明文档和源程序清单,设计者需要清楚每个模块的功能和原理。完成程序的编写、编译、执行和测试,每人至少完成三个题目的设计工作。提交课程设计报告(含括课程设计名称、课程设计题目、课程设计内容、课程设计步骤、调试过程、课程设计结果及结果分析、心得体会)、程序源文件、可执行文件各一份。三、课程设计考核方式测试中演示所设计的程序,占总成绩30%;测试中回答指导老师所提出的问题,占总成绩20%;设计报告,占总成绩40%;考勤情况,占总成绩10%。三、课程设计题目1、 网络聊天程序的设计与实现参照附录1,了解Socket通信的原理,在此基础上编写一个聊天程序。2、 Ping程序设计与实现参照附录2,了解Ping程序的实现原理,并调试通过。如有可能则在此基础上,编写一个可以测试本局域网的所有机器是否在线的程序。如图1所示的QuickPing程序。图1QuickPing运行界面效果图3、 基于IP多播的网络会议程序参照附录3的局域网IP多播程序,设计一个图形界面的网络会议程序(实现文本多播方式即可)。4、 网络嗅探器的设计与实现参照附录4rawsocket编程例子,设计一个可以监视网络的状态、数据流动情况以及网络上传输的信息的网络嗅探器。5、 电子邮件客户端程序设计与实现参照教材6.5节原理,设计一个电子邮件客户端程序。6、 TELNET终端设计与实现参照RFC854、RFC855文档,设计一个TELNET终端程序。7、网络代理服务器的设计与实现实现一个简易的proxy程序。proxy程序的功能:能够做“二传手”的工作。它自身处在能同时连通外界目标服务器和我的机器的位置上。我的机器把请求发送给它,它接受请求,把请求原封不动的抄下来发送给外界目标服务器;外界目标服务器响应了请求,把回答发送给它,它再接受回答,把回答原封不动的抄下来发送给我的机器。这样,我的机器实际上是把它当作了目标服务器(由于是原封不动的转抄,请求和回答没有被修改)。而它则是外界目标服务器的客户端。四、推荐参考文献[1]谢希仁,计算机网络(第五版),电子工业出版社,2008.AndrewS.Tanenbaum计算机网络(第四版)[M].北京:清华大学出版社,2004.中国Linux论坛:/UNIX技术网站(永远的UNIX):/Google/蒋清明,C语言程序设计,人民邮电出版社,2008.陈维兴,林小茶,C++面向对象程序设计(第二版),中国铁道出版社,2009年12月五、附录附录1:附录2:附录3:附录4:附录5:WindowsSocket编程简介PING源代码用VisualC++实现局域网IP多播rawsocket编程例子(基于LINUX操作系统)课程设计报告封面设计附录1、WindowsSocket编程简介使用WinSockAPI的编程,应该了解TCP/IP的基础知识。虽然你可以直接使用WinSockAPI来写网络应用程序,但是,要写出优秀的网络应用程序,还是必须对TCP/IP协议有一些了解的。TCP/IP协议与WinSock网络编程接口的关系WinSock并不是一种网络协议,它只是一个网络编程接口,也就是说,它不是协议,但是它可以访问很多种网络协议,你可以把它当作一些协议的封装。现在的WinSock已经基本上实现了与协议无关。你可以使用WinSock来调用多种协议的功能。那么,WinSock和TCP/IP协议到底是什么关系呢?实际上,WinSock就是TCP/IP协议的一种封装,你可以通过调用WinSock的接口函数来调用TCP/IP的各种功能•例如我想用TCP/IP协议发送数据,你就可以使用WinSock的接口函数Send()来调用TCP/IP的发送数据功能,至于具体怎么发送数据,WinSock已经帮你封装好了这种功能。2、 TCP/IP协议介绍TCP/IP协议包含的范围非常的广,它是一种四层协议,包含了各种硬件、软件需求的定义。TCP/IP协议确切的说法应该是TCP/UDP/IP协议。UDP协议(UserDatagramProtocol用户数据报协议),是一种保护消息边界的,不保障可靠数据的传输。TCP协议(TransmissionControlProtocol传输控制协议),是一种流传输的协议。他提供可靠的、有序的、双向的、面向连接的传输。保护消息边界,就是指传输协议把数据当作一条独立的消息在网上传输,接收端只能接收独立的消息。也就是说存在保护消息边界,接收端一次只能接收发送端发出的一个数据包。而面向流则是指无保护消息边界的,如果发送端连续发送数据,接收端有可能在一次接收动作中,会接收两个或者更多的数据包。举例来说,假如,我们连续发送三个数据包,大小分别是2k、4k、8k,这三个数据包都已经到达了接收端的网络堆栈中,如果使用UDP协议,不管我们使用多大的接收缓冲区去接收数据,我们必须有三次接收动作,才能够把所有的数据包接收完。而使用TCP协议,我们只要把接收的缓冲区大小设置在14k以上,我们就能够一次把所有的数据包接收下来,只需要有一次接收动作。这就是因为UDP协议的保护消息边界使得每一个消息都是独立的。而流传输,却把数据当作一串数据流,它不认为数据是一个一个的消息。所以有很多人在使用TCP协议通讯的时候,并不清楚TCP是基于流的传输,当连续发送数据的时候,他们时常会认识TCP会丢包。其实不然,因为当它们使用的缓冲区足够大时,它们有可能会一次接收到两个甚至更多的数据包,而很多人往往会忽视这一点,只解析检查了第一个数据包,而已经接收的其它据包却被忽略了。3・WinSock编程简单流程WinSock编程分为服务器端和客户端两部分,TCP服务器端的大体流程如下:对于任何基于WinSock的编程首先必须要初始化WinSockDLL库。intWSAStarup(WORDwVersionRequested,LPWSADATAlpWsAData)。wVersionRequested是我们要求使用的WinSock的版本。调用这个接口函数可以初始化WinSock。然后必须创建一个套接字(Socket)。SOCKETSocket(intaf,inttype,intprotocol);套接字可以说是WinSock通讯的核心oWinSock通讯的所有数据传输,都是通过套接字来完成的,套接字包含了两个信息,一个是IP地址,一个是Port端口号,使用这两个信息,就可以确定网络中的任何一个通讯节点。当调用了Socket()接口函数创建了一个套接字后,必须把套接字与你需要进行通讯的地址建立联系,可以通过绑定函数bind来实现这种联系。intbind(SOCKETs,conststructsockaddrFAR*name,intnamelen);structsockaddr_in{shortsin_family;u_shortsin_port;structin_addrsin_addr;charsin_sero[8];}就包含了需要建立连接的本地的地址,包括地址族、IP和端口信息。sin_family字段必须把它设为AF_INET,这是告诉WinSock使用的是IP地址族。sin_port就是要用来通讯的端口号。sin_addr就是要用来通讯的IP地址信息。在这里,必须还得提一下有关'大头(big-endian)'小头(little-endian)'。因为各种不同的计算机处理数据时的方法是不一样的,IntelX86处理器上是用'小头'形式来表示多字节的编号,就是把低字节放在前面,把高字节放在后面,而互联网标准却正好相反,所以,必须把主机字节转换成网络字节的顺序。WinSockAPI提供了几个函数。把主机字节转化成网络字节的函数;u_longhtonl(u_longhostlong);u_shorthtons(u_shorthostshort);把网络字节转化成主机字节的函数;u_longntohl(u_longnetlong);u_shortntohs(u_shortnetshort);这样,设置IP地址和port端口时,就必须把主机字节转化成网络字节后,才能用Bind()函数来绑定套接字和地址。当绑定完成之后,服务器端必须建立一个监听的队列来接收客户端的连接请求。intlisten(SOCKETs,intbacklog);这个函数可以把套接字转成监听模式。如果客户端有了连接请求,我们还必须使用intaccept(SOCKETs,structsockaddrFAR*addr,intFAR*addrlen);来接受客户端的请求。现在基本上已经完成了一个服务器的建立,而客户端的建立的流程则是初始化WinSock,然后创建Socket套接字,再使用intconnect(SOCKETs,conststructsockaddrFAR*name,intnamelen);来连接服务端。下面是一个最简单的创建服务器端和客户端的例子:服务器端的创建:WSADATAwsd;SOCKETsListen;SOCKETsclient;UINTport=800;intiAddrSize;structsockaddr_inlocal,client;WSAStartup(0x11,&wsd);sListen=Socket(AF_INET,SOCK_STREAM,IPPOTO_IP);local.sin_family=AF_INET;local.sin_addr=htonl(INADDR_ANY);local.sin_port=htons(port);bind(sListen,(structsockaddr*)&local,sizeof(local));listen(sListen,5);sClient=accept(sListen,(structsockaddr*)&client,&iAddrSize);客户端的创建:WSADATAwsd;SOCKETsClient;UINTport=800;charszIp[]="";intiAddrSize;structsockaddr_inserver;WSAStartup(0x11,&wsd);sClient=Socket(AF_INET,SOCK_STREAM,IPPOTO_IP);server.sin_family=AF_INET;server.sin_addr=inet_addr(szIp);server.sin_port=htons(port);connect(sClient,(structsockaddr*)&server,sizeof(server));当服务器端和客户端建立连接以后,无论是客户端,还是服务器端都可以使用intsend(SOCKETs,constcharFAR*buf,intlen,intflags);intrecv(SOCKETs,charFAR*buf,intlen,intflags);函数来接收和发送数据,因为,TCP连接是双向的。当要关闭通讯连接的时候,任何一方都可以调用intshutdown(SOCKETs,inthow);来关闭套接字的指定功能,再调用intcloseSocket(SOCKETs);来关闭套接字句柄,这样一个通讯过程就算完成了。计算机网络(第5版)280页图6-32所示的系统调用使用顺序注意:上面的代码没有任何检查函数返回值,如果你作网络编程就一定要检查任何一个WinSockAPI函数的调用结果,因为很多时候函数调用并不一定成功。上面介绍的函数,返回值类型是int的话,如果函数调用失败的话,返回的都是SOCKET_ERROR。4・VC中socket编程步骤sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW);基于TCP的socket编程是采用的流式套接字。在这个程序中,将两个工程添加到一个工作区。要链接一个ws2_32.1ib的库文件。服务器端编程的步骤:1:加载套接字库,创建套接字(WSAStartup()/socket());2:绑定套接字到一个IP地址和一个端口上(bind());3:将套接字设置为监听模式等待连接请求(listen。);4:请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());5:用返回的套接字和客户端进行通信(send()/recv());6:返回,等待另一连接请求;7:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。服务器端代码如下:#include<stdio.h>#include<Winsock2.h>voidmain(){WORDwVersionRequested;WSADATAwsaData;interr;wVersionRequested=MAKEWORD(1,1);err=WSAStartup(wVersionRequested,&wsaData);if(err!=0){return;}if(LOBYTE(wsaData.wVersion)!=1||HIBYTE(wsaData.wVersion)!=1){WSACleanup();return;}SOCKETsockSrv=socket(AF_INET,SOCK_STREAM,0);SOCKADDR_INaddrSrv;addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);addrSrv.sin_family=AF_INET;addrSrv.sin_port=htons(6000);bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));listen(sockSrv,5);SOCKADDR_INaddrClient;intlen=sizeof(SOCKADDR);while(1){SOCKETsockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);charsendBuf[50];sprintf(sendBuf,"Welcome%stohere!",inet_ntoa(addrClient.sin_addr));send(sockConn,sendBuf,strlen(sendBuf)+1,0);charrecvBuf[50];recv(sockConn,recvBuf,50,0);printf("%s\n",recvBuf);closesocket(sockConn);}}客户端编程的步骤:1:加载套接字库,创建套接字(WSAStartup()/socket());2:向服务器发出连接请求(connect。);3:和服务器端进行通信(send()/recv());4:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。客户端的代码如下:#include<stdio.h>#include<Winsock2.h>voidmain(){WORDwVersionRequested;WSADATAwsaData;interr;wVersionRequested=MAKEWORD(1,1);err=WSAStartup(wVersionRequested,&wsaData);if(err!=0){return;}if(LOBYTE(wsaData.wVersion)!=1||HIBYTE(wsaData.wVersion)!=1){WSACleanup();return;}SOCKETsockClient=socket(AF_INET,SOCK_STREAM,0);SOCKADDR_INaddrSrv;addrSrv.sin_addr.S_un.S_addr=inet_addr("");addrSrv.sin_family=AF_INET;addrSrv.sin_port=htons(6000);connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));send(sockClient,"hello",strlen("hello")+1,0);charrecvBuf[50];recv(sockClient,recvBuf,50,0);printf("%s\n",recvBuf);closesocket(sockClient);WSACleanup();}附录2、PING源代码//PING.H#defineWIN32_LEAN_AND_MEAN#include<windows.h>#include<winsock2.h>#include<ws2tcpip.h>#include<stdio.h>#include<stdlib.h>(2B)//sourceip(4B)(2B)//sourceip(4B)//destinationip(4B)//lengthoftotal(20B)typedefstructtagIP_HEADER{unsignedinth_len:4;unsignedintver:4;unsignedchartos;unsignedshorttotal_len;unsignedshortident;unsignedshortfrag_flags;unsignedcharttl;unsignedcharprotocol;unsignedshortchecksum;unsignedintsourceip;unsignedintdestip;}IP_HEADER,*PIP_HEADER;TOC\o"1-5"\h\z//lengthofheader(4b)//version(4b)//tos (1B)//totallength (2B)//identification(2B)//fragandflags(2B)//timeoflives (1B)//protocol (1B)//checksumtypedefstructtagIP_OPT_HEADER{unsignedcharcode;unsignedcharlen;unsignedcharptr;unsignedlongaddr[9];//optiontype (1B)//lengthofoptionheader(1B)//下一个可存放地址的位置 (1B)//listofipaddress(4B/d)}IP_OPT_HEADER,*PIP_OPT_HEADER;//lengthoftotal (39B)typedefstructtagICMP_HEADER{unsignedchartype;unsignedcharcode;unsignedshortchecksum;unsignedshortid;unsignedshortseq;unsignedlongtimestamp;//icmptype (1B)//codeoftype (1B)//checksum(2B)//identification(2B)//sequence(2B)//(2B)//thisisnotstandardheader,butwereservespacefortime//totallength (10B)}ICMP_HEADER,*PICMP_HEADER;//totallength (10B)#defineDEF_PACKET_SIZE32#defineMAX_PACKET_SIZE1024#defineICMP_ECHO8#define ICMP_ECHOREPLY 0#define IP_RECORD_ROUTER 7voidusageinfo(char*progname);voidFillIcmpData(char*icmp_data,intsize);USHORTCheckSum(USHORT*buf,intsize);voidDecodeIcmpHeader(char*buf,intret,LPSOCKADDR_INlpSin);voidDecodeIpHeader(char*buf,intbytes);//PING.CPP#include"Ping.h"intmain(intargc,char*argv[]){//error:return-1,noerr:return0if(argc==1){usageinfo(argv[0]);return-1;}BOOLbRecordRout=FALSE;SOCKEThSocket=INVALID_SOCKET;SOCKADDR_INdstSin;SOCKADDR_INfromSin;IP_OPT_HEADERipOptHeader;char*pIcmpData=NULL;char*pRecvData=NULL;char*lpDstIp=NULL;intdatasize=DEF_PACKET_SIZE;intret;intrcvNum;for(inti=1;i<argc;i++){if(strchr(argv[i],'-')){switch(tolower(argv[i][1])){case'r':bRecordRout=TRUE;break;case'd':datasize=atoi(argv[i+1]);i=argc+1;//toquittheforloopbreak;}
elseif(strchr(argv[i],'.')){intl=strlen(argv[i]);if(l<7||l>15)usageinfo(argv[0]);elselpDstIp=argv[i];}}//initializewinsock//WSADATAwsaData;WORDwVer=MAKEWORD(2,2);if(WSAStartup(wVer,&wsaData)!=0){printf("WSAStartupError!\n");return-1;//err,return-1,}//createsockethandlePPED);//hSocket=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED);if(hSocket==INVALID_SOCKET){printf("WSASocketError,Code:%d",WSAGetLastError());WSACleanup();return-1;}//setipoptionheader(recordrouter)if(bRecordRout){ZeroMemory(&ipOptHeader,sizeof(ipOptHeader));ipOptHeader.code=IP_RECORD_ROUTER;ipOptHeader.len=39;ipOptHeader.ptr=4;if((ret=setsockopt(hSocket,IPPROTO_IP,IP_OPTIONS,(char*)&ipOptHeader,sizeof(ipOptHeader)))==SOCKET_ERROR){printf("setsockopt(IP_OPTIONS)error,code:%d",WSAGetLastError());WSACleanup();closesocket(hSocket);return-1;}}//setsocketrecvandsendtimeout;inttimeout=1000;if((ret=setsockopt(hSocket,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout)))==SOCKET_ERROR){printf("setsockopt(SO_RCVTIMEO)error,code:%d",WSAGetLastError());WSACleanup();closesocket(hSocket);return-1;}if((ret=setsockopt(hSocket,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout)))==SOCKET_ERROR){printf("setsockopt(SO_SNDTIMEO)error,code:%d",WSAGetLastError());WSACleanup();return-1;}pIcmpData=(char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,MAX_PACKET_SIZE);pRecvData=(char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,MAX_PACKET_SIZE);if(pIcmpData==NULL||pRecvData==NULL){printf("HeapAllocError\n");WSACleanup();return-1;}datasize+=sizeof(ICMP_HEADER);ZeroMemory(&dstSin,sizeof(dstSin));dstSin.sin_family=AF_INET;dstSin.sin_addr.s_addr=inet_addr(lpDstIp);FillIcmpData(pIcmpData,datasize);printf("Ping%swith%dbytesofdata\n",inet_ntoa(dstSin.sin_addr),datasize);intcount=0;intseq=0;intrcvNum=0;while(1){count++;if(count==5)break;((PICMP_HEADER)pIcmpData)->checksum=0;((PICMP_HEADER)pIcmpData)->seq=seq++;((PICMP_HEADER)pIcmpData)->timestamp=GetTickCount();((PICMP_HEADER)pIcmpData)->checksum=CheckSum((USHORT*)pIcmpData,datasize);if((ret=sendto(hSocket,pIcmpData,datasize,0,(LPSOCKADDR)&dstSin,sizeof(dstSin)))==SOCKET_ERROR){if(WSAGetLastError()==WSAETIMEDOUT){printf("timeout.\n");continue;}else{printf("sendtoerror,code:%d",WSAGetLastError());closesocket(hSocket);WSACleanup();return-1;}}intfromLen=sizeof(fromSin);if((ret=recvfrom(hSocket,pRecvData,MAX_PACKET_SIZE,0,(sockaddr*)&fromSin,&fromLen))==SOCKET_ERROR){if(WSAGetLastError()==WSAETIMEDOUT){printf("timeout.\n");continue;}printf("recvformfail!\n");closesocket(hSocket);WSACleanup();return-1;}rcvNum++;DecodeIcmpHeader(pRecvData,ret,&fromSin);}printf("\nPingStatisticsfor:%s\n",lpDstIp);printf("\tSend=%d,Received=%d,Lost=%d(%d%%loss)",4,rcvNum,4-rcvNum,(4-rcvNum)/4*100);if(hSocket!=INVALID_SOCKET)closesocket(hSocket);HeapFree(GetProcessHeap(),0,pIcmpData);HeapFree(GetProcessHeap(),0,pRecvData);WSACleanup();return0;}voidusageinfo(char*progname){printf("Pingtool,byblode(blode@\n");printf("usage:ping[-r]<hostip>[-d][datasize]\n");printf("\t-r:\trecordrouter\n");printf("\thostip:\thostiptoping\n");printf("\t-d:\tusedatasizeoption\n");printf("\tdatasize:\tdatasizetoping(<=1024)\n");}voidFillIcmpData(char*icmp_data,intsize){ICMP_HEADER*icmpHdr;icmpHdr=(PICMP_HEADER)icmp_data;icmpHdr->checksum=0;icmpHdr->code=0;icmpHdr->id=(unsignedshort)GetCurrentProcessId();icmpHdr->seq=0;icmpHdr->type=ICMP_ECHO;icmpHdr->timestamp=0;}USHORTCheckSum(USHORT*buf,intsize){//checksumfunctionUSHORTcksum=0;while(size>1){cksum+=*buf++;size-=sizeof(USHORT);}if(size)cksum+=*buf++;cksum=(cksum>>16)+(cksum&0xffff);cksum+=(cksum>>16);return(USHORT)(~cksum);}voidDecodeIcmpHeader(char*buf,intret,LPSOCKADDR_INlpSin){ICMP_HEADER*icmpHdr;IP_HEADER*ipHdr;intipHdrLen;staticintfirst=0;DWORDtick=GetTickCount();ipHdr=(IP_HEADER*)buf;ipHdrLen=ipHdr->h_len*4;if(ipHdrLen==60&&!first)DecodeIpHeader(buf,ret);icmpHdr=(ICMP_HEADER*)(buf+ipHdrLen);if(icmpHdr->type!=ICMP_ECHOREPLY){printf("noechoreply%drecved\n",icmpHdr->type);return;}if(icmpHdr->id!=(USHORT)GetCurrentProcessId()){printf("someoneelse'spacket!\n");return;}printf("Replyfrom:%s",inet_ntoa(lpSin->sin_addr));printf("\tbytes:%dicmpseq:%dTTL=128",ret,icmpHdr->seq);printf("time:%dms\n",tick-icmpHdr->timestamp);first++;return;}voidDecodeIpHeader(char*buf,intbytes){IP_OPT_HEADER*ipOptHdr;IN_ADDRin;ipOptHdr=(IP_OPT_HEADER*)(buf+20);printf("RecordRouter:");for(inti=0;i<(ipOptHdr->ptr/4)-1;i++){in.S_un.S_addr=ipOptHdr->addr[i];printf("\t%-15s\n",inet_ntoa(in));}}附录3用VisualC++实现局域网IP多播在局域网中,管理员常常需要将某条信息发送给一组用户。如果使用一对一的发送方法,虽然是可行的,但是过于麻烦,也常会出现漏发、错发。为了更有效的解决这种组通信问题,出现了一种多播技术(也常称为组播通信),它是基于IP层的通信技术。为了帮助读者理解,下面将简要的介绍一下多播的概念。众所周知,普通ip通信是在一个发送者和一个接收者之间进行的,我们常把它称为点对点的通信,但对于有些应用,这种点对点的通信模式不能有效地满足实际应用的需求。例如:一个数字电话会议系统由多个会场组成,当在其中一个会场的参会人发言时,要求其它会场都能即时的得到此发言的内容,这是一个典型的一对多的通信应用,通常把这种一对多的通信称为多播通信。采用多播通信技术,不仅可以实现一个发送者和多个接收者之间进行通信的功能,而且可以有效减轻网络通信的负担,避免资源的无谓浪费。广播也是一种实现一对多数据通信的模式,但广播与多播在实现方式上有所不同。广播是将数据从一个工作站发出,局域网内的其他所有工作站都能收到它。这一特征适用于无连接协议,因为LAN上的所有机器都可获得并处理广播消息。使用广播消息的不利之处是每台机器都必须对该消息进行处理。多播通信则不同,数据从一个工作站发出后,如果在其它LAN上的机器上面运行的进程表示对这些数据"有兴趣",多播数据才会发给它们。本实例由Sender和Receiver两个程序组成,Sender用户从控制台上输入多播发送数据,Receiver端都要求加入同一个多播组,完成接收Sender发送的多播数据。一、实现方法1、 协议支持并不是所有的协议都支持多播通信,对Win32平台而言,仅两种可从WinSock内访问的协议(IP/ATM)才提供了对多播通信的支持。因通常通信应用都建立在TCP/IP协议之上的,所以本文只针对IP协议来探讨多播通信技术。支持多播通信的平台包括WindowsCE2.1、Windows95、Windows98、WindowsNT4、Windows2000和WindowsXP。自2.1版开始,WindowsCE才开始实现对IP多播的支持。本文实例建立在WindowsXP专业版平台上。2、 多播地址IP采用D类地址来支持多播。每个D类地址代表一组主机。共有28位可用来标识小组。所以可以同时有多达25亿个小组。当一个进程向一个D类地址发送分组时,会尽最大的努力将它送给小组的所有成员,但不能保证全部送到。有些成员可能收不到这个分组。举个例子来说,假定五个节点都想通过IP多播,实现彼此间的通信,它们便可加入同一个组地址。全部加入之后,由一个节点发出的任何数据均会一模一样地复制一份,发给组内的每个成员,甚至包括始发数据的那个节点。D类IP地址范围在到55之间。它分为两类:永久地址和临时地址。永久地址是为特殊用途而保留的。比如,根本没有使用(也不能使用),代表子网内的所有系统(主机),而代表子网内的所有路由器。在RFC1700文件中,提供了所有保留地址的一个详细清单。该文件是为特殊用途保留的所有资源的一个列表,大家可以找来作为参考。"Internet分配数字专家组"(IANA)负责着这个列表的维护。在表1中,我们总结了目前标定为"保留"的一些地址。临时组地址在使用前必须先创建,一个进程可以要求其主机加入特定的组,它也能要求其主机脱离该组。当主机上的最后一个进程脱离某个组后,该组地址就不再在这台主机中出现。每个主机都要记录它的进程当前属于哪个组。表1部分永久地址说明地址说明基本地址(保留)子网上的所有系统子网上的所有路由器子网上所有OSPF路由器子网上所有指定的OSPF路由器RIP第2版本组地址网络时间协议4WINS服务器组地址3、 多播路由器多播由特殊的多播路由器来实现,多播路由器同时也可以是普通路由器。各个多播路由器每分钟发送一个硬件多播信息给子网上的主机(目的地址为),要求它们报告其进程当前所属的是哪一组,各主机将它感兴趣的D类地址返回。这些询问和响应分组使用IGMP(Internetgroupmanagementprotocol),它大致类似于ICMPo它只有两种分组:询问和响应,都有一个简单的固定格式,其中有效载荷字段的第一个字段是一些控制信息,第二字段是一个D类地址,在RFC1112中有详细说明。多播路由器的选择是通过生成树实现的,每个多播路由器采用修改过的距离矢量协议和其邻居交换信息,以便向每个路由器为每一组构造一个覆盖所有组员的生成树。在修剪生成树及删除无关路由器和网络时,用到了很多优化方法。4.库支持WinSock提供了实现多播通信的API函数调用。针对IP多播,WinSock提供了两种不同的实现方法,具体取决于使用的是哪个版本的WinSock。第一种方法是WinSockl提供的,要求通过套接字选项来加入一个组;另一种方法是WinSock2提供的,它是引入一个新函数,专门负责多播组的加入,这个函数便是WSAJoinLeaf,它是基层协议无关的。本文将通过一个多播通信的实例的实现过程,来讲叙多播实现的主要步骤。因为Window98以后版本都安装了Winsock2.0以上版本,所以本文实例在WinSock2.0平台上开发的,但在其中对WinSockl实现不同的地方加以说明。二、编程步骤1、启动VisualC++6.0,创建一个控制台项目工程MultiCaseo在此项目工程中添加Sender和Receiver两个项目。Receiver项目实现步骤:⑴、创建一个SOCK_DGRAM类型的Socket。(2) 、将此Socket绑定到本地的一个端口上,为了接收服务器端发送的多播数据。(3) 、加入多播组。①、WinSock2中引入一个WSAJoinLeaf,此函数原型如下:SOCKETWSAJoinLeaf(SOCKETs,conststructsockaddrFAR*name,intnamelen,LPWSABUFlpCallerData,LPWSABUFlpCalleeData,LPQOSlpSQOS,LPQOSlpGQOS,DWORDdwFlags);其中,第一个参数s代表一个套接字句柄,是自WSASocket返回的。传递进来的这个套接字必须使用恰当的多播标志进行创建;否则的话WSAJoinLeaf就会失败,并返回错误WSAEINVAL。第二个参数是SOCKADDR(套接字地址)结构,具体内容由当前采用的协议决定,对于IP协议来说,这个地址指定的是主机打算加入的那个多播组。第三个参数namelen(名字长度)是用于指定name参数的长度,以字节为单位。第四个参数1pCallerData(呼叫者数据)的作用是在会话建立之后,将一个数据缓冲区传输给自己通信的对方。第五个参数1pCalleeData(被叫者数据)用于初始化一个缓冲区,在会话建好之后,接收来自对方的数据。注意在当前的Windows平台上,IpCallerData和IpCalleeData这两个参数并未真正实现,所以均应设为NULLoLpSQOS和lpGQOS这两个参数是有关Qos(服务质量)的设置,通常也设为NULL,有关Qos内容请参阅MSDN或有关书籍。最后一个参数dwFlags指出该主机是发送数据、接收数据或收发兼并。该参数可选值分别是:JL_SENDER_ONLY、JL_RECEIVER_ONLY或者JL_BOTH。②、在WinSockl平台上加入多播组需要调用setsockopt函数,同时设置IP_ADD_MEMBERSHIP选项,指定想加入的那个组的地址结构。具体实现代码将在下面代码注释列出。(4)、接收多播数据。Sender实现步骤:⑴、创建一个SOCK_DGRAM类型的Socket。、加入多播组。、发送多播数据。3、编译两个项目,在局域网中按如下步骤测试:、将Sender.exe拷贝到发送多播数据的PC上。、将Receiver.exe拷贝到多个要求接收多播数据的PC上。(3)、各自运行相应的程序。(4)、在SenderPC上输入多播数据后,你就可以在ReceiverPC上看到输入的多播数据。//sender.cpp#include<winsock2.h>#include<ws2tcpip.h>#include<stdio.h>#include<stdlib.h>#defineMCASTADDR""//本例使用的多播组地址。#defineMCASTPORT5150//本地端口号。#defineBUFSIZE1024//发送数据缓冲大小。intmain(intargc,char**argv){WSADATAwsd;structsockaddr_inremote;SOCKETsock,sockM;TCHARsendbuf[BUFSIZE];intlen=sizeof(structsockaddr_in);//初始化WinSock2.2if(WSAStartup(MAKEWORD(2,2),&wsd)!=0){printf("WSAStartup()failed\n");return-1;}if((sock=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF|WSA_FLAG_OVERLAPPED))==INVALID_SOCKET){printf("socketfailedwith:%d\n",WSAGetLastError());WSACleanup();return-1;}//加入多播组remote.sin_family=AF_INET;remote.sin_port=htons(MCASTPORT);remote.sin_addr.s_addr=inet_addr(MCASTADDR);if((sockM=WSAJoinLeaf(sock,(SOCKADDR*)&remote,sizeof(remote),NULL,NULL,NULL,NULL,JL_BOTH))==INVALID_SOCKET){printf("WSAJoinLeaf()failed:%d\n",WSAGetLastError());closesocket(sock);WSACleanup();return-1;〃发送多播数据,当用户在控制台输入"QUIT"时退出。while(1){printf("SEND:");scanf("%s",sendbuf);if(sendto(sockM,(char*)sendbuf,strlen(sendbuf),0,(structsockaddr*)&remote,sizeof(remote))==SOCKET_ERROR){printf("sendtofailedwith:%d\n",WSAGetLastError());closesocket(sockM);closesocket(sock);WSACleanup();return-1;}if(strcmp(sendbuf,"QUIT")==0)break;Sleep(500);}closesocket(sockM);closesocket(sock);WSACleanup();return0;}//receiver.cpp#include<winsock2.h>#include<ws2tcpip.h>#include<stdio.h>#include<stdlib.h>#defineMCASTADDR""//本例使用的多播组地址。#defineMCASTPORT5150//绑定的本地端口号。#defineBUFSIZE1024//接收数据缓冲大小。intmain(intargc,char**argv){WSADATAwsd;structsockaddr_inlocal,remote,from;SOCKETsock,sockM;TCHARrecvbuf[BUFSIZE];/*structip_mreqmcast;//Winsock1.0*/intlen=sizeof(structsockaddr_in);intret;//初始化WinSock2.2if(WSAStartup(MAKEWORD(2,2),&wsd)!=0){printf("WSAStartup()failed\n");return-1;}/*创建一个SOCK_DGRAM类型的SOCKET其中,WSA_FLAG_MULTIPOINT_C_LEAF表示IP多播在控制面层上属于"无根"类型;WSA_FLAG_MULTIPOINT_D_LEAF表示IP多播在数据面层上属于"无根",有关控制面层和数据面层有关概念请参阅MSDN说明。*/if((sock=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF|WSA_FLAG_OVERLAPPED))==INVALID_SOCKET){printf("socketfailedwith:%d\n",WSAGetLastError());WSACleanup();return-1;}//将sock绑定到本机某端口上。local.sin_family=AF_INET;local.sin_port=htons(MCASTPORT);local.sin_addr.s_addr=INADDR_ANY;if(bind(sock,(structsockaddr*)&local,sizeof(local))==SOCKET_ERROR){printf("bindfailedwith:%d\n",WSAGetLastError());closesocket(sock);WSACleanup();return-1;}//加入多播组remote.sin_family=AF_INET;remote.sin_port=htons(MCASTPORT);remote.sin_addr.s_addr=inet_addr(MCASTADDR);/*Winsock1.0*//*mcast.imr_multiaddr.s_addr=inet_addr(MCASTADDR);mcast.imr_interface.s_addr=INADDR_ANY;if(setsockopt(sockM,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char*)&mcast,sizeof(mcast))==SOCKET_ERROR){printf("setsockopt(IP_ADD_MEMBERSHIP)failed:%d\n",WSAGetLastError());closesocket(sockM);WSACleanup();return-1;}*//*Winsock2.0*/if((sockM=WSAJoinLeaf(sock,(SOCKADDR*)&remote,sizeof(remote),NULL,NULL,NULL,NULL,JL_BOTH))==INVALID_SOCKET){printf("WSAJoinLeaf()failed:%d\n",WSAGetLastError());closesocket(sock);WSACleanup();return-1;}〃接收多播数据,当接收到的数据为"QUIT"时退出。while(1){if((ret=recvfrom(sock,recvbuf,BUFSIZE,0,(structsockaddr*)&from,&len))==SOCKET_ERROR){printf("recvfromfailedwith:%d\n",WSAGetLastError());closesocket(sockM);closesocket(sock);WSACleanup();return-1;}if(strcmp(recvbuf,"QUIT")==0)break;else{recvbuf[ret]='\0';printf("RECV:'%s'FROM<%s>\n",recvbuf,inet_ntoa(from.sin_addr));}}closesocket(sockM);closesocket(sock);WSACleanup();return0;}附录4:rawsocket编程例子1原始套接字工作原理与规则原始套接字是一种不同于SOCK_STREAM和SOCK_DGRAM的套接字,它实现于系统核心。它的创建方式跟TCP/UDP创建方法几乎是一模一样,例如,通过intsockfd;sockfd=socktet(AF_INET,SOCK_RAW,IPPROTO_ICMP);这两句程序你就可以创建一个原始套接字。这种类型套接字的功能与TCP或者UDP类型套接字的功能有很大的不同:TCP/UDP类型的套接字只能够访问传输层以及传输层以上的数据,因为当IP层把数据传递给传输层时,下层的数据包头已经被丢掉了。而原始套接字却可以访问传输层以下的数据,所以使用raw套接字你可以实现上至应用层的数据操作,也可以实现下至链路层的数据操作。比如:通过sock=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_IP))方式创建的rawsocket就能直接读取链路层的数据。使用原始套接字时应该注意的问题(参考vvunix网络编程>>以及网上的优秀文档):对于UDP/TCP产生的IP数据包,内核不将它传递给任何原始套接字,而只是将这些数据交给对应的UDP/TCP数据处理句柄(所以,如果你想要通过原始套接字来访问TCP/UDP或者其它类型的数据,调用socket函数创建原始套接字第三个参数应该指定为htons(ETH_P_IP),也就是通过直接访问数据链路层来实现。(我们后面的密码窃取器就是基于这种类型的)。:对于ICMP和EGP等使用IP数据包承载数据但又在传输层之下的协议类型的IP数据包,内核不管是否已经有注册了的句柄来处理这些数据,都会将这些IP数据包复制一份传递给协议类型匹配的原始套接字。:对于不能识别协议类型的数据包,内核进行必要的校验,然后会查看是否有类型匹配的原始套接字负责处理这些数据,如果有的话,就会将这些IP数据包复制一份传递给匹配的原始套接字,否则,内核将会丢弃这个IP数据包,并返回一个ICMP主机不可达的消息给源主机。:如果原始套接字bind绑定了一个地址,核心只将目的地址为本机IP地址的数包传递给原始套接字,如果某个原始套接字没有bi
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- T-ZZB 3589-2023 洁净转子泵标准
- 2025年度矿山新能源利用合作开发协议
- 二零二五年度船舶租赁与船舶融资租赁合同
- 二零二五年度金融产品创新增资协议
- 2025年度酒店品牌授权及加盟合作协议
- 二零二五年度有机果园使用权及品牌授权合同
- 二零二五美容院转让合同包含员工培训体系与职业发展规划
- 2025年度旅游度假区合租商铺合作协议
- 二零二五年度知识产权标准化与认证顾问合同
- 二零二五年度科技园区出租房承包管理协议
- 2025年湖南铁路科技职业技术学院单招职业技能测试题库参考答案
- 2025年黑龙江林业职业技术学院单招职业适应性测试题库带答案
- 《ISO 56000-2025创新管理 基础和术语》之1:“引言+范围+术语和定义”专业深度解读与应用指导材料(雷泽佳编写2025A0)-1-150
- DB37-T4817-2025 沥青路面就地冷再生技术规范
- 2025年公共营养师三级理论试题及答案
- 提高设备基础预埋螺栓一次安装合格率
- 煤矿防治水安全质量标准化评分表
- 2024年科技节小学科普知识竞赛题及答案(共100题)
- 2025年度教育培训机构学生综合素质评价协议3篇
- 氧气管道吹扫、打压方案
- 第28课 改革开放和社会主义现代化建设的巨大成就 教学设计(表格式)必修 中外历史纲要(上)
评论
0/150
提交评论