iOS websocket接入的簡單使用


 

 

接觸WebSocket

考慮到普通的HTTP 通信方式只能由客戶端主動拉取,服務器不能主動推給客戶端 。然后就想出的2種解決方案。

1.和后台溝通了一下 他們那里使用的是WebSocket ,所以就使用WebSocket讓我們app端和服務器建立長連接。這樣就可以事實接受他發過來的消息
2.使用推送,也可以實現接收后台發過來的一些消息

最后還是選擇了WebSocket,找到了facebook的 SocketRocket 框架。下面是接入過程中的一些記錄

WebSocket

WebSocket 是 HTML5 一種新的協議。它實現了瀏覽器與服務器全雙工通信,能更好的節省服務器資源和帶寬並達到實時通訊,它建立在 TCP 之上,同 HTTP 一樣通過 TCP 來傳輸數據,但是它和 HTTP 最大不同是:

WebSocket 是一種雙向通信協議,在建立連接后,WebSocket 服務器和 Browser/Client Agent 都能主動的向對方發送或接收數據,就像 Socket 一樣;

WebSocket 需要類似 TCP 的客戶端和服務器端通過握手連接,連接成功后才能相互通信。

具體在這兒  WebSocket 是什么原理?為什么可以實現持久連接?

用法

我使用的是pod管理庫 所以在podfile中加入
pod 'SocketRocket'

在使用命令行工具cd到當前工程 安裝
pod install

如果是copy的工程中的 SocketRocket庫的github地址:SocketRocket

導入庫到工程中以后首先封裝一個SocketRocketUtility單例

SocketRocketUtility.m文件中的寫法如下:

