计算机网络课程设计院别计算机与通信工程学院专业计算机科学与技术班级学号2123322姓名谢磊指导教师于长永成绩2015年7月10日利用VC实现TFTP协议一、设计要求TFTP协议是基于UDP协议的应用协议,用于对从一台主机到另一台主机之间传递的文件或者邮件信息进行读写操作,可参阅RFC783[6-4]。本次设计要求利用VC实现TFTP协议的基本功能。二、实验条件硬件:笔记本一台软件:VSExpress2013forDesktop三、实验原理3.1TFTP介绍TFTP是一个传输文件的简单协议,它已经基于UDP协议实现了,因此可以在不同网络的主机间进行传送。此协议设计的时候是进行小文件传输的。因此它容易实现,不具备通常的FTP的许多功能,它只能从文件服务器上获得或写入文件,不能列出目录,不进行认证,它传输8位数据。传输中有三种模式:netascii,这是8位的ASCII码形式,另一种是octet,这是8位源数据类型;最后一种mail已经不再支持,它将返回的数据直接返回给用户而不是保存为文件。可以由协作的主机间自己定义模式。3.2概况任何传输起自一个读取或写入文件的请求,这个请求也是连接请求。如果服务器批准此请求,则服务器打开连接,数据以定长512字节传输。每个数据包包括一块数据,服务器发出下一个数据包以前必须得到客户对上一个数据包的确认。如果一个数据包的大小小于512字节,则表示传输结束。如果数据包在传输过程中丢失,发出方会在超时后重新传输最后一个未被确认的数据包。通信的双方都是数据的发出者与接收者,一方传输数据接收应答,另一方发出应答接收数据。大部分的错误会导致连接中断,错误由一个错误的数据包引起。这个包不会被确认,也不会被重新发送,因此另一方无法接收到。如果错误包丢失,则使用超时机制。错误主要是由下面三种情况引起的:不能满足请求,收到的数据包内容错误,而这种错误不能由延时或重发解释,对需要资源的访问丢失(如硬盘满)。TFTP只在一种情况下不中断连接,这种情况是源端口不正确,在这种情况下,指示错误的包会被发送到源机。这个协议限制很多,这些都是为了实现起来比较方便而进行的。3.3初始链接初始连接时候需要发出WRQ(请求写入远程系统)或RRQ(请求读取远程系统),收到一个确定应答,是确定可以写出的包或应该读取的第一块数据。通常确认包包括要确认的包的包号,每个数据包都与一个块号相对应,块号从1开始而且是连续的。因此对于写入请求的确定是一个比较特殊的情况,因此它的包的包号是0。如果收到的包是一个错误的包,则这个请求被拒绝。创建连接时,通信双方随机选择一个TID,是随机选择的,因此两次选择同一个TID的可能性就很小了。每个包包括两个TID,发送者TID和接收者TID。这些TID用于在UDP(或其他数据包协议)通信时选择端口,请求主机选择TID的方法上面已经说过了,在第一次请求的时候它会将请求发到TID69,也就是服务器的69端口上。应答时,服务器使用一个选择好的TID作为源TID,并用上一个包中的TID作为目的ID进行发送。这两个被选择的TID在随后的通信中会被一直使用。下例是一个写入的例子,其中WRQ,ACK和DATA代表写入请求,确认和数据。1.主机A向主机B发出WRQ,其中端口为692.B机向A机发出ACK,块号为0,包括B和A的TID此时连接建立,第一个数据包以序列号1从主机开始发出。以后两台主机要保证以开始时确定的TID进行通信。如果源ID与原来确定的ID不一样,这个包会被认识为发送到了错误的地址而被抛弃。错误消息包是被发送到发送不正确包的端口,因而不影响继续传输文件,这种情况仅适合接受到包端口不正确。如果支持协议不允许,这种情况就不会发生。3.4正常终止传输的结束由DATA数据标记,数据区为0-511个字符。这个包可以被ACK数据包确认。接收方在发出对最后数据包的确认后可以断开连接,当然,适当的等待是比较好的,如果最后的确定包丢失可以再次传输。如果发出确认后仍然收到最后数据包,可以确定最后的确认丢失。发送最后一个DATA包的主机必须等待对此包的确认或超时。如果响应是ACK,传输完成。如果发送方超时并不准备重新发送并且接收方有问题或网络有问题时,发送也正常结束。也有可能这种情况传输是不成功的,但无论如何连接都将被关闭。3.5早终结如果请求不能被满足,或者在传输中发生错误,然后发送了ERROR包。这仅是一种传输友好的方式,这种包不会被确认也不会被重新传输,因此这种包可能永远不会被接收到。因此需要用超时来侦测错误。三、代码如下客户端:自定义头文件:define.h#ifndefMAKEWORD#defineMAKEWORD(l,h)((WORD)(((BYTE)(l))|(((WORD)(BYTE)(h))8)))#endif#defineWSA_MAJOR_VERSION1#defineWSA_MINOR_VERSION1#defineWSA_VERSIONMAKEWORD(WSA_MAJOR_VERSION,WSA_MINOR_VERSION)#defineTFTP_RRQ1/*读请求(RRQ)*/#defineTFTP_WRQ2/*写请求(WRQ)*/#defineTFTP_DATA3/*数据(DATA)*/#defineTFTP_ACK4/*ACK(ACK)*/#defineTFTP_ERROR5/*Error(ERROR)*/#defineTFTP_OCTET1#defineTFTP_WSTAT_FIRSTACK0#defineTFTP_WSTAT_NEXTACK1#defineTFTP_WSTAT_LASTACK2#defineMAX_RETRY3/*最大重复次数*/#defineTFTP_NOTEND_DATALEN512+2+2/*数据块长度*///错误类型#defineNot_defined0#defineFile_not_found1#defineAccess_violation2#defineDisk_full3#defineIllegal_TFTP_operation4#defineUnknown_port5#defineFile_already_exists6#defineNo_such_user7#defineTime_out8#defineRead_file_Error9#defineCannot_create_file10自定义头文件:makepack.h#includedefine.hintmakeack(unsignedshortnum,char*buffer,intsize);intmakereq(chartype,intmode,char*filename,char*buffer,intsize);intmakedata(unsignedshortnum,char*data,intdatasize,char*buffer,intbufsize);//ACK包填充intmakeack(unsignedshortnum,char*buffer,intsize){intpos=0;buffer[pos]=0;pos++;buffer[pos]=TFTP_ACK;//opcode=04pos++;buffer[pos]=(char)(num8);//块号2个字节pos++;buffer[pos]=(char)num;pos++;returnpos;}//填充请求包intmakereq(chartype,intmode,char*filename,char*buffer,intsize){intpos=0;unsignedinti=0;char*s=octet;buffer[pos]=0;pos++;buffer[pos]=type;//Opcode=01(RRQ)or02(WRQ)pos++;for(i=0;istrlen(filename);i++){//&buffer[3]=Filenamebuffer[pos]=filename[i];pos++;}buffer[pos]=0;//0pos++;for(i=0;istrlen(s);i++){//modebuffer[pos]=s[i];pos++;}buffer[pos]=0;//0pos++;returnpos;//返回请求包长度}//Data包填充intmakedata(unsignedshortnum,char*data,intdatasize,char*buffer,intbufsize){intpos=0;buffer[pos]=0;pos++;buffer[pos]=TFTP_DATA;//opcode=3pos++;buffer[pos]=(char)(num8);//块号pos++;buffer[pos]=(char)num;pos++;memcpy(&buffer[pos],data,datasize);//datapos=pos+datasize;returnpos;}自定义头文件cmdfunction.h#includestdio.h#includewinsock.h#includemakepack.htypedefvoid(*CMDFUNC)(char[][256],intpcount);typedefstruct_cmdnum{char*cmd;//命令代码intnum;//序号intparamcount;//参数个数CMDFUNCcallback;//回调函数名}CMDNUM,*PCMDNUM;/*定义帮助文档*/char*helptext=help:显示帮助信息\n\connectdest_ip:连接服务器\n\exit:退出程序\n\getfilename:从服务器端下载文件\n\putlocal_fnamedest_fname:向服务器上传文件\n;externchardesthost[256];//目的主机的地址externSOCKETsock;externintfilemode;voidshowhelp(charcmd[][256],intpcount);//显示帮助信息voidconnectto(charcmd[][256],intpcount);//连接目的主机voidquit(charcmd[][256],intpcount);//退出voidgetfile(charcmd[][256],intpcount);//下载文件voidputfile(charcmd[][256],intpcount);//上传文件//提供帮助信息voidshowhelp(charcmd[][256],intpcount){printf(helptext);}//设置目的主机地址voidconnectto(charcmd[][256],intpcount){if(pcount1){printf(格式:connectremoteip\n);return;}strcpy(desthost,cmd[1]);}//退出voidquit(charcmd[][256],intpcount){printf(退出程序\n);closesocket(sock);exit(0);}//下载文件voidgetfile(charcmd[][256],intpcount){charsend_buffer[1024]={0};charrecv_buffer[1024]={0};sockaddr_indest_addr;sockaddr_insour_addr;structtimevaltimeout={5,0};intsour_len=0;intret=0;intlen=0;intretry=0;intflen=0;intstat=0;unsignedshortlastdata=0;fd_setfdr;FILE*file;intc;if(pcount!=1){//输入的参数个数不合要求printf(格式:ge