下载

2下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 1492410

1492410.doc

1492410

mingche_god
2018-09-09 0人阅读 举报 0 0 0 暂无简介

简介:本文档为《1492410doc》,可适用于IT/计算机领域

║嵌入式应用程序开发详解第章嵌入式Linux网络编程║第章嵌入式Linux网络编程本章目标  本章将介绍嵌入式Linux网络编程的基础知识。由于网络在嵌入式中的应用非常广泛基本上常见的应用都会与网络有关因此掌握这一部分的内容是非常重要的。经过本章的学习读者将会掌握以下内容。掌握TCPIP协议的基触知识掌握嵌入式Linux基础网络编程掌握嵌入式Linux高级网络编程分析理解Ping源代码能够独立编写客户端、服务器端的通信程序能够独立编写NTP协议实现程序TCPIP协议概述OSI参考模型及TCPIP参考模型读者一定都听说过著名的OSI协议参考模型它是基于国际标准化组织(ISO)的建议发展起来的从上到下共分为层:应用层、表示层、会话层、传输层、网络层、数据链路层及物理层。这个层的协议模型虽然规定得非常细致和完善但在实际中却得不到广泛的应用其重要的原因之一就在于它过于复杂。但它仍是此后很多协议模型的基础这种分层架构的思想在很多领域都得到了广泛的应用。与此相区别的TCPIP协议模型从一开始就遵循简单明确的设计思路它将TCPIP的层协议模型简化为层从而更有利于实现和使用。TCPIP的协议参考模型和OSI协议参考模型的对应关系如下图所示。下面分别对者TCPIP的层模型进行简要介绍。图OSI模型和TCPIP参考模型对应关系(网络接口层:负责将二进制流转换为数据帧并进行数据帧的发送和接收。要注意的是数据帧是独立的网络信息传输单元。(网络层:负责将数据帧封装成IP数据报并运行必要的路由算法。(传输层:负责端对端之间的通信会话连接与建立。传输协议的选择根据数据传输方式而定。(应用层:负责应用程序的网络访问这里通过端口号来识别各个不同的进程。TCPIP协议族虽然TCPIP名称只包含了两个协议但实际上TCPIP是一个庞大的协议族它包括了各个层次上的众多协议图列举了各层中一些重要的协议并给出了各个协议在不同层次中所处的位置如下。(ARP:用于获得同一物理网络中的硬件主机地址。(MPLS:多协议标签协议是很有发展前景的下一代网络协议。(IP:负责在主机和网络之间寻址和路由数据包。(ICMP:用于发送报告有关数据包的传送错误的协议。(IGMP:被IP主机用来向本地多路广播路由器报告主机组成员的协议。(TCP:为应用程序提供可靠的通信连接。适合于一次传输大批数据的情况。并适用于要求得到响应的应用程{perror("listen")exit()}printf("listeningn")*调用fcntl函数设置非阻塞参数*if((flags=fcntl(sockfd,FSETFL,))<)perror("fcntlFSETFL")flag|=ONONBLOCKif(fcntl(fd,FSETEL,flags)<)perror("fcntl")while(){sinsize=sizeof(structsockaddrin)if((clientfd=accept(sockfd,(structsockaddr*)clientsockaddr,sinsize))==(){perror("accept")exit()}if((recvbytes=recv(clientfd,buf,MAXDATASIZE,))==(){perror("recv")exit()}if(read(clientfd,buf,MAXDATASIZE)<){perror("read")exit()}printf("receivedaconnection:s",buf)close(clientfd)exit()}*while*}运行该程序结果如下所示:root(none)tmp#fcntlsocketsuccess!,sockfd=bindsuccess!listeningaccept:Resourcetemporarilyunavailable可以看到当accept的资源不可用时程序就会自动返回。.select使用fcntl函数虽然可以实现非阻塞IO或信号驱动IO但在实际使用时往往会对资源是否准备完毕进行循环测试这样就大大增加了不必要的CPU资源。在这里可以使用select函数来解决这个问题同时使用select函数还可以设置等待的时间可以说功能更加强大。下面是使用select函数的服务器端源代码:*selectsocketc*#include<systypesh>#include<syssocketh>#include<syswaith>#include<stdioh>#include<stdlibh>#include<errnoh>#include<stringh>#include<sysunh>#include<systimeh>#include<sysioctlh>#include<unistdh>#include<netinetinh>#defineSERVPORT#defineBACKLOG#defineMAXCONNECTEDNO#defineMAXDATASIZEintmain(){structsockaddrinserversockaddr,clientsockaddrintsinsize,recvbytesfdsetreadfdfdsetwritefdintsockfd,clientfdcharbufMAXDATASIZEif((sockfd=socket(AFINET,SOCKSTREAM,))==(){perror("socket")exit()}printf("socketsuccess!,sockfd=dn",sockfd)serversockaddrsinfamily=AFINETserversockaddrsinport=htons(SERVPORT)serversockaddrsinaddrsaddr=INADDRANYbzero((serversockaddrsinzero),)if(bind(sockfd,(structsockaddr*)serversockaddr,sizeof(structsockaddr))==(){perror("bind")exit()}printf("bindsuccess!n")if(listen(sockfd,BACKLOG)==(){perror("listen")exit()}printf("listeningn")*将调用socket函数的描述符作为文件描述符*FDZERO(readfd)FDSET(sockfd,readfd)while(){sinsize=sizeof(structsockaddrin)*调用select函数*if(select(MAXCONNECTEDNO,readfd,,,(structtimeval*))>){if(FDISSET(sockfd,readfd)>){if((clientfd=accept(sockfd,(structsockaddr*)clientsockaddr,sinsize))==(){perror("accept")exit()}if((recvbytes=recv(clientfd,buf,MAXDATASIZE,))==(){perror("recv")exit()}if(read(clientfd,buf,MAXDATASIZE)<){perror("read")exit()}printf("receivedaconnection:s",buf)}*if*close(clientfd)}*select*}*while*}运行该程序时可以先启动服务器端再反复运行客户端程序即可服务器端运行结果如下所示:root(none)tmp#serversocketsuccess!,sockfd=bindsuccess!listeningreceivedaconnection:helloreceivedaconnection:helloping源码分析ping简介Ping是网络中应用非常广泛的一个软件它是基于ICMP协议的。下面首先对ICMP协议做一简单介绍。ICMP是IP层的一个协议它是用来探测主机、路由维护、路由选择和流量控制的。ICMP报文的最终报宿不是报宿计算机上的一个用户进程而是那个计算机上的IP层软件。也就是说当一个带有错误信息的ICMP报文到达时IP软件模块就处理本身问题而不把这个ICMP报文传送给应用程序。ICMP报文类型有:回送(ECHO)回答()报宿不可到达()报源断开()重定向(改变路由)()回送(ECHO)请求()数据报超时()数据报参数问题()时间印迹请求()时间印迹回答()信息请求()信息回答()地址掩码请求()地址掩码回答()。虽然每种报文都有不同的格式但它们开始都有下面三段:(一个位整数报文TYPE(类型)段(一个位CODE(代码码)段提供更多的报文类型信息(一个位CHECKSUM(校验和)段此外报告差错的ICMP报文还包含产生问题数据报的网际报头及前位数据。一个ICMP回送请求与回送回答报文的格式如表所示。表ICMP回送请求与回送回答报文格式类型CODE校验和CHECKSUM标识符序列号数据ping源码分析下面的pingc源码是在busybox里实现的源码。在这个完整的pingc代码中有较多选项的部分代码因此这里先分析除去选项部分代码的函数实现部分流程接下来再给出完整的ping代码分析。这样读者就可以看到一个完整协议实现应该考虑到的各个部分。.Ping代码主体流程Pingc主体流程图如下图所示。另外由于ping是IP层的协议因此在建立socket时需要使用SOCKRAW选项。在循环等待回应信息处用户可以指定“f”洪泛选项这时就会使用select函数来指定在一定的时间内进行回应。.主要选项说明Ping函数主要有以下几个选项:(d:调试选项(FSODEBUG)(f:洪泛选项(FFLOOD)(i:等待选项(FINTERVAL)(r:路由选项(FRROUTE)(l:广播选项(MULTICASTNOLOOP)对于这些选项尤其是路由选项、广播选项和洪泛选项都会有不同的实现代码。另外ping函数可以接受用户使用的SIGINT和SIGALARM信号来结束程序它们分别指向了不同的结束代码请读者阅读下面相关代码。图ping主体流程图.源代码及注释()主体代码ping代码的主体部分可以四部分首先是一些头函数及宏定义:#include<sysparamh>#include<syssocketh>#include<sysfileh>#include<systimeh>#include<syssignalh>#include<netinetinh>#include<netinetiph>#include<netinetipicmph>#include<arpaineth>#include<netdbh>#include<unistdh>#include<stdlibh>#include<stringh>#include<stdioh>#include<ctypeh>#include<errnoh>#include<getopth>#include<resolvh>#defineFFLOODx#defineFINTERVALx#defineFNUMERICx#defineFPINGFILLEDx#defineFQUIETx#defineFRROUTEx#defineFSODEBUGx#defineFSODONTROUTEx#defineFVERBOSEx*多播选项*intmoptions#defineMULTICASTNOLOOPx#defineMULTICASTTTLx#defineMULTICASTIFx…接下来的第部分是建立socket并处理选项:Intmain(intargc,char*argv){structtimevaltimeoutstructhostent*hpstructsockaddrin*tostructprotoent*protostructinaddrifaddrintiintch,fdmask,hold,packlen,preloaduchar*datap,*packetchar*target,hnamebufMAXHOSTNAMELENucharttl,loopintamiroot…staticchar*=*environ=*amiroot=(getuid()==)**建立socket连接并且测试是否是root用户*if((s=socket(AFINET,SOCKRAW,IPPROTOICMP))<){if(errno==EPERM){fprintf(stderr,"ping:pingmustrunasrootn")}elseperror("ping:socket")exit()}…preload=datap=outpacksizeof(structtimeval)while((ch=getopt(argc,argv,"I:LRc:dfh:i:l:np:qrs:t:v"))!=EOF)switch(ch){case'c':npackets=atoi(optarg)if(npackets<=){(void)fprintf(stderr,"ping:badnumberofpacketstotransmitn")exit()}break*调用选项*case'd':options|=FSODEBUGbreak*flood选项*case'f':if(!amiroot){(void)fprintf(stderr,"ping:sn",strerror(EPERM))exit()}options|=FFLOODsetbuf(stdout,)break*等待选项*case'i':*waitbetweensendingpackets*interval=atoi(optarg)if(interval<=){(void)fprintf(stderr,"ping:badtimingintervaln")exit()}options|=FINTERVALbreakcase'l':if(!amiroot){(void)fprintf(stderr,"ping:sn",strerror(EPERM))exit()}preload=atoi(optarg)if(preload<){(void)fprintf(stderr,"ping:badpreloadvaluen")exit()}break…default:usage()}argc=optindargv=optindif(argc!=)usage()target=*argv接下来的第部分是用于获取地址这里主要使用了inetaton函数将点分十进制地址转化为二进制地址。当然作为完整的ping程序有较完善的出错处理:memset(whereto,,sizeof(structsockaddr))to=(structsockaddrin*)wheretoto>sinfamily=AFINET*地址转换函数*if(inetaton(target,to>sinaddr)){hostname=target}else{#ifchar*addr=resolvename(target,)if(!addr){(void)fprintf(stderr,"ping:unknownhostsn",target)exit()}to>sinaddrsaddr=inetaddr(addr)hostname=target#else*调用gethostbyname识别主机名*hp=gethostbyname(target)if(!hp){(void)fprintf(stderr,"ping:unknownhostsn",target)exit()}to>sinfamily=hp>haddrtypeif(hp>hlength>(int)sizeof(to>sinaddr)){hp>hlength=sizeof(to>sinaddr)}memcpy(to>sinaddr,hp>haddr,hp>hlength)(void)strncpy(hnamebuf,hp>hname,sizeof(hnamebuf))hostname=hnamebuf#endif}接下来的一部分主要是对各个选项(如路由、多播)的处理这里就不做介绍了。再接下来是ping函数的最主要部分就是接收无限循环回应信息这里主要用到了函数recvfrom。另外对用户中断信息也有相应的处理如下所示:if(to>sinfamily==AFINET)(void)printf("PINGs(s):ddatabytesn",hostname,inetntoa(*(structinaddr*)to>sinaddrsaddr),datalen)else(void)printf("PINGs:ddatabytesn",hostname,datalen)*若程序接收到SIGINT或SIGALRM信号调用相关的函数*(void)signal(SIGINT,finish)(void)signal(SIGALRM,catcher)…*循环等待客户端的回应信息*for(){structsockaddrinfromregisterintccintfromlenif(optionsFFLOOD){*形成ICMP回应数据包在后面会有讲解*pinger()*设定等待实践*timeouttvsec=timeouttvusec=fdmask=<<s*调用select函数*if(select(s,(fdset*)fdmask,(fdset*),(fdset*),timeout)<)continue}fromlen=sizeof(from)*接收客户端信息*if((cc=recvfrom(s,(char*)packet,packlen,,(structsockaddr*)from,fromlen))<){if(errno==EINTR)continueperror("ping:recvfrom")continue}prpack((char*)packet,cc,from)if(npacketsnreceived>=npackets)break}finish()*NOTREACHED*return}()其他函数下面的函数也是ping程序中用到的重要函数。首先catcher函数是用户在发送SIGINT时调用的函数在该函数中又调用了SIGALARM信号的处理来结束程序。staticvoidcatcher(intignore){intwaittime(void)ignorepinger()*调用catcher函数*(void)signal(SIGALRM,catcher)if(!npackets||ntransmitted<npackets)alarm((uint)interval)else{if(nreceived){waittime=*tmaxif(!waittime)waittime=if(waittime>MAXWAIT)waittime=MAXWAIT}elsewaittime=MAXWAIT*调用finish函数并设定一定的等待实践*(void)signal(SIGALRM,finish)(void)alarm((uint)waittime)}}Pinger函数也是一个非常重要的函数用于形成ICMP回应数据包其中ID是该进程的ID数据段中的前字节用于存放时间间隔从而可以计算ping程序从对端返回的往返时延差这里的数据校验用到了后面定义的incksum函数。其代码如下所示:staticvoidpinger(void){registerstructicmphdr*icpregisterintccinti*形成icmp信息包填写icmphdr结构体中的各项数据*icp=(structicmphdr*)outpackicp>icmptype=ICMPECHOicp>icmpcode=icp>icmpcksum=icp>icmpseq=ntransmittedicp>icmpid=ident*ID*CLR(icp>icmpseqmxdupck)*设定等待实践*if(timing)(void)gettimeofday((structtimeval*)outpack,(structtimezone*))cc=datalen*skipsICMPportion**computeICMPchecksumhere*icp>icmpcksum=incksum((ushort*)icp,cc)i=sendto(s,(char*)outpack,cc,,whereto,sizeof(structsockaddr))if(i<||i!=cc){if(i<)perror("ping:sendto")(void)printf("ping:wrotesdchars,ret=dn",hostname,cc,i)}if(!(optionsFQUIET)optionsFFLOOD)(void)write(STDOUTFILENO,DOT,)}prpack是数据包显示函数分别打印出IP数据包部分和ICMP回应信息。在规范的程序中通常将数据的显示部分独立出来这样就可以很好地加强程序的逻辑性和结构性。voidprpack(char*buf,intcc,structsockaddrin*from){registerstructicmphdr*icpregisterintiregisteruchar*cp,*dp*#if*registerulonglregisterintjstaticintoldrrlenstaticcharoldrrMAXIPOPTLEN*#endif*structiphdr*ipstructtimevaltv,*tplongtriptime=inthlen,dupflag(void)gettimeofday(tv,(structtimezone*))*检查IP数据包头信息*ip=(structiphdr*)bufhlen=ip>iphl<<if(cc<datalenICMPMINLEN){if(optionsFVERBOSE)(void)fprintf(stderr,"ping:packettooshort(dbytes)fromsn",cc,inetntoa(*(structinaddr*)from>sinaddrsaddr))return}*ICMP部分显示*cc=hlenicp=(structicmphdr*)(bufhlen)if(icp>icmptype==ICMPECHOREPLY){if(icp>icmpid!=ident)return*'TwasnotourECHO*nreceivedif(timing){#ifndeficmpdatatp=(structtimeval*)(icp)#elsetp=(structtimeval*)icp>icmpdata#endiftvsub(tv,tp)triptime=tvtvsec*(tvtvusec)tsum=triptimeif(triptime<tmin)tmin=triptimeif(triptime>tmax)tmax=triptime}if(TST(icp>icmpseqmxdupck)){nrepeatsnreceiveddupflag=}else{SET(icp>icmpseqmxdupck)dupflag=}if(optionsFQUIET)returnif(optionsFFLOOD)(void)write(STDOUTFILENO,BSPACE,)else{(void)printf("dbytesfroms:icmpseq=u",cc,inetntoa(*(structinaddr*)from>sinaddrsaddr),icp>icmpseq)(void)printf("ttl=d",ip>ipttl)if(timing)(void)printf("time=ldldms",triptime,triptime)if(dupflag)(void)printf("(DUP!)")*checkthedata*#ifndeficmpdatacp=((uchar*)(icp))#elsecp=(uchar*)icp>icmpdata#endifdp=outpacksizeof(structtimeval)for(i=i<dataleni,cp,dp){if(*cp!=*dp){(void)printf("nwrongdatabyte#dshouldbexxbutwasxx",i,*dp,*cp)cp=(uchar*)(icp)for(i=i<dataleni,cp){if((i)==)(void)printf("nt")(void)printf("x",*cp)}break}}}}else{*We'vegotsomethingotherthananECHOREPLY*if(!(optionsFVERBOSE))return(void)printf("dbytesfroms:",cc,praddr(from>sinaddrsaddr))pricmph(icp)}*#if**显示其他IP选项*cp=(uchar*)bufsizeof(structiphdr)for(hlen>(int)sizeof(structiphdr)hlen,cp)switch(*cp){caseIPOPTEOL:hlen=breakcaseIPOPTLSRR:(void)printf("nLSRR:")hlen=j=*cpcpif(j>IPOPTMINOFF)for(){l=*cpl=(l<<)*cpl=(l<<)*cpl=(l<<)*cpif(l==)(void)printf("t")else(void)printf("ts",praddr(ntohl(l)))hlen=j=if(j<=IPOPTMINOFF)break(void)putchar('n')}breakcaseIPOPTRR:j=*cp*getlength*i=*cp*andpointer*hlen=if(i>j)i=ji=IPOPTMINOFFif(i<=)continueif(i==oldrrlencp==(uchar*)bufsizeof(structiphdr)!memcmp((char*)cp,oldrr,i)!(optionsFFLOOD)){(void)printf("t(sameroute)")i=((i))*hlen=icp=ibreak}oldrrlen=imemcpy(oldrr,cp,i)(void)printf("nRR:")for(){l=*cpl=(l<<)*cpl=(l<<)*cpl=(l<<)*cpif(l==)(void)printf("t")else(void)printf("ts",praddr(ntohl(l)))hlen=i=if(i<=)break(void)putchar('n')}breakcaseIPOPTNOP:(void)printf("nNOP")breakdefault:(void)printf("nunknownoptionx",*cp)break}*#endif*if(!(optionsFFLOOD)){(void)putchar('n')(void)fflush(stdout)}}incksum是数据校验程序如下所示:staticintincksum(ushort*addr,intlen){registerintnleft=lenregisterushort*w=addrregisterintsum=ushortanswer=*这里的算法很简单就采用bit的加法*while(nleft>){sum=*wnleft=}if(nleft==){*(uchar*)(answer)=*(uchar*)wsum=answer}*把高bit加到低bit上去*sum=(sum>>)(sumxffff)sum=(sum>>)answer=~sumreturn(answer)}Finish程序是ping程序的结束程序主要是打印出来一些统计信息如下所示:staticvoidfinish(intignore){(void)ignore(void)signal(SIGINT,SIGIGN)(void)putchar('n')(void)fflush(stdout)(void)printf("spingstatisticsn",hostname)(void)printf("ldpacketstransmitted,",ntransmitted)(void)printf("ldpacketsreceived,",nreceived)if(nrepeats)(void)printf("ldduplicates,",nrepeats)if(ntransmitted)if(nreceived>ntransmitted)(void)printf("somebody'sprintinguppackets!")else(void)printf("dpacketloss",(int)(((ntransmittednreceived)*)ntransmitted))(void)putchar('n')if(nreceivedtiming)(void)printf("roundtripminavgmax=ldldluldldldmsn",tmin,tmin,(tsum(nreceivednrepeats)),(tsum(nreceivednrepeats)),tmax,tmax)if(nreceived==)exit()exit()}#ifdefnotdefstaticchar*ttab={"EchoReply",*ipsequdata*"DestUnreachable",*net,host,proto,port,frag,srIP*"SourceQuench",*IP*"Redirect",*redirect类型,gateway,IP*"Echo","TimeExceeded",*传输超时*"ParameterProblem",*IP参数问题*"Timestamp",*idseqthreetimestamps*"TimestampReply",*"*"InfoRequest",*idsq*"InfoReply"*"*}#endifpricmph函数是用于打印ICMP的回应信息如下所示:staticvoidpricmph(structicmphdr*icp){switch(icp>icmptype){*ICMP回应*caseICMPECHOREPLY:(void)printf("EchoReplyn")*XXXIDSeqData*break*ICMP终点不可达*caseICMPDESTUNREACH:switch(icp>icmpcode){caseICMPNETUNREACH:(void)printf("DestinationNetUnreachablen")breakcaseICMPHOSTUNREACH:(void)printf("DestinationHostUnreachablen")breakcaseICMPPROTUNREACH:(void)printf("DestinationProtocolUnreachablen")break…default:(void)printf("DestUnreachable,UnknownCode:dn",icp>icmpcode)break}*PrintreturnedIPheaderinformation*#ifndeficmpdataprretip((structiphdr*)(icp))#elseprretip((structiphdr*)icp>icmpdata)#endifbreak…default:(void)printf("Redirect,BadCode:d",icp>icmpcode)break}(void)printf("(Newaddr:s)n",inetntoa(icp>icmpgwaddr))#ifndeficmpdataprretip((structiphdr*)(icp))#elseprretip((structiphdr*)icp>icmpdata)#endifbreakcaseICMPECHO:(void)printf("EchoRequestn")*XXXIDSeqData*breakcaseICMPTIMEEXCEEDED:switch(icp>icmpcode){caseICMPEXCTTL:(void)printf("Timetoliveexceededn")breakcaseICMPEXCFRAGTIME:(void)printf("Fragreassemblytimeexceededn")breakdefault:(void)printf("Timeexceeded,BadCode:dn",icp>icmpcode)break}…default:(void)printf("BadICMPtype:dn",icp>icmptype)}}priph函数是用于打印IP数据包头选项如下所示:staticvoidpriph(structiphdr*ip){inthlenuchar*cphlen=ip>iphl<<cp=(uchar*)ip*pointtooptions*(void)printf("VrHLTOSLenIDFlgoffTTLProcksSrcDstDatan")(void)printf("xxxxx",ip>ipv,ip>iphl,ip>iptos,ip>iplen,ip>ipid)(void)printf("xx",((ip>ipoff)xe)>>,(ip>ipoff)xfff)(void)printf("xxx",ip>ipttl,ip>ipp,ip>ipsum)(void)printf("s",inetntoa(*((structinaddr*)ip>ipsrc)))(void)printf("s",inetntoa(*((structinaddr*)ip>ipdst)))*dumpandoptionbytes*while(hlen>){(void)printf("x",*cp)}(void)putchar('n')}praddr是用于将ascii主机地址转换为十进制点分形式并打印出来这里使用的函数是inetntoa如下所示:staticchar*praddr(ulongl){structhostent*hpstaticcharbufif((optionsFNUMERIC)||!(hp=gethostbyaddr((char*)l,,AFINET)))(void)sprintf(buf,*sizeof(buf),*"s",inetntoa(*(structinaddr*)l))else(void)sprintf(buf,*sizeof(buf),*"s(s)",hp>hname,inetntoa(*(structinaddr*)l))return(buf)}Usage函数是用于显示帮助信息如下所示:staticvoidusage(void){(void)fprintf(stderr,"usage:pingLRdfnqrvccountiwaitlpreloadntppatternspacketsizetttlIinterfaceaddresshostn")exit()}实验内容NTP协议实现.实验目的通过实现NTP协议的练习进一步掌握Linux下网络编程并且提高协议的分析与实现能力为参与完成综合性项目打下良好的基础。.实验内容NetworkTimeProtocol(NTP)协议是用来使计算机时间同步化的一种协议它可以使计算机对其服务器或时钟源(如石英钟GPS等)做同步化它可以提供高精确度的时间校正(LAN上与标准间差小于毫秒WAN上几十毫秒)且可用加密确认的方式来防止恶毒的协议攻击。NTP提供准确时间首先要有准确的时间来源这一时间应该是国际标准时间UTC。NTP获得UTC的时间来源可以是原子钟、天文台、卫星也可以从Internet上获取。这样就有了准确而可靠的时间源。时间是按NTP服务器的等级传播。按照距离外部UTC源的远近将所有服务器归入不同的Stratun(层)中。Stratum在顶层有外部UTC接入而Stratum则从Stratum获取时间Stratum从Stratum获取时间以此类推但Stratum层的总数限制在以内。所有这些服务器在逻辑上形成阶梯式的架构相互连接而Stratum的时间服务器是整个系统的基础。进行网络协议实现时最重要的是了解协议数据格式。NTP数据包有个字节其中NTP包头字节时间戳个字节。其协议格式如图所示。图NTP协议数据格式其协议字段的含义如下所示。(LI:跳跃指示器警告在当月最后一天的最终时刻插入的迫近闺秒(闺秒)。(VN:版本号。(Mode:模式。该字段包括以下值:-预留-对称行为-客户机-服务器-广播-NTP控制信息。(Stratum:对本地时钟级别的整体识别。(Poll:有符号整数表示连续信息间的最大间隔。(Precision:有符号整数表示本地时钟精确度。(RootDelay:有符号固定点序号表示主要参考源的总延迟很短时间内的位到间的分段点。(RootDispersion:无符号固定点序号表示相对于主要参考源的正常差错很短时间内的位到间的分段点。(ReferenceIdentifier:识别特殊参考源。(OriginateTimestamp:这是向服务器请求分离客户机的时间采用位时标格式。(ReceiveTimestamp:这是向服务器请求到达客户机的时间采用位时标格式。(TransmitTimestamp:这是向客户机答复分离服务器的时间采用位时标格式。(Authenticator(Optional):当实现了NTP认证模式时主要标识符和信息数字域就包括已定义的信息认证代码(MAC)信息。由于NTP协议中涉及到比较多的时间相关的操作为了简化实现过程本实验仅要求实现NTP协议客户端部分的网络通信模块也就是构造NTP协议字段进行发送和接收最后与时间相关的操作不需进行处理。.实验步骤()画出流程图简易NTP客户端实现流程图如图所示。图简易NTP客户端流程图()编写程序具体代码如下:#include<syssocketh>#include<syswaith>#include<stdioh>#include<stdlibh>#include<errnoh>#include<stringh>#include<sysunh>#include<systimeh>#include<sysioctlh>#include<unistdh>#include<netinetinh>#include<stringh>#include<netdbh>structNTPPacket{charLeapVerMode*client=*charStartumcharPollcharPrecisiondoubleRootDelaydoubleDispersioncharRefIdentifiercharRefTimeStampcharOriTimeStampcharRecvTimeStampcharTransTimeStamp}#defineNTPPORT#defineTIMEPORT#defineNTPV"NTPV"#defineNTPV"NTPV"#defineNTPV"NTPV"#defineNTPV"NTPV"#defineTIME"TIMEUDP"doubleSecondBefstructsockaddrinsinstructaddrinfohints,*res=intrc,skcharProtocol*构建NTP协议包*intConstructPacket(char*Packet){charVersion=longSecondFromlongZero=intPorttimettimerstrcpy(Protocol,NTPV)*判断协议版本*if(strcmp(Protocol,NTPV)||strcmp(Protocol,NTPV)||strcmp(Protocol,NTPV)||strcmp(Protocol,NTPV)){Port=NTPPORTVersion=ProtocolxPacket=(Version<<)|LIVersionModePacket=StartumPacket=PollintervalPacket=Precision*包括Rootdelay、Rootdisperse和RefIndentifier*memset(Packet,,)*包括Reftimestamp、Oritimastamp和ReceiveTimestamp*memset(Packet,,)time(timer)SecondFrom=SecondBef(long)timerSecondFrom=htonl(SecondFrom)memcpy(Packet,SecondFrom,)memcpy(Packet,Zero,)return}elsetimeudp{Port=TIMEPORTmemset(Packet,,)return}return}*计算从年到现在一共有多少秒*longGetSecondFrom(intEnd){intOrdinal=intRun=longResultintifor(i=i<Endi){if(((i==)(i!=))||(i==))RunelseOrdinal}Result=(Run*Ordinal*)**returnResult}*获取NTP时间*longGetNtpTime(intsk,structaddrinfo*res){charContentintPacketLenfdsetPendingDatastructtimevalBlockTimeintFromLenintCount=intresult,iintrestructNTPPacketRetTimePacketLen=ConstructPacket(Content)if(!PacketLen)return*客户端给服务器端发送NTP协议数据包*if((result=sendto(sk,Content,PacketLen,,res>aiaddr,res>aiaddrlen))<)perror("sendto")elseprintf("sendtosuccessresult=dn",result)for(i=i<i){printf("inforn")*调用select函数并设定超时时间为s*FDZERO(PendingData)FDSET(sk,PendingData)BlockTimetvsec=BlockTimetvusec=if(select(sk,PendingData,,,BlockTime)>){FromLen=sizeof(sin)*接收服务器端的信息*if((Count=recvfrom(sk,Content,,,res>aiaddr,(res>aiaddrlen)))<)perror("recvfrom")elseprintf("recvfromsuccess,Count=dn",Count)if(Protocol==TIME){memcpy(RetTimeTransTimeStamp,Content,)return}elseif(Count>=Protocol!=TIME){RetTimeLeapVerMode=ContentRetTimeStartum=ContentRetTimePoll=ContentRetTimePrecision=Contentmemcpy((void*)RetTimeRootDelay,Content,)memcpy((void*)RetTimeDispersion,Content,)memcpy((void*)RetTimeRefIdentifier,Content,)memcpy((void*)RetTimeRefTimeStamp,Content,)memcpy((void*)RetTimeOriTimeStamp,Content,)memcpy((void*)RetTimeRecvTimeStamp,Content,)memcpy((void*)RetTimeTransTimeStamp,Content,)return}}}close(sk)return}intmain(){memset(hints,,sizeof(hints))hintsaifamily=PFUNSPEChintsaisocktype=SOCKDGRAMhintsaiprotocol=IPPROTOUDP*调用getaddrinfo函数获取地址信息*rc=getaddrinfo("","",hints,res)if(rc!=){perror("getaddrinfo")return}sk=socket(res>aifamily,res>aisocktype,res>aiprotocol)if(sk<){perror("socket")}else{printf("socketsuccess!n")}*调用取得NTP时间函数*GetNtpTime(sk,res)}本章小结本章首先概括地讲解了OSI分层结构以及TCPIP协议各层的主要功能介绍了常见的TCPIP协议族并且重点讲解了网络编程中需要用到的TCP和UDP协议为嵌入式Linux的网络编程打下良好的基础。接着本章介绍了socket的定义及其类型并逐个介绍常见的socket基础函数包括地址处理函数、数据存储转换函数这些函数都是最为常用的函数要在理解概念的基础上熟练掌握。接下来介绍的是网络编程中的基础函数这也是最为常见的几个函数这里要注意TCP和UDP在处理过程中的不同。同时本章还介绍了较为高级的网络编程包括调用fcntl和select函数这两个函数在之前都已经讲解过但在这里会有特殊的用途。最后本章以ping函数为例讲解了常见协议的实现过程读者可以看到一个成熟的协议是如何实现的。本章的实验安排了实现一个较为简单的NTP客户端程序主要实现了其中数据收发的主要功能至于其他时间调整相关的功能在这里就不详细介绍了。思考与练习实现一个小型模拟的路由器就是接收从某个IP地址的连接再把该请求转发到另一个IP地址的主机上去。�图UDP数据包头�SHAPE*MERGEFORMAT��EMBEDWordPicture����图TCPIP协议族docbindcloserecvrecvfromsendsendtoconnectsocket客户端closesendsendtorecvrecvfromacceptlistenbindsocket服务器端doc网络接口层网络层传输层应用层UDPTCPICMPIGMPMPLSARP、RARPIPv、IPvftptelnetdocACKKSYNK,ACKJSYNJdocrecvfrom客户端sendrecvacceptlistensocket服务器端sendtorecvfromsendtocloserecvsendconnectsocketclosevsd��开始�解析地址getaddrinfo�新建socket构建ntp协议包发送ntp协议包接收ntp协议包结束docOSI参考模型网络接口层网络层传输层应用层物理层数据链路层网络层传输层会话层表示层应用层TCPIP参考模型vsd��开始�建立socket�选项处理地址处理对信号SIGINT和SIGALRM进行处理循环等待回应信息结束

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

评分:

/49

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利