很多系統在實現原型時,由於初期對執行效率、處理速度等方面沒有苛刻的要求,都會設計成輪詢的模型。
而當我們實現完輪詢的架構後,可能由於各種需求,需要將系統整體的響應速度縮短。於是我們需要考慮,如何將輪詢的機制變為即時通知呢?(具體的應用比如參考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 PushClientManagermanager = PushClientManager('192.168.1.113',27000)manager.join('cutworker')manager.write('controlcenter','go')while(True): manager.wait(5) print 'do work...' manager.close()
PushClient.py
#encoding=utf8'''Created on 2012-4-15@author: chenggong'''import socketimport timeimport threadingBUFFERSIZE = 1024*20CMD_END_TAG = "#EndOfCmd#"taskmutex = threading.Event()reconnect_interval = 10 #you can set thisclass 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()