好好很久沒有在cnblogs上寫博客,不過在這裏寫的最先的一篇博客的時間戳,真是時間久遠啊,那時候還沒畢業。不在cnblogs的期間,在github pages、簡書上寫過博客,github pages的markdown仍是不錯的,不過百度不能檢索到文章,也就是經過百度,永遠沒法導流搜索到個人文章(感動moving),簡書感受更適合抒情雞湯,可能我不太能融入那個用戶羣體。不過如今我回來了,那些在github pages上的文章,我暫時也不遷移了,畢竟人生原本就不完美,提醒本身不能有強迫症(心裏默唸三次)。續上上一篇博客的時間(2016-04-29)繼續回到這裏,中間的間隔兩年多,甚至更長。這段空白時間的大概狀況介紹完了~git
---------------------髒兮兮的分割線---------------------github
言歸正傳,最近由於公司產品的須要,計劃在移動端開發即時聊天的通信功能。即時聊天的第三方SDK供應商也是很是多的,由於項目高度的自由定製性,數據隱私等方面的考慮,最終Server-Client端都由本身來實現,服務端採用worker man的PHP socket服務器架構。web
在長鏈接雙向通訊上,選擇的是WebSocket協議。開發主要負責iOS Client端的開發,按照開發第三方SDK的標準,將關鍵的部分封裝起來,只留出必要的API供外部調用,將相關代碼模塊化,方便後期向公司其餘項目中移植聊天模塊。(不能本身坑本身,遇到移植的需求的可能性是很是大的,因此與其散漫的寫代碼,不如按照SDK的標準去作開發。)算法
WebSocket服務器
WebSocket 協議在2008年誕生,2011年成爲國際標準。WebSocket 協議本質上是一個基於 TCP 的協議。是創建在 TCP 協議之上的全雙工通信協議,與 HTTP 協議有着良好的兼容性。默認端口也是80和443,而且握手階段採用 HTTP 協議,所以握手時不容易屏蔽,能經過各類 HTTP 代理服務器,因此服務器端的實現比較容易。協議標識符是ws,請求地址格式:ws://example.com:80/path
websocket
握手過程:markdown
爲了創建一個 WebSocket 鏈接,客戶端首先要向服務器發起一個 HTTP 請求,這個請求和一般的 HTTP 請求不一樣,包含了一些附加頭信息。以下所示:網絡
客戶端請求Header:架構
1 --- request header --- 2 GET /chat HTTP/1.1 3 Upgrade: websocket 4 Connection: Upgrade 5 Host: 127.0.0.1:8001 6 Origin: http://127.0.0.1:8001 7 Sec-WebSocket-Key: hj0eNqbhE/A0GkBXDRrYYw== 8 Sec-WebSocket-Version: 13
其中附加頭信息"Upgrade: WebSocket"代表這是一個申請協議升級的 HTTP 請求,服務器端解析這些附加的頭信息,根據Sec-WebSocket-Key的字符串,經過sha1算法處理,將response信息(sec-Websocket-Accept字符串)返回給客戶端,客戶端能成功解碼字符串,就和服務器端的 WebSocket鏈接就創建起來了。socket
服務器的Response:
1 HTTP/1.1 101 Switching Protocols 2 Content-Length: 0 3 Upgrade: websocket 4 Sec-Websocket-Accept: ZEs+c+VBk8Aj01+wJGN7Y15796g= 5 Server: TornadoServer/4.5.1 6 Connection: Upgrade 7 Date: Wed, 21 Jun 2017 03:29:14 GMT
雙方就能夠經過這個鏈接通道自由的傳遞信息,而且這個鏈接會持續存在直到客戶端或者服務器端的某一方主動的關閉鏈接。
使用封裝Websocket的SocketRocket(Objective-C)
上面是WebSocket握手鍊接通訊,而站在巨人的肩膀上,這裏使用的是Github上facebook的SocketRocket項目,這是關於WebSocket的Objective-C的封裝,提供簡單的API,讓開發者不用去跟底層協議打交道,而是關注於鏈路上的數據處理,邏輯層。關於SocketRocket的Features使用等,在Github上有詳細介紹,使用起來也很是簡單。須要注意SRWebSocketDelegate協議的相關方法:
//當收到服務器的Message時調用,這裏的message是id類型,能夠是NSString,也能夠是NSData。
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;
//當與服務器創建鏈接時調用
- (void)webSocketDidOpen:(SRWebSocket *)webSocket;
//當發生未知錯誤的時調用,多是網絡緣由等
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
//當關閉WebSocket時調用
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
//接收到服務器的Pong時調用
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload;
//返回YES表示對messages進行轉換,以NSString的形式發送,返回NO,表示跳過NSData->NSString的轉換,直接以NSData來傳遞。默認YES
- (BOOL)webSocketShouldConvertTextFrameToString:(SRWebSocket *)webSocket;
使用方式與以前學Java時使用的socket通信相似,大概流程以下所示:init websocket -> open -> connected -> sendMsg -> handle server response -> close
具體的代碼也是很是容易在網上找到的,就不大段的貼代碼了。
上述的通道握手創建而且能與服務器簡單通訊後,就要考慮各類狀況的處理,包括斷網,信號差等,就須要考慮斷線重連,發送心跳包肯定是否與服務器保持着鏈接的狀態。
這裏心跳包的發送是定時執行的,使用NSTimer的方式。
1 dispatch_main_async_safe(^{ 2 3 [self destoryHeartBeat]; 4 5 __weak typeof(self) weakSelf = self; 6 //心跳設置爲3分鐘,NAT超時通常爲5分鐘 7 _heartBeat = [NSTimer scheduledTimerWithTimeInterval:3 * 60 repeats:YES block:^(NSTimer * _Nonnull timer) { 8 NSLog(@"heart"); 9 //和服務端約定好發送什麼做爲心跳標識,儘量的減少心跳包大小 10 [weakSelf sendHeartBeatMessage]; 11 }]; 12 [[NSRunLoop currentRunLoop]addTimer:_heartBeat forMode:NSRunLoopCommonModes]; 13 })
錯誤斷網等重連的實現:
1 - (void)reConnect { 2 3 [self stopSocket]; 4 5 if (_connectInterval < 2) { 6 _connectInterval = 2; 7 }else{ 8 _connectInterval = _connectInterval + 2; 9 } 10 11 // 斷開鏈接後每過n+2秒後從新創建一次鏈接 12 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_connectInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 13 [self startSocket]; 14 }); 15 }
一個好的與服務器鏈接的Websocket模塊須要細細的打磨,這裏展現的都是很粗糙的模塊,須要根據之後的需求,出現的問題進行不斷的修正,纔能有一個好用的Websocket模塊。想到了一句話:細節決定成敗。因此打磨好生活工做學習中的每個細節~