在这一系列里面我不会着重介绍太多应用层的协议,因为应用层协议太多了而且细节及其丰富,一节的时光根本没有办法写清楚,所以我只会写一些简单的应用层协议,不求能完全介绍清楚,更重要的是粗略看一下应用层协议是如何和底层协议结合起来的,这样算是从上到下都有点吧。

FTP?TFTP!

我相信FTP是每个程序员肯定知道的名词,而且以前在学校的时候还是我们下载文件的一个重要场所,TFTP是什么玩意儿?就像前面我所提过的一样,我建议去看看UDP的RFC协议入门RFC都在写啥一样样,如果有人问我应用层协议在RFC中是啥样的,能不能给个入门的推荐,那么我肯定推荐TFTP。TFTP相关的RFC有两个,783和1350,都只有10几页。有两个的原因是因为在第一版协议中因为定义的不严谨性导致了一个严重的问题,从这里还能看到协议的一个迭代过程。

言归正传,TFTP,学名Trivial File Transfer Protocol,中文一般会翻译成小型文件传输协议,但是trivial这个词除了琐碎的意思,还有不重要的意思,由于TFTP是完全建立在UDP之上的应用协议,而UDP又不是可靠的传输层协议,所以这个不重要就特别的有点意思了。TFTP类似于FTP,是用来传输文件的,所以其主要的命令有读文件和写文件。那么为什么要有这么一个“不重要”的文件传输协议而不直接用FTP呢?因为TFTP很简单,通过很少量的存储器就能实现,这在网络发展的初期是极为重要的,因为成本是人类商业和工业发展永恒的主题。而TFTP主要用于引导计算机,现在仍热会被广泛用于在小型网络中的主机之间传输小文件。而大名鼎鼎的X window system就利用了TFTP来读取引导文件。

TFTP作为一个工作在UDP上面的应用层协议,其利用的UDP端口号是69。如果长期使用FTP的童鞋知道FTP可以列出文件列表,还有验证加密啊这些机制,很抱歉,为了实现上的简单TFTP都没有这些东西。而TFTP支持三种传输模式用于在远程服务器上读取或者写入文件,分别是,"netascii","octet"和“mail”,第三种基本就是不用的,前两种翻译成人话就是字符模式而二进制模式。

而作为一个文件传输协议,最基本要保证文件传输的完整,而UDP是一个不可靠的协议,所以为了能达到这一点,只能在UDP上自己实现一种机制来保证前述之事。而且作为一个服务端,TFTP要能接收很多客户机同时的请求,或者说更快的处理不同的客户来的请求。而这些在更加复杂的协议里都有很成熟的设计,作为一个简单并且古老的传输协议,TFTP,用的办法虽然很原始而又简单,但是却可以看到一点点简单协议如何往复杂高级协议过渡的初级阶段。

停!等……

TFTP使用了最符合人类思考方式的一个逻辑来解决文件传输完整的问题。想要文件传输完整,其实就是需要发送的每个数据包能够按照顺序在接收端拼凑还原出来就可以了,因为不可能所有的文件都可以只用一个数据包就传送完了。所以TFTP使用了学名叫步锁的机制,翻译成人话就是这一步不完成下一步就不开始。那么具体的过程和这个思想逻辑有个不恰当的比喻就是像战争电影里士兵们作战的通信系统,经常我们看到的是,“某某某,我的坐标是xxx,xxx,请求支援,收到请回答,收到请回答!”,“某某某,收到收到,请给出被困人数”。当一段发送一个请求时,会等到对方的回应再进行下一次的请求的发起。而TFTP为了能够标识每一个小数据包,很自然的采用一个编号的机制就可以解决这个问题。

而另一个问题是TFTP这个协议怎样让请求方知道一个文件发完了,或者让接收端知道一个文件写完了?按照程序员的思路可能最先想到的是用某一个标记,这绝对是是在大多数协议里常用的一种方法,但是TFTP使用了更加简单的一个方法——数据块的长度!TFTP协议里面,每个数据包的大小都是558个字节,如果请求方接收到的一个数据包小于558个字节,那么就表示文件已发送完毕。可能看到这里会产生一个疑问,如果文件的大小正好是558个字节的整数倍咋办?答案是再发一个0字节的数据包就行啦!从这里也可以学到一点,有时候那些复杂的设计,思想固然是精妙,但是也许在实用中一个简单的方法也是很有效的,所以我觉得人还是思想不能固化,要能接收各种不同的看起来不靠谱的观点。

说了这么多文字,不太直观,从一个直观的数据包中可以更清晰的认识下TFTP这个协议。 UDP的实践—TFTP  - 图1这是一个TFTP的读请求,可以看到请求端向服务器端请求一个文件rfc1530.txt,要求传输的方法是octet,也就是二进制流。服务器端收到这个请求之后,传输了558个字节的数据过来,而请求端收到这558个字节的数据后,发送一个Acknowledgement给服务器端,告诉请求端我已经妥善收到了这个558个字节。服务器端在收到这个对面的告知之后,他就知道对面已经妥善接收到这第一个数据块,于是服务器端开始向请求端发送下一个558字节。如果这段文字还是有点绕的话,那么下面这个图应该是能清晰的放映出整个过程:

 UDP的实践—TFTP  - 图2

