您的当前位置:首页用C≠实现TFTP协议及其应用

用C≠实现TFTP协议及其应用

2020-07-18 来源:乌哈旅游
… ‘ 。 … ‘ ’ ’J 实用第一 智慧密集 用c≠≠实现TFTP协议及其应用 明廷堂 摘 要:全面解读了TFTP协议的技术细节.并在此基础上运用c≠≠语言完整实现该协议。通过 一个TFTP客户端、服务器应用,在真实的网络管理环境中进行测试。项目保持完整性、独立性, 为Socket编程开发基于网络协议的应用程序提供了一个基本思维框架。 关键词:简单文件传输协议;客户端应用;服务端应用;网络管理 1 TFTP概述 0pcode l Filei1a“ l 0 l Mode TfTrP是一个基于UDP的简单文件传输协议。其设计初衷 是进行小文件传输的,因此与fTrP相比.它只能从文件服务器 图2 TFTP的RRQ^^『RQ报文格式 上获得或写入文件,不能列出目录,不进行认证。它传输8位 数据.有3种传输模式:netascii,特殊的8位ASCII码; octet.8比特位组数据类型;mail,目前已不再支持,它将返 每个T兀’P报文都包含一个2字节的操作码。RRQ、WRQ 报文的操作码分别为0x01、0x02。文件名指定客户端要读取或 写入的文件服务器上的文件。文件名字段以1字节长的0字节 回的数据直接返回给用户而不是保存为文件。TfTrP的优点在 于实现简单而不是系统的高吞吐量。 值得注意的是协议端[j:TFTP协议需要客户进程向服务 器进程的熟知UDP端口69发送第一个分组,服务器进程监 作为结束符。模式字段是一个ASCII码串字符netascii或octet (大小写可任意组合),其中:netascii表示数据是以成行的ASCII 码字符组成。以两个字节的回车字符后跟换行字符f简称CR/ Ln作为行结束符。CR/LF在这种格式和本地主机使用的行定界 听到此请求后,就向服务器主机申请一个尚未使用的临时端 口,然后服务器进程使用该临时端口与该请求的客户进程进 行数据交换。这种端口切换原因是:服务器进程不能长期占 用这个熟知端口来完成一些需要较长时间f可能是几十秒或数 符之间进行转化:octet则表示将数据看作8比特位组的字节流 而不作任何解释。模式字段同样以一个字节长的0字节结束。 2_2 DATA报文 TFTP的数据报文fDATA)用于实际数据的传输。如图3 所示。 2 bytes 2 bytes 0-512 byt ̄s 分钟1的文件传输,在传输当前文件的过程中,这个熟知端 口要留出来供服务器进程监听其他的TFTP客户进程发送的 并发请求。 {0p ̄ode{Bl0 #l Data l 2 TFTP报文及其选项 一图3 TFTP的DATA报文格式 个T丌P分组包含几个子域:本地媒介头.IP头,数据 DATA报文的操作码为0x03。每个DATA分组包含一个2 个字节的块编号字段.用于后续传输过程的数据块标识和确 认。数据域从0字节到512字节。如果数据域是512字节,则 报头,TFTP头,剩下的就是TFTP数据了。TrI1P在IP头中不 指定任何数据,但它使用UDP中的源和目标端口以及包长度 域。TFTP使用的分组标记fTID1在连接初始化时用作UDP端 口,且必须介于0到65,535之间。T丌IP分组的头部顺序如图 1所示 Local edi 1}linternet l Datagrara{TFTP 指示延续传输过程.如果小于512字节则表示这是最后一个分 组,传输终止。 2.3 ACK报文 T兀1P的确认报文fACK】格式如图4所示。 2 byte8 2 bytes 图1 TFTP分组的头部顺序 l Opcode l Bloct ̄# l 2.1 RRQ/\/\『RQ报文 TFTrP的读请求报文(RRQ)和写请求报文(WRQ)具有相 同的格式。如罔2所示。 图4 TFTP的ACK报文格式 ACK报文的操作码为0x04。块编码域是前一次传输的数 据块的编码,用于确认该数据块是否成功传输。 58电 与雏 …… … …… … … ……… 。… 实用第一 智慧密集 … … .. ~…  、, … . , 4.1 TFTP Library实现过程 TFTPLibrary包含4个主要部分:事件参数设置模块、单 线程报文处理模块、多线程报文处理管理模块、传输会话管理 模块。 单线程报文处理程序(TF33 ̄Process1是核心模块,它是传 输任务的真正执行者。TF3"PProcess定义了一些方法,一方面 用于TFTP报文的构造、发送与接收,另一方面用于传输会话 (TFTPSession)的管理。 任何网络应用程序离不开底层的数据发送、接收。下面 的源代码采用Socket编程以及回调的方式实现异步发送和接收 数据: privatevoid SendCaIIback(IAsVncResuIt result) { tⅣ { ((Socket)result.AsyncState).EndSendTO(resuIt): ) catch(ObjectDisposedException) {} ) privatevoid Send(1PEndPo{nt RemoteEndPoint,byte[1 Data) { trv { LocalSocket.BeginSendTo(Data,0,Data Length, SocketFlags.None,RemoteEndPoint, newAsyncCallback(SendCaltback),LocalSocket); 1 catch(Exception ex) {} } privatevoid Receive() { while(Loop) { bool ReceivedWorked=false;Byte[1 ReceiveBuf=newByte 【MaxRecvSize]; EndPoint tempPoint=newIPEndPoint(IPAddress.Any,O):int NumBytesReceived=O: trv ( NumBytesReceived=LocalSocket.ReceiveFrom (ReceiveBuf.ref tempPoint); ReceivedWorked:true; ) catch(Exception ex) { if(ex.GetType0.FullName==“System.Net.Sockets. SOcketExceDtiOn && 与雄 ((SocketException)e×).ErrorCode!=1 0004) { this.StopListenerO; ) ) (ReceivedWorked) { IPEndPoint RemoteEndPoint=(IPEndPoint)tempPoint;bool MatchlP=true; (ClientMode) ( MatchIP==false;bool blslPStr=false; trv { IPAddress HostAddr=IPAddress.Parse(CIientHost): blslPStr=true; if(RemoteEndPoint.Address.Equals(HostAddr))MatchlP= true; ) catch O If(blslPStr=:false) ( foreach(IPAddress ip inDns.GetHostAddresses(CtientHost)) { If(ip=:RemoteEndPoint.Address)MatchlP=true; } } ) If(MatchlP) ( ProcessDatagram (ReceiveBuf,NumBytesReceived, RemoteEndPoint); } } } ) 从Socket缓冲区接收到n_TrP报文后,就需要调用 ProcessDatagram 0方法对报文进行处理,而ProcessDatagram() 又调用各种针对具体T兀甲报文类型的处理方法。下面简要描 述这些处理方法。 TrrP报文处理的一项重要工作是根据接收到的确认报文 (ACK】的块编号发送下一个DATA报文,下面的源代码片段实 现了这一处理过程: private void SendNextDataDatagram(byrte【】ReceivedBytes. IPEndPoint RemoteEndPoint,string EndPointString) { trv { byte【】DataBytes=session.GetData《ReceivedBytes【21. H盯WORK&O0MMUNIC盯ION…-u, 一………… … 一… ……………一…u- … ReceivedBytesI3】): byte[】SendBytes=newbyte[DataBytes.Length 4-4】 DataBytes.CopyTO(SendBytes.4): SendBytes[1 l=3: SendBytes[2】=session.BlockIDBytel: SendBytes[3l=session.BlockIDByte2: LogMsg(Leve1.Verbose,GloballD.ToString 0 4-“:Sending bytes to”+EndP0intString) Send(RemoteEndPoint,SendBytes); ) catch(System.IO.IOException ex) { Send(RemoteEndPoint,Error1): session.Close(); StOpListener() ) ) TfTrP的终极目的是实现文件的传输。从客户端的角度, 对于上传操作,需要调用PutFile 0方法;对于下载操作,需 要调用GetFile 0方法。下面的源代码片段实现了GetFile 0 文件交互过程: publicvoid GetFile(string Host,int Port,string Filename,bool Filesize,int Timeout,int BlockSize) { ClientMode=true; ClientHost=Host; IPEndPoint RemoteEndPoint=newIPEndPoint(IPAddress. Parse(Host),Port); #region Construt Standard RRQ Datagram ArrayList DataBytes=newArrayList(20); DataBytes.Add(Convert.ToByte(0)); DataBytes.Add(Convert.ToByte(1)): byte[】filebytes:System.Text.Encoding.ASCII.GetBytes 《Filename); foreach(byte FileByte in filebytes) f DataBytes.Add(FileByte); ) DataBytes.Add(Convert.ToByte(0)); DataBytes.Add(Convert.ToByte(Convert.ToChar(…O))): DataBytes.Add(Convert.ToBvte(Convert.ToChar(”C ))): DataBytes.Add(Convert.ToByte(Convert.ToChar(~t))): DataBytes.Add(Convert.ToByte(Convert.ToChar(“e。))): DataBytes.Add(Convert.TOByte(COnvert.ToChar(一t))); DataBytes.Add(Convert.ToByte(0)); #endregion #region Construct RRQ Options If(Filesize) { DataBytes.Add(Convert.ToBvte(Convert.ToChar(”t ))): DataBytes.Add(Convert.T0Bvte(COnvert.ToChar(”S“))) DataBytes.Add(Convert.TOByte《Convert.ToChar(“i“))): DataBytes.Add(Convert.T0Bvte《COnvert.ToChar(“Z“))) DataBytes.Add(Convert.ToByte(Convert.ToChar(“e ))) DataBytes.Add(Convert.ToByte(0)); DataBytes.Add(Convert.TOBvte(COnvert.ToChar(“0“))) DataBytes.Add(Convert.ToByte(0)); ) if(BlockSize!=512) { DataBytes.Add(Convert.T0Byte(Convert.ToChar(…b))); DataBytes.Add(Convert.TOBvte(Conve rt.ToChar( ㈨): DataBytes.Add(Convert.ToBVte(COnvert.ToChar( k”))); DataBytes.Add(Convert.TOBVte(COnve rt_ToChar( S ))): DataBytes.Add(Convert.ToByte(Convert.ToChar( -¨)】): DataBytes.Add(Convert.ToByte(Convert.ToChar( Z“))): DataBytes.Add(Convert.TOByte(Convert.ToChar(“e”))); DataBytes.Add(Convert.ToByte(0)); string size=BlockSize.ToString0; foreach(char ch in size) DataBytes.Add(Convert.ToByte(ch)); DataBytes.Add(Convert.ToBvte(0)): ) if(Timeout!=1) { DataBytes.Add(Convert.ToByte(Convert.ToChar( t。)JJ= DataBytes.Add(Convert.ToByte(Convert.ToChar( ㈨): DataBytes.Add(Convert.ToBvte(Convert.ToChar( m ))): DataBytes.Add(Convert.ToByte(COnvert.ToChar(…e))): DataBytes.Add(Convert.ToByte(Convert.ToChar(…O))): DataBytes.Add(Convert.ToByte(Convert.ToChar(…U))); DataBytes.Add(Convert.TOBVte(Convert.T0Char(…t))): DataBytes.Add(Convert.ToByte(0)); string time:Timeout.ToString0; foreach《char ch jn time) DataBytes.Add(Convert.ToByte(ch)); DataBytes.Add(Convert.ToByte(0)); } #endregion Send{RemoteEndPoint,(byte【])DataBytes.ToArray(typeof 《bvte))): session:CreateNewSession (RemoteEndPoint,2. Filename, octet”,BlockSize); ) 限于篇幅TFTPProcess类中其他方法不再赘述。 为了在服务器端实现文件传输并发处理。实现了 T} ̄FPManager对象来管理这些多线程。它以队列 TFTPProcessContainer的形式对多个报文处理与传输单线程进 行统一管理。 下面的源代码实现了TFTPManager类的多线程报文处理过 ≯i 脑螭穗l2技0巧13与.0娥9扣 61 实用第 一 翅 日 慧密集 程f其实际任务执行还是调用了TFl'PProcess类中的相关方法): privatevoid ProcessDatagram(byte[】ReceivedBvtes,int NumBytesReceived,IPEndPoint RemoteEndPoint) { int CurrOpcode=Convert.Tolntl 6(ReceivedBvtes…): string EndPointString=((IPEndPoint)RemoteEndPoint) Address.ToString()+”:“-I- ((IPEndPoint)RemoteEndPoint).Port.ToString0; if((CurrOpcode==1)II(CurrOpcode=:2)) { TFTPPrOcessCOntainercOntainer=newTFTPPrOcessCOn tainer0; container.process=newTFTPProcess( FullPath,LeveIOfLogger,LeveIOfEvent,Al1OwRRQ AIIowWRQ,AIIowWRQOverwrite, AIIowOptions,AIIowTIDCheck,Resendlnterval,Timeout TFTPLogger,LocalEndpoint.Address); container.threadStart=newThreadStart(container process.StartListener); container.thread=newThread(container.threadStart); container.thread.Name=container.process.GloballD ToString0; container.thread.IsBackground=true; container.thread.Start(); lock(ProcQueueLock) { ProcessQueue.Add(containe r): } for(int SleepCounter=O SleepCounter<=5 SIeepCounter++) { if(container.process.IsListening) { container.process.ProcessDatagram (ReceivedBVtes,NumBytesReceived, RemoteEndPoint); this.OnTFTPTransferEvent(newTFTPTransferEventArgs(conta ner.process)); container.process.TFTPPrOcessEvent+= newTFTPProcessEventHandler(this.OnTFTPPrOcessEvent): SleepCounter=1 O: } else { Thread.Sleep(1 OO): ) ) } else { 电 电脑缡程技巧与雄护’与雄 _一 Send(RemoteEndPoint,Error4); ) } 为了在UI中方便管理TfTrP服务,TFTPManager类中定义 了两个方法:StartListener 0用于启动TFTrP服务器监听请求; StopListener 0用于终止TFTP服务器监听请求。 下面的源代码实现了TnP服务的管理: publicvoid StartListener() { trv { TFTPLogger.Open(); LocalSocket=newSocket(LocalEndpoint.Address. AddressFamily,SocketType.Dgram,ProtocolType.Udp); LocaISocket.Bind(LocaIEndpoint): StartStateTimer(); Loop=true; Receive(); ) catch(SocketException ex) { Loop=false; ) } publicvoid StopListener() { Loop=false; if(CheckTimer!=nul1) CheckTimer.Dispose(); trv ( if《LocaISocket l=nul1) { LocaISocket.Shutdown{SocketShutdown.Both);//I think we want both here unlike the process LocalSocket.Close(); ) ) catch(0 bjectDisposedException ex) {} Iock(ProcQueueLock) { for(int Statelndex=O:Statelndex<ProcessQueue.Count; Statelndex++) { ProcessQueue[Statelndex].process.StopListener0; ProcessQueue[Statelndex].process.CloseSocket0; } ProcessQueue.Clear(); ) ……H盯WORK&C0啊MUNIC盯ION… TFTPLogger.Close(); J 事件参数有3类:多线程管理事件参数(TF ̄PManagerEve ntArgs)、单线程报文处理事件参数(TFTPProcessEventArgs)、传 输会话管理事件参数(TF ̄PTransferEventArgs),3种参数都通 过代理的方法进行参数设置。 TF ̄PSession类描述了’传输会话过程中的文件I/O操作。 为了跟踪会话状态,还定义了TFⅥyI’ransferstate类,以保存 在连续的TfTI’P传输中的状态信息,它主要用于应用程序中 的UI。 4.2应用TFTPServerApp的实现 TFTPServerApp是一个简单精致的服务器应用程序。主要 用于TFTP服务管理以及TFTP参数的设置。它启动时,首先 加载TFTI’P配置文件,将其反序列化为一个TFTPPammeters对 象.然后根据这些参数启动TFTP服务,监听客户端的TFTP 请求。 下列代码片段实现TF丫rP服务的管理: privatevoid StopTFTPServer() { tftp.StopListenerO; if(TFTPServerThread{=nul1) TFTPServerThread.Abort(); TFTPServerThread=null; timerEvents.Stop(); ) privatevoid StartTFTPServer() { trv { bool Is=tftp.IsListening; TFTPServerThread=newThread(newThreadStart(tftp. StartListener)): TFTPServerThread.Name=”TFTP Server Thread“: TFTPServerThread.IsBackground=true; TFTPServerThread.Start(); timerEvents.Start(); } catch(Exception ex) { MessageBox.Show(ex.Message); ) ) 4.3 TFTPCIientApp的实现 TFfPClientApp是一个简单客户端应用程序,其实现非常 简单.核心功能都是调用TFrPLibrary中的方法来完成。 TFTPClientApp主要用于测试目的:可在两台机器上同时运行 TFrPClientApp.exe和TFl'PServerApp.exe.设置好T 1P参数 rf 网络与通信j————————————————————————]  f保证一致),就可以完成文件的f 传与下载。笔者是在同一机 器上测试.只需要客户端将TFTP服务器地址设为本机地址 即可。 5 TFTP在网络管理中的应用 在网络管理中。TfTrP服务器程序常HJ于网络设备的配置 文件(Configuration)变更或系统映像(Image)升级 大多数应 用是基于DOS界面的.使用命令启动。在这种应用环境中, 网络设备通常充当TFTP客户端。 为验证TFTPServerApp的有效性.笔者在本机上运行 TF ̄PServerApp.exe.然后Telnet到TfTITP客户端f局域网中的 一台交换机DES一3550.地址为172.20.0_3,Image文件为 des3550一b37.had1,在其CLI界面中从服务器(笔者本机,地 址为202.196.107.2)下载该映像文件对设备成功升级。罔8显 示的是服务器.图9显示的是客户端运行结果。 图8 TFTP应用:服务器端运行结果 图9 TFTP应用:客户端运行结果 (收稿日期:2013—02—21) 勇 赫螭 矗 63 

因篇幅问题不能全部显示,请点此查看更多更全内容