標籤:細節決定成敗 star color 演算法 n+2 標識符 protoc RKE 細節
好好好久沒有在cnblogs上寫部落格,不過在這裡寫的最早的一篇部落格的時間戳記,真是時間久遠啊,那時候還沒畢業。不在cnblogs的期間,在github pages、簡書上寫過部落格,github pages的markdown還是不錯的,不過百度不能檢索到文章,也就是通過百度,永遠無法導流搜尋到我的文章(感動moving),簡書感覺更適合抒情雞湯,可能我不太能融入那個使用者群體。不過現在我回來了,那些在github pages上的文章,我暫時也不遷移了,畢竟人生本來就不完美,提醒自己不能有強迫症(內心默念三次)。續上上一篇部落格的時間(2016-04-29)繼續回到這裡,中間的間隔兩年多,甚至更長。這段空白時間的大概情況介紹完了~
---------------------髒兮兮的分割線---------------------
言歸正傳,最近因為公司產品的需要,計劃在移動端開發即時聊天的通訊功能。即時聊天的第三方SDK供應商也是非常多的,因為項目高度的自由定製性,資料隱私等方面的考慮,最終Server-Client端都由自己來實現,服務端採用worker man的PHP socket伺服器架構。
在長串連雙向通訊上,選擇的是WebSocket協議。開發主要負責iOS Client端的開發,按照開發第三方SDK的標準,將關鍵的部分封裝起來,只留出必要的API供外部調用,將相關代碼模組化,方便後期向公司其他項目中移植聊天模組。(不能自己坑自己,遇到移植的需求的可能性是非常大的,所以與其散漫的寫代碼,不如按照SDK的標準去做開發。)
WebSocket
WebSocket 通訊協定在2008年誕生,2011年成為國際標準。WebSocket 通訊協定本質上是一個基於 TCP 的協議。是建立在 TCP 協議之上的全雙工系統通訊協議,與 HTTP 協議有著良好的相容性。預設連接埠也是80和443,並且握手階段採用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP Proxy 伺服器,所以伺服器端的實現比較容易。協議標識符是ws,請求地址格式:ws://example.com:80/path
握手過程:
為了建立一個 WebSocket 串連,用戶端首先要向伺服器發起一個 HTTP 要求,這個請求和通常的 HTTP 要求不同,包含了一些附加頭資訊。如下所示:
用戶端請求Header:
1 --- request header ---2 GET /chat HTTP/1.13 Upgrade: websocket4 Connection: Upgrade5 Host: 127.0.0.1:80016 Origin: http://127.0.0.1:80017 Sec-WebSocket-Key: hj0eNqbhE/A0GkBXDRrYYw==8 Sec-WebSocket-Version: 13
其中附加頭資訊"Upgrade: WebSocket"表明這是一個申請協議升級的 HTTP 要求,伺服器端解析這些附加的頭資訊,根據Sec-WebSocket-Key的字串,通過sha1演算法處理,將response資訊(sec-Websocket-Accept字串)返回給用戶端,用戶端能成功解碼字串,就和伺服器端的 WebSocket串連就建立起來了。
伺服器的Response:
1 HTTP/1.1 101 Switching Protocols2 Content-Length: 03 Upgrade: websocket4 Sec-Websocket-Accept: ZEs+c+VBk8Aj01+wJGN7Y15796g=5 Server: TornadoServer/4.5.16 Connection: Upgrade7 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模組。想到了一句話:細節決定成敗。所以打磨好生活工作學習中的每一個細節~
基於WebSocket協議的iOS端即時聊天