前言
在新公司入職的兩個月時間里學習到了不少新的知識肴焊。其中聊天室就是近期在研究公司代碼時學習碰到的一個技術(shù)點就乓。其實在上一家公司也接觸過做即時通訊的需求烹吵,但之前由于工期比較緊所以就選擇了用環(huán)信(一個比較主流的第三方即時通訊sdk)來實現(xiàn)惧眠。但其中實現(xiàn)的原理沒有深究打颤,學到的東西并不多暴拄。因此借著這個機會拜讀一下同事的代碼,寫下這篇學習筆記補充一下即時通訊這一塊漏掉的知識编饺。
實現(xiàn)方式
iOS 不用第三方sdk實現(xiàn)即時通訊的主流方法主要有4種:1乖篷、基于Scoket原生:代表框架 CocoaAsyncSocket。2反肋、基于WebScoket:代表框架 SocketRocket那伐。3、基于MQTT:代表框架 MQTTKit。4罕邀、基于XMPP:代表框架 XMPPFramework畅形。這四種方式都各有利弊。我們公司選擇的實現(xiàn)方式是基于WebSocket實現(xiàn)的诉探,因此我先從這個方式入手日熬,其他3種方式待以后探究。
什么是WebSocket?
我們在客戶端開發(fā)的過程中肾胯,相信我們遇到最多的網(wǎng)絡(luò)協(xié)議是HTTP協(xié)議竖席。WebSocket 和HTTP 一樣是網(wǎng)絡(luò)協(xié)議的一種。那么我們已經(jīng)有強大的HTTP協(xié)議了為什么還需要另外一種網(wǎng)絡(luò)協(xié)議呢敬肚?那是因為HTTP協(xié)議有一個很大的弊端--通訊只能有客戶端發(fā)起毕荐。客戶端發(fā)起的request 和服務(wù)器下發(fā)的respond 是一一對應(yīng)的艳馒。在HTTP協(xié)議下如果客戶端有連續(xù)的狀態(tài)變化憎亚,客戶端想要獲取就比較麻煩。我們只能依靠輪詢機制(每隔一段時間向服務(wù)器請求一次弄慰,了解服務(wù)器最新的數(shù)據(jù))來獲取第美。但是輪詢不但耗費性能,而且也并非真正意義上的實現(xiàn)即時性陆爽。這就導致HTTP 協(xié)議并不適合用于即時通訊什往。而WebSocket就是解決這一問題而發(fā)明的。WebSocket借用了HTTP的協(xié)議來完成一次握手慌闭,在建立連接后别威,WebSocket 服務(wù)器和 Browser/Client Agent 都能主動的向?qū)Ψ桨l(fā)送或接收數(shù)據(jù)了。
如何在iOS項目中使用WebSocket
在上面介紹即時通訊的實現(xiàn)方式時提到驴剔,WebSocket的代表框架是SocketRocket兔港。團隊的項目也是用SocketRocket來實現(xiàn)聊天室功能的。因此我也來順帶了解一下SocketRocket這個框架仔拟。SocketRocket是Facebook開源的一個用于 iOS, macOS and tvOS客戶端的websocket框架。SocketRocket是對WebSocket的封裝飒赃。
1利花、集成
集成SocketRocket方法非常簡單,用cocoapods 在podfile中加入 pod 'SocketRocket'载佳,執(zhí)行pod install指令就可以完成集成炒事。
2、實現(xiàn)聊天室功能需要做哪些事情蔫慧?
那我們在一次建立一個即時聊天的過程中挠乳,我們需要做哪些事情呢?①、首先我們需要建立連接 ②睡扬、遵守并指定代理 ③盟蚣、打開連接加載請求 ④、關(guān)閉連接 ⑤卖怜、發(fā)送消息 ⑥屎开、通過代理方法來獲取接收到的消息
-(void)SRWebSocketOpen{
? ? ? ? ? ? //如果是同一個url return
? ? if (self.socket) {
? ? ? ? ? ? ? ? return;
? ? ? ? }
? ? self.socket = [[SRWebSocket alloc] initWithURLRequest: ? ? ? ? ? ? ? ? ? [NSURLRequest requestWithURL:[NSURL URLWithString:@"ws:xxxxxxxxxxx"]]];//這里填寫你服務(wù)器的地址
? ? self.socket.delegate = self;? //SRWebSocketDelegate 協(xié)議
? ? ? ? ? ? [self.socket open];? ? //開始連接
}
①、建立連接 ②马靠、遵守并指定代理 ③奄抽、打開連接加載請求
在SRWebSocketOpen方法里,我們先創(chuàng)建一個SRWebSocket對象甩鳄,并設(shè)置了SocketRocket的回調(diào)代理逞度。在完成以上兩個操作后,調(diào)用[_socket Open];開始建立連接妙啃。
-(void)SRWebSocketClose{
? ? ? ? if (self.socket){
? ? ? ? ? ? ? ? [self.socket close];
? ? ? ? ? ? ? ? self.socket = nil;
? ? ? ? ? ? ? ? //斷開連接時銷毀心跳
? ? ? ? ? ? ? ? [self destoryHeartBeat];
? ? ? ? }
}
④档泽、關(guān)閉連接:
在SRWebSocketClose方法里,通過調(diào)用[_socket close]; 關(guān)閉連接并銷毀心跳彬祖。等一下茁瘦,什么是心跳?這部分內(nèi)容在下一個章節(jié)說明储笑,暫時先可以理解為檢測連接是否正常的一個機制甜熔。
- (void)sendData:(id)data {
? ? ? ? [self.socket send:data] ;
}
⑤、發(fā)送消息
- (void)sendData:(id)data 方法中通過調(diào)用[_socket send:data];方法發(fā)送消息突倍。這個data可以是一個UTF8的字符串或者NSData對象腔稀。
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message? {
? ? if (webSocket == self.socket) {
? ? ? ? ? ? ? ? NSLog(@"接收到后臺下發(fā)的信息,在這解析羽历。");
? ? ? ? }
}
⑥焊虏、通過代理方法來獲取接收到的消息
當接收到信息時,會通過- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message代理方法回調(diào)秕磷。
按照常規(guī)诵闭,實現(xiàn)以上方法就已經(jīng)實現(xiàn)了即時聊天的基本功能。但是澎嚣,在現(xiàn)實的使用過程中疏尿,往往會出現(xiàn)一些特殊的情況需要處理。例如:1易桃、在建立了鏈接后褥琐,如何確保客戶端和服務(wù)端的之間的鏈接有效可用晤郑?2敌呈、雖然WebSocket通過握手建立了鏈接贸宏,但是在鏈接過程中可能遇到因為網(wǎng)絡(luò)不好等的原因?qū)е碌倪B接中斷情況,鏈接斷了如何處理磕洪?3吭练、在即時聊天項目中通常還要實現(xiàn)APSN即時推送功能,那服務(wù)器如何判斷什么時候通過WebSocket發(fā)送消息褐鸥,什么時候走APNs離線推送呢线脚?
解決以上三個問題,就涉及到了三個webSocket的機制
1叫榕、心跳機制
用NSTimer每隔固定時間向服務(wù)器發(fā)一個心跳包浑侥,以此來告訴服務(wù)器,這個客戶端還活著晰绎。事實上這是為了保持長連接寓落,至于這個包的內(nèi)容,是沒有什么特別規(guī)定的荞下,不過一般都是很小的包伶选,或者只包含包頭的一個空包。
//初始化心跳
- (void)initHeartBeat {
? ? ? dispatch_main_async_safe(^{
? ? ? ? ? ? ? ? ? ? [self destoryHeartBeat]; ? ? ? ? //心跳設(shè)置為3分鐘尖昏,NAT超時一般為5分鐘
? ? ? ? ? ? ? ? ? ? heartBeat = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(sentheart) userInfo:nil repeats:YES]; ? ? ? ? //和服務(wù)端約定好發(fā)送什么作為心跳標識仰税,盡可能的減小心跳包大小
? ? ? ? ? ? ? ? ? ? [[NSRunLoop currentRunLoop]addTimer:heartBeat forMode:NSRunLoopCommonModes];
? ? ? ? })
}
//取消心跳
- (void)destoryHeartBeat {
? ? dispatch_main_async_safe(^{
? ? ? ? ? ? ? ? if (heartBeat) {
? ? ? ? ? ? ? ? ? ? ? ? if ([heartBeat respondsToSelector:@selector(isValid)]){
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if ([heartBeat isValid]){
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [heartBeat invalidate];
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? heartBeat = nil;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? }
? ? })
}
-(void)sentheart{
? ? ? ? ? ? //發(fā)送心跳 和后臺可以約定發(fā)送什么內(nèi)容? 一般可以調(diào)用ping? 這里根據(jù)后臺的要求 發(fā)送了data給他
? ? ? ? ? ? [self sendData:@"heart"];
}
2、重連機制
重連機制比較好理解抽诉,值得注意的是當重連到一定次數(shù)仍然失敗后要提示用戶網(wǎng)絡(luò)存在問題陨簇,就沒必要再繼續(xù)重連了。
- (void)reConnect {
? ? ? ? ? ? [self SRWebSocketClose];
? ? ? ? ? ? ? ? if (reConnectTime > 50) {
? ? ? ? ? ? ? ? // 重連50次都失敗
? ? ? ? ? ? ? ? reConnectTime = 0;
? ? ? ? ? ? ? ? // 在這里彈出提示告知用戶網(wǎng)絡(luò)不好
? ? ? ? ? ? ? ? ? ? ? ? return;
? ? ? ? ? ? }
? ? ? ? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
? ? ? ? ? ? ? ? ? ? self.socket = nil;
? ? ? ? ? ? ? ? ? ? [self SRWebSocketOpen];
? ? ? ? ? ? NSLog(@"重連");
? ? ? ? ? ? });
? ? ? ? ? ? ? ? ? ? reConnectTime ++;
? ? }
3迹淌、pingpong機制
當服務(wù)端發(fā)出一個Ping河绽,客戶端沒有在約定的時間內(nèi)返回響應(yīng)的ack,則認為客戶端已經(jīng)不在線唉窃,這時我們Server端會主動斷開Scoket連接耙饰,并且改由APNS推送的方式發(fā)送消息。
//pingPong
- (void)ping{
? ? if (self.socket.readyState == SR_OPEN) {
? ? ? ? ? ? ? ? [self.socket sendPing:nil];
? ? ? ? }
}
//sendPing的時候纹份,如果網(wǎng)絡(luò)通的話苟跪,則會收到回調(diào),但是必須保證ScoketOpen蔓涧,否則會crash
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload {
? ? ? ? NSLog(@"收到pong回調(diào)");
}
至此,用SocketRocket建立一個聊天室所需的基本方法大概都列了一下削咆。當然,中間仍有許多代碼邏輯需要處理蠢笋。這些內(nèi)容會在仔細研讀后繼續(xù)整理出來。
寫在最后
這篇文章只是我自己的學習筆記鳞陨。第一次寫簡書昨寞,有點語無倫次瞻惋,不好意思。文中的代碼內(nèi)容大多都是參考公司項目及網(wǎng)上的一些簡書作者的文章援岩。非常感謝這些博主及公司同事的無私分享歼狼。最后附上這些博主的原文。
iOS--SocketRocket框架的使用及測試服務(wù)器的搭建