这个有个细节,请注意端口号,在253第一次发送请求时,服务器端10的端口号是69,这也是TFTP的这个应用层协议所规定的端口号。但是在第二行,也就是TFTP服务器向接收端发送数据的时候用了一个新的端口号,3445,这是随机选择的一个非保留端口号。而后面的通信中,都将使用这个端口号进行通信,这样做的目的是为什么呢?

设想一下,所以的客户机端想和TFTP的服务器端进行通信,都要向69这个端口号发送请求消息,而在前面介绍过,一个IP地址加一个端口号就是唯一的一个通信终端标识,如果这个终端已经被占用了,那么其他接下来的通信更本就进不来了。这个道理就像端口号就是每个IP地址上的一个坑,在对端建立连接的时候,这个坑就被占了,后面来的朋友要么你就回家,要么你就等着,等着这个坑被腾出来。可以想象,这是一种极其没有效率的做法,而且也根本不可能是一个商业或者工业上的可行技术。

而TFTP采用的控制和数据用分开的端口进行就可以完全避免这个问题,而这种思想可以在很多很多的编程,或者协议涉及上有着广泛的应用。

前面还说了,TFTP是利用数据包的长度来标识文件有没有传完,所以可以看看这个数据包的最后两包,可以看到这个数据长度只有69个字节,小于558个字节,这样请求端就知道数据包已经传完,文件已经传输完毕。 UDP的实践—TFTP  - 图3

TFTP包格式

TFTP可能是为数不多可以详细阐述一下报文格式的应用层协议了,为什么呢?因为简单!我又要从RFC里面盗图了,毕竟人家是标准:

 UDP的实践—TFTP  - 图4

这是从RFC上面截的原图,第一个表示了TFTP是在datagram里面的数据,这里没有用UDP,从这里可以看出作为一个标准的制定者,保持一定的灵活度而又严谨是一项很必要的技能。

TFTP Formats描述了TFTP数据包的具体格式,可以看到根据操作符的腹痛一共也就5种数据包,分别是读请求/写请求,数据包,确认包和错误包。

在读请求/写请求包里,除了操作符,接着就是文件名,然后是1个字节的保留0,接着是模式,也就是上面说的是字节还是二进制流,最后又是1个字节的保留0。

而数据包里,主要是块号和数据,对应的应答包里是块号,这样就知道某条应答是对于哪条数据包的。

最后再错误包中主要是错误代码和错误信息。

那么,可以wireshark的抓包里随机来找一个来验证上面所说的格式:

 UDP的实践—TFTP  - 图5

可以看到在请求包里,操作符是1,需要的文件和需要传输的模式都包含在在UDP的数据中。

魔术新手症候群

一听这个名字,浓浓的一股神秘而又霸气感,其实这是因为TFTP在第一次设计时候的一个缺陷而导致比较严重的问题,所以TFTP才会在RFC里有两个版本,其后果就是导致网络资源被耗尽,大家都玩完了。

在阐述这个现象之前,先小小的回忆下前面的TFTP的过程,其中一个重点是下一个数据的传输只会发生在发送数据端已经收到对上一次数据的确认,而如果收到了这个确认,数据发送端必定会发送下一个数据块。虽然很绕但是仔细想想这是哦能想到的最严谨的说法了。那么假设如果这个确认在传送的过程中丢失了,这在网络中是很常见很正常的现象,而UDP又是一个不可靠协议,那发送数据端是根本分不清到底是自己发出去的没有到达对端还是对端的确认丢失了。所以在这种不可靠协议中,一般都有个超时机制,如果我不确定发出去的有没有递送到对方,那么我就在一定时间后再发送一份,因为在我的协议逻辑中我只管最大程度的交付就行了。

先停五分钟来在脑子中思考下上面说的这种情景,因为下面又有一个假设了。假设在数据发送端重新发送了他认为丢失了的数据包之后,这个确认并没有丢失,而只是迷路了,或者堵塞到某一个地方,在一些乱七八糟的机遇之后,这个确认又到达了数据发送端。那么根据上面的拗口的定义,这时候数据发送端就会根据块号发送下一个数据包。

但是很不巧,重发的那个数据包也正常的到达了对端并且对端按照协议也发送了对该数据块的确认,在数据发送端接收到这个重复的确认后,就开始有问题了,从这里开始,同一个数据块都会重复的被发送两边。如果网络状况不太好,那么这样双倍的数据包必定会导致网络流量更大,从而更加拥堵,更加可能发生上面的假设,周而复始结果网络就崩溃了。

如果对这段文字的描述还不是那么直观,那么只好再一次献上我的手版了:

 UDP的实践—TFTP  - 图6

其中标号标识对应的数据与ACK,很不好意思,最后一个左边的4被我截掉了。

既然知道问题是什么,那么接下来就是怎样解决问题了,在修改后的TFTP的RFC里面,用了个很简单的方法就解决了这个问题,数据发送端或者是确认接收端,只会对第一个的ACK会被承认从而触发下一块数据的发送。

至于这个现象为什么叫这个名字,传说是来自一个故事魔法师的学徒,不过我没有看过。

TFTP是一个很简单的协议,但是其中很多思想都有TCP里面概念的影子,所以说有时候基础而简单的东西并不是没有意义,透过他们,你可以看到更多的演变,从而更加深理解,没有什么软件是一个版本就成型的。