用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/
关于这个问题,经过修改后。得出了几点儿结论:
TCP是基于流的,经过TCP/IP公网传输后,长报文极有可能发生TCP传输报文分段传输的情况;
以上情况如果是报文极短的情况,需要了解设备端网络设备与操作系统之间的配合,缓存管理,并对比内网表现才能够得出是否是1)的情况;否则,需要设备端将TCP报文分段传输情况降到最低。
Twisted和其他框架都有类支持多种协议。最好说服客户修改成带长度信息或者结束符的协议(请参考Twisted文档中IntNetString等说明)
在许多情况下,客户无法或者拒绝修改的情况下,可以修改dataReceived()中代码实现TCP报文拼合。
Twisted标准文档中建议需要持久存储的保存在Factory中。这句话需要倒过来理解:跨连接的可以共享的参数保存在Factory中,当前连接当前设备的参数可以保存在Protocol中。所以在Protocol中可以保存TCP报文缓存器。
与5)类似,可以实现基础Protocol,这需要对Twisted底层较为了解。
以上5)6),需要设备和服务器间采用长连接TCP。UDP和短连接TCP不适用。
如果用于实时数据采集,数据不附带时间戳的话,那么拼合断开的TCP报文仅可以修复数据,无法弥补它在时间轴上的位移。
特此更新,以饷同仁。