基于WebSocket構(gòu)建移動(dòng)端實(shí)時(shí)應(yīng)用
前言
對于傳統(tǒng)的網(wǎng)絡(luò)應(yīng)用,大部分情況下使用的是Http短連接,這意味著你發(fā)送一次請求,服務(wù)器給你返回響應(yīng)信息,然后連接就被斷掉了。然而現(xiàn)實(shí)生活 中,很多應(yīng)用實(shí)際上是需要一種實(shí)時(shí)機(jī)制的支持,比如微信,你就需要實(shí)時(shí)收到對方發(fā)送的回復(fù)信息。對于應(yīng)用處于后臺的情況下,你可以使用系統(tǒng)級別的推送服 務(wù),比如iOS下的APNS和Android下的GCM。應(yīng)用處于前臺時(shí)呢,則需要自己去和服務(wù)端建立一個(gè)Http長連接或者輪詢,這種方式對于服務(wù)器的性能要求還是比較高的。HTML5中提出了一種新的雙向通信協(xié)議--WebSocket,本文嘗試采用這種技術(shù)來實(shí)現(xiàn)以上的實(shí)時(shí)推送功能。
WebSocket
WebSocket是HTML5開始提供的一種在客戶端和服務(wù)器間持久連接的雙向通信網(wǎng)絡(luò)技術(shù)。 WebSocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn) RFC 6455,WebSocketAPI被W3C定為標(biāo)準(zhǔn)。協(xié)議本身使用新的ws://URL格式,但它是在標(biāo)準(zhǔn)HTTP上實(shí)現(xiàn)的。通過使用HTTP和 HTTPS端口,它避免了從Web代理后的網(wǎng)絡(luò)連接站點(diǎn)時(shí)引入的各種問題。HTML5規(guī)范不只描述了協(xié)議本身,還描述了使用WebSocket編寫客戶端 代碼所需要的瀏覽器API。在WebSocket API中,瀏覽器和服務(wù)器只需要做一個(gè)握手的動(dòng)作,然后,瀏覽器和服務(wù)器之間就形成了一條快速通道。兩者之間就直接可以數(shù)據(jù)互相傳送。
服務(wù)端實(shí)現(xiàn)
服務(wù)端采用了Facebook開源的Tornado框架,由于Tornado原生支持WebSocket協(xié)議,用它來實(shí)現(xiàn)服務(wù)端非常方便。
Tornado在WebSocket模塊中提供了一個(gè)WebSocketHandler類。這個(gè)類提供了和已連接的客戶端通信的WebSocket 事件和方法的鉤子。當(dāng)一個(gè)新的WebSocket連接打開時(shí),open方法被調(diào)用,而on_message和on_close方法分別在連接接收到新的消 息和客戶端關(guān)閉時(shí)被調(diào)用。
此外,WebSocketHandler類還提供了write_message方法用于向客戶端發(fā)送消息,close方法用于關(guān)閉連接。
以開關(guān)狀態(tài)改變檢測為例,服務(wù)端部署的代碼如下:
- class Switch(BaseModel):
- __tablename__ = 'switch'
- id = Column(Integer, primary_key = True)
- name = Column(String(30))
- status = Column(Boolean, server_default = text('False')) #開關(guān)當(dāng)前狀態(tài)
- level = Column(Integer) #最小可操作等級
- callbacks = []
- @classmethod
- def register(cls, callback):
- cls.callbacks.append(callback)
- @classmethod
- def unregister(cls, callback):
- cls.callbacks.remove(callback)
- def notifyCallbacks(self):
- for callback in self.callbacks:
- callback(self.id,self.status)
以上是封裝的開關(guān)對象,使用了SQLAlchemy作為ORM,其中BaseModel是繼承自declarative_base。 定義了兩個(gè)類方法,分別是注冊和移除回調(diào),另外一個(gè)實(shí)例方法來通知回調(diào)。
對外處理請求的handler:
- class GetSwitchStatusHandler(BaseWebsockHandler):
- def open(self):
- Switch.register(self.callback)
- def on_close(self):
- Switch.unregister(self.callback)
- def on_message(self,msg):
- pass
- def callback(self,switch_id,status):
- self.write_message('{"switch_id":"%s","status":"%d"}'%(switch_id,status))
BaseWebsockHandler繼承自tornado.websocket.WebSocketHandler,在initialize中對于SQLAlchemy的session進(jìn)行了初始化
由于在本例中不需要取客戶端的上行消息,故直接pass調(diào)了on_message方法。callback方法則用來處理回調(diào),將改變后的開關(guān)信息返回給客戶端。
客戶端實(shí)現(xiàn)
客戶端使用了Square開源的SocketRocket。
在iOS工程中安裝可以直接使用源碼,也可以用CocoaPods安裝,將以下依賴加入到PodFile中,再執(zhí)行install命令即可
pod 'SocketRocket', '~> 0.3.1-beta2'
SocketRocket的核心是SRWebSocket這個(gè)類,需要在使用WebSocket連接的ViewController中實(shí)現(xiàn)SRWebSocketDelegate。
- - (void)connect{
- webSocket.delegate = nil;
- [webSocket close];
- webSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:SWITCH_STATUS_URL]]];
- webSocket.delegate = self;
- NSLog(@"Opening Connection...");
- [webSocket open];
- }
- ···
- pragma mark - SRWebSocketDelegate
- - (void)webSocketDidOpen:(SRWebSocket *)webSocket;{
- NSLog(@"Websocket Connected");
- }
- - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;{
- NSLog(@":( Websocket Failed With Error %@", error);
- webSocket = nil;
- }
- - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;{
- NSLog(@"Received \"%@\"", message);
- }
- - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;{
- NSLog(@"WebSocket closed");
- webSocket = nil;
- }
把delegate指向自身,然后調(diào)用SRWebSocket中的方法發(fā)送請求即可。 收到下行消息會調(diào)用didReceiveMessage這個(gè)方法,如果需要在請求時(shí)攜帶參數(shù),可以用類似get請求的方法,將請求放在url字符串中。
到此為止WebSocket的基本功能就實(shí)現(xiàn)完畢了,完整demo可以參考我的Github。
原文地址。