基于WebSocket構建移動端實時應用
前言
對于傳統的網絡應用,大部分情況下使用的是Http短連接,這意味著你發送一次請求,服務器給你返回響應信息,然后連接就被斷掉了。然而現實生活中,很多應用實際上是需要一種實時機制的支持,比如微信,你就需要實時收到對方發送的回復信息。對于應用處于后臺的情況下,你可以使用系統級別的推送服務,比如iOS下的APNS和Android下的GCM。應用處于前臺時呢,則需要自己去和服務端建立一個Http長連接或者輪詢,這種方式對于服務器的性能要求還是比較高的。HTML5中提出了一種新的雙向通信協議--WebSocket,本文嘗試采用這種技術來實現以上的實時推送功能。
WebSocket
WebSocket是HTML5開始提供的一種在客戶端和服務器間持久連接的雙向通信網絡技術。 WebSocket通信協議于2011年被IETF定為標準 RFC 6455,WebSocketAPI被W3C定為標準。協議本身使用新的ws://URL格式,但它是在標準HTTP上實現的。通過使用HTTP和 HTTPS端口,它避免了從Web代理后的網絡連接站點時引入的各種問題。HTML5規范不只描述了協議本身,還描述了使用WebSocket編寫客戶端代碼所需要的瀏覽器API。在WebSocket API中,瀏覽器和服務器只需要做一個握手的動作,然后,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。
服務端實現
服務端采用了非死book開源的Tornado框架,由于Tornado原生支持WebSocket協議,用它來實現服務端非常方便。
Tornado在WebSocket模塊中提供了一個WebSocketHandler類。這個類提供了和已連接的客戶端通信的WebSocket 事件和方法的鉤子。當一個新的WebSocket連接打開時,open方法被調用,而on_message和on_close方法分別在連接接收到新的消息和客戶端關閉時被調用。
此外,WebSocketHandler類還提供了write_message方法用于向客戶端發送消息,close方法用于關閉連接。
以開關狀態改變檢測為例,服務端部署的代碼如下:
class Switch(BaseModel):
tablename = 'switch'
id = Column(Integer, primary_key = True)
name = Column(String(30))
status = Column(Boolean, server_default = text('False')) #開關當前狀態
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)</pre><br />
</figure>
以上是封裝的開關對象,使用了SQLAlchemy作為ORM,其中BaseModel是繼承自declarative_base。定義了兩個類方法,分別是注冊和移除回調,另外一個實例方法來通知回調。
對外處理請求的handler: def callback(self,switch_id,status): self.write_message('{"switch_id":"%s","status":"%d"}'%(switch_id,status))
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))</pre><br />
</figure>
BaseWebsockHandler繼承自tornado.websocket.WebSocketHandler,在initialize中對于SQLAlchemy的session進行了初始化
由于在本例中不需要取客戶端的上行消息,故直接pass調了on_message方法。callback方法則用來處理回調,將改變后的開關信息返回給客戶端。
客戶端實現
客戶端使用了Square開源的SocketRocket。
在iOS工程中安裝可以直接使用源碼,也可以用CocoaPods安裝,將以下依賴加入到PodFile中,再執行install命令即可
pod 'SocketRocket', '~> 0.3.1-beta2'
SocketRocket的核心是SRWebSocket這個類,需要在使用WebSocket連接的ViewController中實現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; }</pre>
</figure>把delegate指向自身,然后調用SRWebSocket中的方法發送請求即可。收到下行消息會調用didReceiveMessage這個方法,如果需要在請求時攜帶參數,可以用類似get請求的方法,將請求放在url字符串中。
到此為止WebSocket的基本功能就實現完畢了,完整demo可以參考我的Github。
參考
《Introduction to Tornado》http://book.douban.com/subject/7906788/
來自:http://shiningio.com/2014/03/16/build-real-time-app-with-websocket/