首页 > Twisted 如何缓存拼接前后TCP报文

Twisted 如何缓存拼接前后TCP报文

用Twisted做了一个简单的TCPServer。使用自己的模拟客户端进行测试无问题。但是客户设备出现一个奇葩问题。该设备使用一个国产的WiFi模块上传数据。但是出现将完整数据包截断分成两个TCP报文上传的现象。

这个可能是无法避免的事情。毕竟嵌入式系统里RAM总是有限的。

解决方法之一,是将datareceived()改成linereceived(),即每个数据包成为带结束符号的一行。但是这在二级制数据里面似乎还需要转义字符之类的。

解决方法之二:是在TCP连接中将前后两个TCP报文进行拼合。但是这个上下文存在哪里呢?是Factory里?好像不能够是全局变量。


首先,TCP协议是基于流的,所以你遇到的“出现将完整数据包截断分成两个TCP报文上传的现象”这个问题是很正常的,并不奇葩,“使用自己的模拟客户端进行测试无问题”的原因只是网络好。

TCP一端发送的数据是这样:

接收方收到的很可能是这样:

针对这个问题,则需要接收方对接收到的数据进行分割或者合并。

主要有以下几种方式:
1、use fixed length messages
使用固定长度的消息。比如每个长度4字节,那么接收的时候按每条4字节拆分就可以了。
2、use a fixed length header that indicates the length of the body
使用固定长度的Header,Header中指定Body的长度(字节数),将信息的内容放在Body中。例如Header中指定的Body长度是100字节,那么Header之后的100字节就是Body,也就是信息的内容,100字节的Body后面就是下一条信息的Header了。
3、using a delimiter; for example many text-based protocols append a newline (or CR LF pair) after every message
使用分隔符。例如许多文本内容的协议会在每条消息后面加上换行符(CR LF,即”\r\n”),也就是一行一条消息。当然也可以用其他特殊符号作为分隔符,例如逗号、分号等等。

针对按换行符分割的方式,可以使用LineOnlyReceiver的lineReceived,如果每条消息本身有可能包含换行符,那么这种方法就不行了。

所以一般比较好的解决方法是第二种:用一个固定字节数的Header前缀来指定Body的字节数,以此来分割消息,Twisted中使用的API是Int32StringReceiver:

# -*- coding:utf-8 –*-

from twisted.protocols.basic import Int32StringReceiver
from twisted.internet.protocol import Factory
from twisted.internet import reactor

class TcpServerHandle(Int32StringReceiver):

    # 新的连接建立
    def connectionMade(self):
        print 'connectionMade'

    # 连接断开
    def connectionLost(self, reason):
        print 'connectionLost'

    # 接收到新的数据
    def stringReceived(self, data):
        print 'stringReceived:' + data

factory = Factory()
factory.protocol = TcpServerHandle
reactor.listenTCP(8080, factory)
reactor.run()

最后,推荐几篇我写的相关博客:
TCP消息边界问题及按行分割消息:http://xxgblog.com/2014/08/21/mina-netty-twisted-2/
TCP消息固定大小的前缀(Header):http://xxgblog.com/2014/08/22/mina-netty-twisted-3/
定制自己的协议:http://xxgblog.com/2014/08/25/mina-netty-twisted-4/


关于这个问题,经过修改后。得出了几点儿结论:

  1. TCP是基于流的,经过TCP/IP公网传输后,长报文极有可能发生TCP传输报文分段传输的情况;

  2. 以上情况如果是报文极短的情况,需要了解设备端网络设备与操作系统之间的配合,缓存管理,并对比内网表现才能够得出是否是1)的情况;否则,需要设备端将TCP报文分段传输情况降到最低。

  3. Twisted和其他框架都有类支持多种协议。最好说服客户修改成带长度信息或者结束符的协议(请参考Twisted文档中IntNetString等说明)

  4. 在许多情况下,客户无法或者拒绝修改的情况下,可以修改dataReceived()中代码实现TCP报文拼合。

  5. Twisted标准文档中建议需要持久存储的保存在Factory中。这句话需要倒过来理解:跨连接的可以共享的参数保存在Factory中,当前连接当前设备的参数可以保存在Protocol中。所以在Protocol中可以保存TCP报文缓存器。

  6. 与5)类似,可以实现基础Protocol,这需要对Twisted底层较为了解。

  7. 以上5)6),需要设备和服务器间采用长连接TCP。UDP和短连接TCP不适用。

  8. 如果用于实时数据采集,数据不附带时间戳的话,那么拼合断开的TCP报文仅可以修复数据,无法弥补它在时间轴上的位移。

特此更新,以饷同仁。

【热门文章】
【热门文章】