http://www.cocoachina.com/ios/20190425/26875.html
轉(zhuǎn)載自:
接觸WebSocket
最近公司的項(xiàng)目中有一個(gè)功能 需要服務(wù)器主動推數(shù)據(jù)到APP欲侮。
考慮到普通的HTTP 通信方式只能由客戶端主動拉取灵寺,服務(wù)器不能主動推給客戶端 。然后就想出的2種解決方案字支。
1.和后臺溝通了一下 他們那里使用的是WebSocket 浩嫌,所以就使用WebSocket讓我們app端和服務(wù)器建立長連接钠糊。這樣就可以事實(shí)接受他發(fā)過來的消息
2.使用推送垫言,也可以實(shí)現(xiàn)接收后臺發(fā)過來的一些消息
最后還是選擇了WebSocket杉女,找到了facebook的 SocketRocket 框架瞻讽。下面是接入過程中的一些記錄
WebSocket
WebSocket 是 HTML5 一種新的協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信熏挎,能更好的節(jié)省服務(wù)器資源和帶寬并達(dá)到實(shí)時(shí)通訊速勇,它建立在 TCP 之上,同 HTTP 一樣通過 TCP 來傳輸數(shù)據(jù)坎拐,但是它和 HTTP 最大不同是:
WebSocket 是一種雙向通信協(xié)議烦磁,在建立連接后,WebSocket 服務(wù)器和 Browser/Client Agent 都能主動的向?qū)Ψ桨l(fā)送或接收數(shù)據(jù)哼勇,就像 Socket 一樣都伪;
WebSocket 需要類似 TCP 的客戶端和服務(wù)器端通過握手連接,連接成功后才能相互通信积担。
具體在這兒WebSocket 是什么原理陨晶?為什么可以實(shí)現(xiàn)持久連接?
用法
我使用的是pod管理庫 所以在podfile中加入
pod 'SocketRocket'
在使用命令行工具cd到當(dāng)前工程 安裝
pod install
如果是copy的工程中的 SocketRocket庫的github地址:SocketRocket
導(dǎo)入庫到工程中以后首先封裝一個(gè)SocketRocketUtility單例
SocketRocketUtility.m文件中的寫法如下:
#import"SocketRocketUtility.h"#import<SocketRocket.h>NSString*constkNeedPayOrderNote =@"kNeedPayOrderNote";//發(fā)送的通知名稱@interfaceSocketRocketUtility(){int_index;NSTimer* heartBeat;NSTimeIntervalreConnectTime;}@property(nonatomic,strong) SRWebSocket *socket;@end@implementationSocketRocketUtility+ (SocketRocketUtility *)instance {staticSocketRocketUtility *Instance =nil;staticdispatch_once_tpredicate;dispatch_once(&predicate, ^{? ? ? ? Instance = [[SocketRocketUtility alloc] init];? ? });returnInstance;}//開啟連接-(void)SRWebSocketOpenWithURLString:(NSString*)urlString {if(self.socket) {return;? ? }if(!urlString) {return;? ? }//SRWebSocketUrlString 就是websocket的地址 寫入自己后臺的地址self.socket = [[SRWebSocket alloc] initWithURLRequest:? ? ? ? ? ? ? ? ? [NSURLRequestrequestWithURL:[NSURLURLWithString:urlString]]];self.socket.delegate =self;//SRWebSocketDelegate 協(xié)議[self.socket open];//開始連接}//關(guān)閉連接- (void)SRWebSocketClose {if(self.socket){? ? ? ? [self.socket close];self.socket =nil;//斷開連接時(shí)銷毀心跳[selfdestoryHeartBeat];? ? }}#pragma mark - socket delegate- (void)webSocketDidOpen:(SRWebSocket *)webSocket {NSLog(@"連接成功帝璧,可以與服務(wù)器交流了,同時(shí)需要開啟心跳");//每次正常連接的時(shí)候清零重連時(shí)間reConnectTime =0;//開啟心跳 心跳是發(fā)送pong的消息 我這里根據(jù)后臺的要求發(fā)送data給后臺[selfinitHeartBeat];? ? [[NSNotificationCenterdefaultCenter] postNotificationName:kWebSocketDidOpenNote object:nil];}- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError*)error {NSLog(@"連接失敗珍逸,這里可以實(shí)現(xiàn)掉線自動重連,要注意以下幾點(diǎn)");NSLog(@"1.判斷當(dāng)前網(wǎng)絡(luò)環(huán)境聋溜,如果斷網(wǎng)了就不要連了谆膳,等待網(wǎng)絡(luò)到來,在發(fā)起重連");NSLog(@"2.判斷調(diào)用層是否需要連接撮躁,例如用戶都沒在聊天界面漱病,連接上去浪費(fèi)流量");NSLog(@"3.連接次數(shù)限制,如果連接失敗了把曼,重試10次左右就可以了杨帽,不然就死循環(huán)了。)";? ? _socket =nil;//連接失敗就重連[selfreConnect];}- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString*)reason wasClean:(BOOL)wasClean {NSLog(@"被關(guān)閉連接嗤军,code:%ld,reason:%@,wasClean:%d",code,reason,wasClean);//斷開連接 同時(shí)銷毀心跳[selfSRWebSocketClose];}/*
該函數(shù)是接收服務(wù)器發(fā)送的pong消息注盈,其中最后一個(gè)是接受pong消息的,
在這里就要提一下心跳包叙赚,一般情況下建立長連接都會建立一個(gè)心跳包老客,
用于每隔一段時(shí)間通知一次服務(wù)端僚饭,客戶端還是在線,這個(gè)心跳包其實(shí)就是一個(gè)ping消息胧砰,
我的理解就是建立一個(gè)定時(shí)器鳍鸵,每隔十秒或者十五秒向服務(wù)端發(fā)送一個(gè)ping消息,這個(gè)消息可是是空的
*/-(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData*)pongPayload{NSString*reply = [[NSStringalloc] initWithData:pongPayload encoding:NSUTF8StringEncoding];NSLog(@"reply===%@",reply);}- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message? {//收到服務(wù)器發(fā)過來的數(shù)據(jù) 這里的數(shù)據(jù)可以和后臺約定一個(gè)格式 我約定的就是一個(gè)字符串 收到以后發(fā)送通知到外層 根據(jù)類型 實(shí)現(xiàn)不同的操作NSLog(@"%@",message);? ? ? ? [[NSNotificationCenterdefaultCenter] postNotificationName:kNeedPayOrderNote object:message];}#pragma mark - methods//重連機(jī)制- (void)reConnect{? ? [selfSRWebSocketClose];//超過一分鐘就不再重連 所以只會重連5次 2^5 = 64if(reConnectTime >64) {return;? ? }? ? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(reConnectTime *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{self.socket =nil;? ? ? ? [selfSRWebSocketOpen];NSLog(@"重連");? ? });//重連時(shí)間2的指數(shù)級增長if(reConnectTime ==0) {? ? ? ? reConnectTime =2;? ? }else{? ? ? ? reConnectTime *=2;? ? }}//初始化心跳- (void)initHeartBeat{? ? dispatch_main_async_safe(^{? ? ? ? [selfdestoryHeartBeat];? ? ? ? __weaktypeof(self) weakSelf =self;//心跳設(shè)置為3分鐘尉间,NAT超時(shí)一般為5分鐘heartBeat = [NSTimerscheduledTimerWithTimeInterval:3*60repeats:YESblock:^(NSTimer* _Nonnull timer) {NSLog(@"heart");//和服務(wù)端約定好發(fā)送什么作為心跳標(biāo)識偿乖,盡可能的減小心跳包大小[weakSelf sendData:@"heart"];? ? ? ? }];? ? ? ? [[NSRunLoopcurrentRunLoop]addTimer:heartBeat forMode:NSRunLoopCommonModes];? ? })}//取消心跳- (void)destoryHeartBeat{? ? dispatch_main_async_safe(^{if(heartBeat) {? ? ? ? ? ? [heartBeat invalidate];? ? ? ? ? ? heartBeat =nil;? ? ? ? }? ? })}//pingPong機(jī)制- (void)ping{? ? [self.socket sendPing:nil];}#define WeakSelf(ws) __weak __typeof(&*self)weakSelf = self- (void)sendData:(id)data {? ? WeakSelf(ws);dispatch_queue_tqueue =? dispatch_queue_create("zy",NULL);dispatch_async(queue, ^{if(weakSelf.socket !=nil) {// 只有 SR_OPEN 開啟狀態(tài)才能調(diào) send 方法,不然要崩if(weakSelf.socket.readyState == SR_OPEN) {? ? ? ? ? ? ? ? [weakSelf.socket send:data];// 發(fā)送數(shù)據(jù)}elseif(weakSelf.socket.readyState == SR_CONNECTING) {NSLog(@"正在連接中哲嘲,重連后其他方法會去自動同步數(shù)據(jù)");// 每隔2秒檢測一次 socket.readyState 狀態(tài)贪薪,檢測 10 次左右// 只要有一次狀態(tài)是 SR_OPEN 的就調(diào)用 [ws.socket send:data] 發(fā)送數(shù)據(jù)// 如果 10 次都還是沒連上的,那這個(gè)發(fā)送請求就丟失了眠副,這種情況是服務(wù)器的問題了画切,小概率的[selfreConnect];? ? ? ? ? ? ? ? ? ? ? ? ? ? }elseif(weakSelf.socket.readyState == SR_CLOSING || weakSelf.socket.readyState == SR_CLOSED) {// websocket 斷開了,調(diào)用 reConnect 方法重連[selfreConnect];? ? ? ? ? ? }? ? ? ? }else{NSLog(@"沒網(wǎng)絡(luò)侦啸,發(fā)送失敗槽唾,一旦斷網(wǎng) socket 會被我設(shè)置 nil 的");? ? ? ? }? ? });}-(void)dealloc{? ? [[NSNotificationCenterdefaultCenter] removeObserver:self];}
然后在需要開啟socket的地方調(diào)用
[[SocketRocketUtility instance] SRWebSocketOpenWithURLString:@"寫入自己后臺的地址"];
在需要斷開連接的時(shí)候調(diào)用
[[SocketRocketUtility instance] SRWebSocketClose];
使用這個(gè)框架最后一個(gè)很重要的 需要注意的一點(diǎn)
這個(gè)框架給我們封裝的webscoket在調(diào)用它的sendPing senddata方法之前丧枪,一定要判斷當(dāng)前scoket是否連接光涂,如果不是連接狀態(tài),程序則會crash拧烦。
結(jié)語
這里簡單的實(shí)現(xiàn)了連接和收發(fā)數(shù)據(jù) 后續(xù)看項(xiàng)目需求在加上后續(xù)的改進(jìn) 希望能夠幫助第一次寫的iOSer 忘闻。 希望有更好的方法的童鞋可以有進(jìn)一步的交流? : )
4月10日 更新:
/// Creates and returns a new NSTimer object initialized with the specified block object and schedules it on the current run loop in the default mode./// - parameter:? ti? ? The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead/// - parameter:? repeats? If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires./// - parameter:? block? The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references+ (NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(NSTimer*timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
上面發(fā)送心跳包的方法是iOS10才可以用的 其他版本會崩潰? 要適配版本 要選擇 這個(gè)方法
+ (NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullableid)userInfo repeats:(BOOL)yesOrNo;
8月10日 更新demo地址
可以下載下來看看哦 :)
注
demo中的后臺地址未設(shè)置? 所以很多同學(xué)直接運(yùn)行就報(bào)錯了 設(shè)置一個(gè)自己后臺的地址就ok了 :)
作者:CoderSJun
鏈接:http://www.reibang.com/p/821b777555d3
來源:簡書
簡書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處恋博。