WTP协议的实现
前面给出了一个WSP/WTP/WDP层次结构示意图,采用该协议实现的任何会话都必须通过WTP层,与WSP层不同,WTP没有连接——传输数据——断开连接的过程,它是实现数据的完整正确传输的这样一套协议。WTP有着一整套的完善的分包、丢包检测、重传,数据重组机制。而WSP并不实现这些的功能。其它的话不多说,让我们来看看WTP PUD的结构。
WTP协议只是在WSP PDU的前面加一个Header,WSP PDU作为WTP的Data域,WTP PDU可以没有Data域。WTP Header包括3~4个字节个固定域和一个不固定的可变域。WTP协议在WAP-224-WTP-20010710-a.pdf里有详细的描述,固定域有下表列出的7种类型PDU:
表3 WTP PDU类型及其编码
Note 2 所示的是WTP可选的支持功能,但也是比较重要的几种PDU,它们用于分组传输。由于无线网络的特殊性,为了对传输的数据进行精确的控制,WTP允许对每个PDU进行单独而复杂的控制(如包的大小,允许传输延迟时间等),这通过在每个WTP Header的第一个字节置位,然后在Header可变域填充一节或多节称之为TPI的结构进行控制,每个TPI的长度为2~8Byte,同样,TPI是可选的。
5.1 WTP 协议数据单元结构和编码
通用Header域
Continue 标志(CON)
在每个WTP Header的最开始一个固定的1bit,Continue 标志指出了后面是否接有TPI。如果该标准置位,表示后面有一个或多个TPI,如果清零,则表示WTP Header的可变域是空的,即没有TPI结构。这个标志同样用于每个TPI,用于指定该TPI后面是否还有TPI。置位则表示有,清零表示没有。
Group Trailer (GTR) and Transmission Trailer (TTR) Flag
翻译成中文就是组尾标记和传输尾标记,这两个标记用于分组传送控制。GTR表明这个WTP PDU包是这组最后一个PDU,TTR表明这个WTP PDU是整个消息的最后一个PDU,一个消息里面可包括多个组,一组里面可以最多有255个PDU。通常GTR和TTR标记组合起来使用,它们的编码如表4所示:
表4 GTR/TTR标志组合
一般的,应该置GTR和TTR位为1,即分组和重传功能无效。在分组传送和重传过程中,如果TTR置位,则GTR标志将被忽略。
Packet Sequence Number(PSN)
包的顺序号,在分组传送中,这个值用于指定分割包在整个消息中的位置。注意的是该值用一个字节来表示,并且这个域的值不能循环打卷,即如果PSN到了255以后,下一个包不能重新从0开始,所以一个组中最多只能有255个包。
PDU类型
该域用来指定前面列出来的PDU Type(Invoke,Ack等等)。
Re-transmission Indicator (RID)
重传标记,如果这个标记置位,表示先前的包传递超时或者丢失或者接收方没有响应,这个包是重传前面的包。
Transaction Identifier (TID)
我翻译为事务ID,对于每次传输,这个ID是唯一的,并且该值是递增的,每次增1。这样理解它的意义,在应用层,当我们客户端同时发起多个网络请求时,怎么来区分不同请求使用的是哪个或哪些PDU,特别在服务端,由于传输的延迟,先发出的PDU比后发的PDU先收到的情况下,怎么来区分该PDU是对哪个PDU的响应呢。这就是TID存在的必要性。TID号可以循环打卷,即如果到了最大的值,下次又可以从0开始,只是需要把TIDnew标志置1,表示这是新一轮的TID号,以避免和前面的TID混淆(实际上这种可能性很小)。TID占用两个字节。注意一点,如果是客户端向服务器发送PDU,TID的最高位必须清零,如果是服务器端给客户端发送PDU,TID的最高位必须置1,所以实际TID值只有15bit。
对于WTP Header中任何保留的位,都必须清零,除非有特别说明。
5.1.1 WTP Header 域的固定部分结构
Invoke PDU
表5 Invoke PDU结构
请求PDU,这种PDU是最重要的PDU之一,当我们需要向网络发送数据也好,请求接收数据也罢,只要是向网络请求服务,都是使用这种类型的PDU。
Invoke PDU中里面通用的域在前面提到过,这里不再说明。Version指明版本号,当前把它清零;TIDnew指定TID是否是新一轮的TID编号,在彩信开发中,一般把它清零;U/P和TCL标志有很大的关系,这里说明一下TCL。
TCL即Transaction Class,中文意思是事务类别。WTP提供三种事务类别,分别是Class 0,Class1和Class2。Class 0是不可靠的数据传输服务,它只管把数据发送出去,而不管对方是否收到,WSP里面不稳定的push方法就是使用的这种类型的事务类型。Class1是可靠的但没有确认的数据传输服务,即我可以保证把数据发给你,但是不管你那边能否及时处理。Class2是可靠的有应答的数据传输服务,即发送端可以保证把数据传递给接收端,接收端对所接收数据及时进行处理以后,需要给发送端一个确认,表示数据处理完毕。这样发送端可以继续发送下一批数据。彩信的传输中我们使用的是Class2事务类型。TCL就是选择所使用事务类型,编码如下,U/P指示在Class2中是否需要发送确认。置1表示需要发送确认,清零表示不需要发送确认。两位Res清零。
表6 Class 编码
Result PDU
这种PDU用于服务器给客户端一个请求的结果的响应,更多的表示上层WSP服务请求是否成功,如发送彩信时发送是否成功,接收彩信时Result PDU的Data域包括彩信的内容等等。其结构和编码如下表所示,PDU里面的TID是对应Invoke PDU里面的TID,最高位置1。
表7 Result PDU结构
Acknowledgement PDU(ACK)
该类型的PDU仅仅用于WTP层的服务确认,所以该PDU没有Data域。在Class2类型里面,如果服务器需要客户端在接收完数据后发送一个确认 PDU,客户端就发送Acknowledgement PDU给服务端,里面的TID是对应Result PDU里面的TID,最高位清零。PDU结构如下表所示:
表8 Ack PDU结构
结构里面的Tve/Tok指示:这次消息传输成功完成,注意:如果置1,则表示消息传输失败。如果清零,表示消息传输成功。
Abort PDU
正如字面意思一样,用于取消本次会话请求。如当彩信正在发送的时候,用户突然不想发送了,这时可以使用Abort PDU来撤消本次会话请求。其PDU结构如下图所示:
表9 Abort PDU结构
Abort type只有两种取值,0x00表示这是WTP层产生的中止信号;0x01表示这是上层协议产生的中止信号,如WSP层。Abort Reason 指示产生终止的原因,如果Abort type是0x00,则Abort Reason编码见 WAP-224-WTP-20010710-a.pdf的8.3.4.1节的table19。如果Abort type是0x01,则Abort Reason编码见 WAP-230-WSP-20010705-a.pdf附录A中的table19。同ACK Header一样,Abort PDU没有Data域,只是WTP层的服务。
Segmented Invoke PDU
这种类型的PDU用于客户端向服务器分组传输数据,当需要向服务器发送的数据量很大,而应用程序的收发能力有限时,需要对数据进行分组传送。PDU中前三个字节的内容和Invoke PDU一样,第四个字节是该PDU包在整个消息的位置号,从0开始编号。PDU结构如下表所示:
表10 Segmented Invoke PDU结构
注意,顺序号为零的分组PDU并不是Segmented Invoke PDU,而是一个GTR和TTR标志清零的Invoke PDU,通过前面对Invoke PDU说明我们知道,Invoke PDU并没有PSN域,但是系统默认其序号为0。所有实际的Segmented Invoke PDU的PSN是从1开始的。
Segmented Result PDU
如果服务器向客户端返回的数据量大于服务端的发送能力或者客户端的接收能力时,就使用该类型 PDU 把要发送的数据分成多个WTP PDU,来响应客户端的请求,PDU的DATA域包含返回的数据。该PDU的结构和Segmented Invoke PDU结构一样,如下表所示:
表11 Segmented Result PDU结构
经过测试,移动的MMSC返回的Segmented Result PDU是以每5个包为一组,在信号条件一般的情况下,每组平均丢1~2个包,且多发生在数据传送的后面分组中。
Negative Acknowledgement PDU(NAck)
如果分组的数据在传送过程中丢失了一部分,当接收方接收到带GTR或TTR标志的PDU时,就给发送方发送一个NAck PDU,里面包含丢失的PDU个数和所丢包的序号,然后发送方可以根据NAck PDU所包含的信息,重发这一部分PDU。这里,由于PSN是连续的,我们可以通过检查所接收PDU的PSN是否连续来判断是否有丢包,并且得到所丢包的PSN。
表12 Negative Acknowledgement PDU结构
Packet Sequence Number TPI (Optional)
记得前面提过用于控制WTP PDU传送过程的TPI吧,它是Transport Information Item的缩写,整个WTP协议有7种TPI,这里仅介绍和MMS传输相关的一种(PSN TPI),对于其它几种TPI,可以参照WAP-224-WTP-20010710-a.pdf.的8.4节。TPI位于WTP Header的可变域部分,一个TPI接在一个固定域的后面或者接在一个另一个TPI后面,固定域里面只能是前面七种类型的一种,而可变域里面可以有多个TPI,如果一个固定域或者TPI后面接有另一个TPI,需要把相应的CON标志位置1,否则清零。
数据接收端在发送端传输完一组WTP PDU后,在没有丢包的情况下,向发送端发送ACK PDU,PSN TPI放在ACK Header中的可变域部分,用于指示接收端已经接收到的WTP PDU顺序号,其结构如下
表13 PSN TPI结构
TPI Length域指定该TPI 后面包含了几个顺序号,一个顺序号占用1byte的空间,由于TPI Length只用2bit表示,所以一个PSN TPI最多只能包含3个顺序号,多于3个顺序号的时候需要用多个PSN TPI(这种情况经常发生)。PSN TPI的TPI Identity域编码为0x03。
到此,用于彩信收发的WTP部分基本知识说完了,下面来介绍应用。
一个WTP PDU最大的长度默认的情况下不超过1400 byte,包括WTP Header,区分于后面介绍的WSP PDU的长度控制,WTP PDU长度即是每次客户端通过无线收发模块一次向无线服务端发送的数据量大小,因此,这个长度值的设定关系到应用程序的功能稳定性。最大延迟时间默认为10s,典型的,在Class2模式下,如果一方发出需要确认的数据后,在这个时间内另一方没有响应的话,发送方会重发上次的数据,实践中发现,对于WAP网关,重发的次数取决于PDU的类型,对于客户端,重发的次数取决于客户端的策略。如果要修改这些值,需要使用Option TPI,其结构可参见WAP-224-WTP-20010710-a.pdf中的8.4.4节。不推荐设定太大长度的PDU,实践中发现,如果指定的PDU长度太长,由于无线网络信号的不稳定性,经常出现应用程序接收线程假死的现象,即线程一直接收不到数据,处于接收等待状态。因此,推荐的WTP PDU大小不超过5k,这样可以取得比较好的稳定性,又不至于收发次数过多。
在Class2模式下,当WTP PDU不超过长度限制的时候,其正常的传输过程如下:
图15 没有丢包情况下的WTP PDU传输
WSP 层向WTP层请求服务,WTP通过以WSP PDU为Data域的Invoke PDU发给服务器,然后服务器返回一个Result PDU,在Result PDU的Data部分包含服务器对WSP请求的响应结果,然后在WTP层,客户端发送一个Ack PDU给服务器,表示Result PDU收到。注意,如果客户端在指定时间内没有作出Ack确认的话,服务器会重发Result PDU。如果客户端一直没有确认,则本次传输做作废处理。
5.2 WTP 分组传送
当WSP层的PDU大小大于WTP每次能够传输的最大数据块大小时,就需要把WSP PDU分割成块数据,然后用多个WTP PDU来传输,这个过程对于WSP层服务是不可见的。即只要在WSP PDU大小在WSP层协商的大小范围内,WSP可以把它的PDU交给WTP层处理,而不用关心WTP层的具体处理过程。由于无线网络的数据传输的特殊性,我们一次向服务端传输的数据包不能太大。
先看Class2正常的传输过程,如图16所示:
图16 Class2没有丢包情况下的分组传输过程
分组传送的第一个包是Invoke PDU,后面的都是SegmInvoke PDU,整个传输过程的TID是一样的,PSN是递增的,传送完一组,需要接收方给出一个Ack PDU,前面说过,这个Ack PDU后面接了一个或多个PSN TPI,这是必须的。整个消息的最后一个PDU传送完毕以后,接收方对整个消息做一个结果返回,这个Result PDU的Data域包含了对WSP层的结果响应数据。不要忘记,在WTP层,需要再给接收方一个Ack。
当网络信号不好的时候,这种情况是经常存在的,即分组传送过程中存在丢包现象,这时需要我们客户端重传所丢失的PDU,过程如下:
图17 Class2丢包的分组传输过程
过程中,顺序号为1的PDU丢失了,在检测到TTR标志置位的SegmInvoke PDU后(即该组的最后一个PDU),接收方检查所有收到的PSN号,然后作出Nack应答,Nack PDU没有Data域,它的Header里面包含丢失的PDU的顺序号,这时,发送方重传丢失的PDU,该PDU和前面所丢的包唯一不同的地方就是RID标志是置1的。重传完以后,接收方再给出一个Ack应答。表示该组数据已经完整接收到。为了方便根据顺序号定位丢包的Data域数据,建议每个SegmInvoke PDU的Data域大小固定。
注意,SegmInvoke PDU限于客户端向服务器发送分组数据,而当服务器向客户端返回的结果数据需要分组的话,使用的是SegmResult PDU。这个在接收超过20k大小的彩信时经常用到,这时客户端可以采用与上面接收方一致的应答方法来控制SegmResult PDU的接收过程。