很多系统在实现原型时,由于初期对执行效率、处理速度等方面没有苛刻的要求,都会设计成轮询的模型。
而当我们实现完轮询的架构后,可能由于各种需求,需要将系统整体的响应速度缩短。于是我们需要考虑,如何将轮询的机制变为实时通知呢?(具体的应用比如参考HTTP协议,它在设计初期,就是设计成客户端到服务端:请求、响应、断开。HTTP协议非常适合初期窄带宽且网络不稳定的情况下的数据传输,而直到今天,WEB应用仍然沿袭了它最初的设计架构,而大量应用均在此单向连接的基础上开发。
最初如果需要实现服务器通知机制,则需要客户端主动刷新。或者后台客户端去服务端轮询刷新(比如一些使用AJAX的实现)
当我们需要开发实时性非常强的应用的话,这种架构就不合适的。我们可能会使用到comet技术,简而言之就是:请求、挂起、响应。但这样还是有些投机取巧,并且给服务端带来较大的压力……)
OK,言归正传,我们在已有的轮询的系统上,如何影响最小、代码修改量最小的实现PUSH的机制呢?这是本文需要讨论解决的问题。
一般轮询的代码逻辑如下:
while(true){
sleep
logic
}
每次logic中去检测是否有想要的数据。
如果我们能有消息时将sleep迅速结束,而立刻执行logic,则可完成我们的PUSH转变。
那么问题转变为如何将sleep迅速结束?很自然的想到 WaitForSimpleObject,我们每次等一个信号量timeout的时长,可以由另一个线程来Release信号量,这样我们可以立刻执行任务。侦听线程负责接收其他模块的通讯。
问题自然转变为了一个消息派发机制,如果是跨进程的通讯,有人发起信号,有人接收信号。这个就是最典型的聊天室架构了,接口非常简单,每个模块可以join一个频道,可以在各个频道发言,发言后,所有侦听的模块都将立即释放线程锁来执行逻辑。发言的内容可以定义为各个模块自己才懂的私有协议,这样我们只需要修改代码为:
pushclient.joinchannel
while(true){
pushclient.wait
logic
}
即可,对整个系统改动极小。
以下给出python的CLIENT端的示例代码,我使用的TCP通讯,自己制定的通讯协议,这里可以仁者见仁了。
PushClientSample.py
#encoding=utf8
'''
Created on 2012-4-16
@author: chenggong
'''
from PushClient import PushClientManager
manager = PushClientManager('192.168.1.113',27000)
manager.join('cutworker')
manager.write('controlcenter','go')
while(True):
manager.wait(5)
print 'do work...'
manager.close()
#encoding=utf8
'''
Created on 2012-4-15
@author: chenggong
'''
import socket
import time
import threading
BUFFERSIZE = 1024*20
CMD_END_TAG = "#EndOfCmd#"
taskmutex = threading.Event()
reconnect_interval = 10 #you can set this
class ListenSockThread(threading.Thread):
def init(self,ip,port):
self.quit = False
self.msgbuffer = ""
self.ip = ip
self.port = port
self.channels = []
def close(self):
self.quit = True
self.join()
self.client.close()
def get_msgs(self,msg):
self.msgbuffer += msg
list = msg.split(CMD_END_TAG)
self.msgbuffer = list[-1]
if(len(list)>1):
return list[:-1]
return None
def get_client(self):
return self.client
def reconnect(self):
self.msgbuffer = ""
try:
self.client.close()
self.client = None
except:
pass
try:
self.client = PushClient(self.ip,self.port)
for c in self.channels:
self.client.listen(c)
except:
return False
return True
def run(self):
while(not self.quit):
try:
try:
msg = self.client.read(BUFFERSIZE)
except Exception,e:
msg = ""
if(str(e)!='timed out'):
raise e
list = self.get_msgs(msg)
if(list==None):
continue
for m in list:
self.client.on_get_msg(m)
except Exception,e:
if( not self.reconnect()):
time.sleep(reconnect_interval)
class PushClient:
def __init__(self,ip,port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((ip,int(port)))
self.sock.setblocking(False)
self.sock.settimeout(1)
def send(self,msg):
self.sock.send(msg+CMD_END_TAG)
def write(self,channel,msg):
self.send("say %s %s"%(channel,msg))
def listen(self,channel):
self.send("join %s"%channel)
def close(self):
self.send("quit")
self.sock.close()
def unlisten(self,channel):
self.send("exit %s"%channel)
def read(self,buffersize):
return self.sock.recv(buffersize)
#override this fun
def on_get_msg(self,msg):
if msg == 'go':
taskmutex.set()
taskmutex.clear()
class PushClientManager():
def __init__(self,ip,port):
self.listenThread = ListenSockThread()
self.listenThread.init(ip,port)
self.listenThread.start()
def write(self,channel,msg):
try:
self.listenThread.get_client().write(channel,msg)
except:
self.listenThread.reconnect()
def join(self,channel):
channel = channel.replace(" ","")
self.listenThread.channels.append(channel)
try:
self.listenThread.get_client().listen(channel)
except:
self.listenThread.reconnect()
def exit(self,channel):
channel = channel.replace(" ","")
self.listenThread.channels.remove(channel)
try:
self.listenThread.get_client().unlisten(channel)
except:
self.listenThread.reconnect()
def wait(self,timeout):
taskmutex.wait(timeout)
def close(self):
self.listenThread.close()
if __name__ == '__main__':
manager = PushClientManager('127.0.0.1',27000)
manager.join('cut worker')
while(True):
manager.wait(3)
print 'do work'
manager.close()