#import "SocketRocketUtility.h" #import <SocketRocket.h> NSString * const kNeedPayOrderNote = @"kNeedPayOrderNote";//發送的通知名稱 @interface SocketRocketUtility()<SRWebSocketDelegate> { int _index; NSTimer * heartBeat; NSTimeInterval reConnectTime; } @property (nonatomic,strong) SRWebSocket *socket; @end @implementation SocketRocketUtility + (SocketRocketUtility *)instance { static SocketRocketUtility *Instance = nil; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ Instance = [[SocketRocketUtility alloc] init]; }); return Instance; } //開啟連接 -(void)SRWebSocketOpenWithURLString:(NSString *)urlString { if (self.socket) { return; } if (!urlString) { return; } //SRWebSocketUrlString 就是websocket的地址 寫入自己后台的地址 self.socket = [[SRWebSocket alloc] initWithURLRequest: [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]]; self.socket.delegate = self; //SRWebSocketDelegate 協議 [self.socket open]; //開始連接 } //關閉連接 - (void)SRWebSocketClose { if (self.socket){ [self.socket close]; self.socket = nil; //斷開連接時銷毀心跳 [self destoryHeartBeat]; } } #pragma mark - socket delegate - (void)webSocketDidOpen:(SRWebSocket *)webSocket { NSLog(@"連接成功,可以與服務器交流了,同時需要開啟心跳"); //每次正常連接的時候清零重連時間 reConnectTime = 0; //開啟心跳 心跳是發送pong的消息 我這里根據后台的要求發送data給后台 [self initHeartBeat]; [[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketDidOpenNote object:nil]; } - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { NSLog(@"連接失敗,這里可以實現掉線自動重連,要注意以下幾點"); NSLog(@"1.判斷當前網絡環境,如果斷網了就不要連了,等待網絡到來,在發起重連"); NSLog(@"2.判斷調用層是否需要連接,例如用戶都沒在聊天界面,連接上去浪費流量"); NSLog(@"3.連接次數限制,如果連接失敗了,重試10次左右就可以了,不然就死循環了。)"; _socket = nil; //連接失敗就重連 [self reConnect]; } - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { NSLog(@"被關閉連接,code:%ld,reason:%@,wasClean:%d",code,reason,wasClean); //斷開連接 同時銷毀心跳 [self SRWebSocketClose]; } /* 該函數是接收服務器發送的pong消息,其中最后一個是接受pong消息的, 在這里就要提一下心跳包,一般情況下建立長連接都會建立一個心跳包, 用於每隔一段時間通知一次服務端,客戶端還是在線,這個心跳包其實就是一個ping消息, 我的理解就是建立一個定時器,每隔十秒或者十五秒向服務端發送一個ping消息,這個消息可是是空的 */ -(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload{ NSString *reply = [[NSString alloc] initWithData:pongPayload encoding:NSUTF8StringEncoding]; NSLog(@"reply===%@",reply); } - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message { //收到服務器發過來的數據 這里的數據可以和后台約定一個格式 我約定的就是一個字符串 收到以后發送通知到外層 根據類型 實現不同的操作 NSLog(@"%@",message); [[NSNotificationCenter defaultCenter] postNotificationName:kNeedPayOrderNote object:message]; } #pragma mark - methods //重連機制 - (void)reConnect { [self SRWebSocketClose]; //超過一分鍾就不再重連 所以只會重連5次 2^5 = 64 if (reConnectTime > 64) { return; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self.socket = nil; [self SRWebSocketOpen]; NSLog(@"重連"); }); //重連時間2的指數級增長 if (reConnectTime == 0) { reConnectTime = 2; }else{ reConnectTime *= 2; } } //初始化心跳 - (void)initHeartBeat { dispatch_main_async_safe(^{ [self destoryHeartBeat]; __weak typeof(self) weakSelf = self; //心跳設置為3分鍾,NAT超時一般為5分鍾 heartBeat = [NSTimer scheduledTimerWithTimeInterval:3*60 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"heart"); //和服務端約定好發送什么作為心跳標識,盡可能的減小心跳包大小 [weakSelf sendData:@"heart"]; }]; [[NSRunLoop currentRunLoop]addTimer:heartBeat forMode:NSRunLoopCommonModes]; }) } //取消心跳 - (void)destoryHeartBeat { dispatch_main_async_safe(^{ if (heartBeat) { [heartBeat invalidate]; heartBeat = nil; } }) } //pingPong機制 - (void)ping{ [self.socket sendPing:nil]; } #define WeakSelf(ws) __weak __typeof(&*self)weakSelf = self - (void)sendData:(id)data { WeakSelf(ws); dispatch_queue_t queue = dispatch_queue_create("zy", NULL); dispatch_async(queue, ^{ if (weakSelf.socket != nil) { // 只有 SR_OPEN 開啟狀態才能調 send 方法,不然要崩 if (weakSelf.socket.readyState == SR_OPEN) { [weakSelf.socket send:data]; // 發送數據 } else if (weakSelf.socket.readyState == SR_CONNECTING) { NSLog(@"正在連接中,重連后其他方法會去自動同步數據"); // 每隔2秒檢測一次 socket.readyState 狀態,檢測 10 次左右 // 只要有一次狀態是 SR_OPEN 的就調用 [ws.socket send:data] 發送數據 // 如果 10 次都還是沒連上的,那這個發送請求就丟失了,這種情況是服務器的問題了,小概率的 [self reConnect]; } else if (weakSelf.socket.readyState == SR_CLOSING || weakSelf.socket.readyState == SR_CLOSED) { // websocket 斷開了,調用 reConnect 方法重連 [self reConnect]; } } else { NSLog(@"沒網絡,發送失敗,一旦斷網 socket 會被我設置 nil 的"); } }); } -(void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self]; } 

然后在需要開啟socket的地方調用
[[SocketRocketUtility instance] SRWebSocketOpenWithURLString:@"寫入自己后台的地址"];
在需要斷開連接的時候調用
[[SocketRocketUtility instance] SRWebSocketClose];

使用這個框架最后一個很重要的 需要注意的一點

這個框架給我們封裝的webscoket在調用它的sendPing senddata方法之前,一定要判斷當前scoket是否連接,如果不是連接狀態,程序則會crash。

結語

這里簡單的實現了連接和收發數據 后續看項目需求在加上后續的改進 希望能夠幫助第一次寫的iOSer 。 希望有更好的方法的童鞋可以有進一步的交流 : )

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)); 

上面發送心跳包的方法是iOS10才可以用的 其他版本會崩潰 要適配版本 要選擇 這個方法

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo; 

8月10日 更新demo地址

demo地址
可以下載下來看看哦 :)

demo中的后台地址未設置 所以很多同學直接運行就報錯了 設置一個自己后台的地址就ok了 :)

 
 
164人點贊
 
 
"小禮物走一走,來簡書關注我"
還沒有人贊賞,支持一下
CoderSJun 一個小coder 希望和大家交流編程中的問題和總結
總資產14 (約1.17元)共寫了6023字獲得178個贊共61個粉絲
 

被以下專題收入,發現更多相似內容


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM