通常我们在编写网络应用时,会制定自己的协议格式。
我们的协议一般是在TCP协议层上建立的,TCP是一个流式协议,它每次接收的不一定和我们send的字节数相同,所以我们的协议通常会有2层:第一层分割数据包(包协议),第二层才是把数据包解析成我们自己的协议格式,通常这2层非常靠近,以致于我们不愿意去区分它。
本文打算完成一个简单的协议处理框架,采用的协议是一个简单的基于字符串的协议。在上一篇文章里,我们完成了一个简单的客户端,本章就继续在这个客户端的基础上完成,当然存在一个问题是没有服务器去测试。我想其实做些简单的测试并不需要真的存在一个服务器,何况我们这个协议是简单的字符串协议,模拟字符串收到的过程就可以完成测试了。
就网络应用来说,除了连接过程有区别以外,连接以后我们不用去区分哪台是客户机,哪台是服务器,我们只要规定它们用一样的协议格式来通信。我编写了这个简单的协议处理框架,并且预留了一个协议扩充的功能,代码如下:
from sys import stdout
import logging, struct
# 我习惯这样使用logger
LOG = logging.getLogger ()
# Message类,只要实现了getAction的类,都可以与这个框架一起使用
class Message:
def __init__(self, body=''):
self.body = body
def getAction(self):
return 0
# Message工厂类,这个类与Message一起完成协议解析
class MessageFactory:
def parseMessage(self, data):
if len(data) < 4:
return None, data
(length,) = struct.unpack('I', data[:4])
body = data[4:4+length]
if len (body) == length:
data = data[4+length:]
msg = Message(body)
return msg, data
elif len(body) > length:
print 'Error! Error! Error!'
assert False
return None, data
def packMessage(self, msg):
return struct.pack ('I', len(msg.body)) + msg.body
以上2个类是用户协议处理部分,这个协议比较简单,4字节的长度值加上与此长度匹配的字符串构成一个包。
下面的ConnectionHandle类是协议处理框架,它的__init__方法接受一个message factory,每次收到数据,将调用factory的parseMessage处理,每次发送message,也使用factory的packMessage把message串化。我去掉了前一章例子中的一些代码,这样看起来会更清晰。
当完整解析出一个message时,会查找通过addHandler函数注册的message处理器。本例因为都是文本字符串,所以Message.getAction()返回的值也相同,各个message也会调用同一个处理器。
def __init__ (self, msgFactory, **args):
self._msgFactory = msgFactory
self._recvBuf = ''
self._handlers = { }
def onMessage0(self, msg):
print 'received message:', msg.body
def addHandler (self, action, handler):
self._handlers.setdefault (action, []).append (handler)
def sendMessage (self, msg):
self.transport.write (self._msgFactory.packMessage (msg))
def dataReceived (self, data):
self._recvBuf += data
while True:
msg, self._recvBuf = self._msgFactory.parseMessage (self._recvBuf)
if msg is None:
break
self.onMessage (msg)
def onMessage (self, msg):
action = msg.getAction ()
if action in self._handlers:
LOG.debug('Received message: %s', str(action))
for handler in self._handlers[action]:
handler (msg)
else:
self.onUnprocessedMessage (msg)
def onUnprocessedMessage (self, msg):
LOG.warning ('Unprocessed message %s', str(msg.getAction ()))
以下是测试代码,OnMsg是一个message处理器,因为这部分测试不需要reactor,所以我模拟了数据“流”入dataReceived函数的过程。
def OnMsg(msg):
print 'received message:', msg.body
msgFactory = MessageFactory()
# 把MessageFactory传进去,如果有不同的协议解析器,则可灵活使用
connection = ConnectionHandle(MessageFactory())
connection.addHandler(0, OnMsg)
# 初始化模拟数据,把2个message串化
msg = Message()
msg.body = 'China'
buffer = msgFactory.packMessage(msg)
msg.body = 'People\'s Republic of China'
buffer += msgFactory.packMessage(msg)
# 模拟数据“流”入dataReceived的过程,下面这些pos用来模拟每次发送后的位置
bytes_pos = [0, 3, 7, 9, 10, 11, 14, 15, 17, 21, 28, 32, 35, 39]
bytes_pos = [i for i in bytes_pos if i < len(buffer)]
bytes_pos.append(len(buffer))
for i in range(len(bytes_pos) - 1):
start, end = bytes_pos[i], bytes_pos[i+1]
connection.dataReceived(buffer[start:end])
之所以把它称为框架,是因为Message和MessageFactory是作为参数传递进去的,要实现新的协议,只需要重新编写这2个类就可以了。
另外,message处理器可以是类成员方法,不像C语言里面那样,要使用回调函数,这得益于python语言的灵活。