iOS開發-SocketRocket使用篇

在使用SocketRocket之前先了解關於網絡層的幾個概念

關於Socket

我們都知道socket是套接字,描述ip地址和端口,它本身並不是協議,而是一個調用接口,爲了大家直接使用更底層的協議(TCP或UDP),是對TCP/IP 或 UDP/IP的封裝。socket處於網絡層中的第五層,是一個抽象層。

關於WebSocket

websocket是一個協議,是基於http協議的,是建立在TCP連接之上的,是應用層上的一個應用層協議,和socket不是一個概念。

WebSocket的特點

websocket可以傳輸文本和二進制。
websocket的協議頭是ws開頭的,並不是http。

WebSocket和HTTP協議

WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。
HTTP 協議是一種無狀態的、無連接的、單向的應使用層協議。它採使用了請求/響應模型。通信請求只能由用戶端發起,服務端對請求做出應答解決。這種通信模型有一個弊端:HTTP 協議無法實現服務器主動向用戶端發起消息。這種單向請求的特點,註定了假如服務器有連續的狀態變化,用戶端要獲知就非常麻煩。大多數 Web 應使用程序將通過頻繁的異步JavaScript和XML(AJAX)請求實現長輪詢。輪詢的效率低,非常白費資源(由於必需不停連接,或者者 HTTP 連接始終打開)。
WebSocket 連接允許用戶端和服務器之間進行全雙工通信,以便任一方都可以通過建立的連接將數據推送到另一端。WebSocket 只要要建立一次連接,即可以一直保持連接狀態。這相比於輪詢方式的不停建立連接顯然效率要大大提高。

WebSocket與Socket的關係

Socket其實並不是一個協議,而是爲了方便用TCP或者UDP而籠統出來的一層,是位於應使用層和傳輸控制層之間的一組接口。是應使用層與TCP/IP協議族通信的中間軟件籠統層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對使用戶來說,一組簡單的接口就是一律,讓Socket去組織數據,以符合指定的協議。當兩臺主機通信時,必需通過Socket連接,Socket則利使用TCP/IP協議建立TCP連接。TCP連接則更依靠於底層的IP協議,IP協議的連接則依賴於鏈路層等更低層次。
WebSocket則是一個典型的應使用層協議。
區別是Socket是傳輸控制層協議,WebSocket是應使用層協議。

框架

在iOS 平臺上,我們知道socket的開源框架有 CocoaAsyncSocket, 而websocket的框架有Facebook的 SocketRocket, 以及socket.io-client-swift。

⤵️下面介紹我們今天的主角

SocketRocket

SocketRocket是一個WebSocket客戶端(WebSocket是適用於Web應用的下一代全雙工通訊協議,被成爲“Web的TCP”,它實現了瀏覽器與服務器的雙向通信),採用Object-C編寫。SocketRocket遵循最新的WebSocket規範RFC 6455

特性:

支持TLS (wss)。
使用NSStream/CFNetworking。
使用ARC。
採用並行架構。大部分的工作由後端的工作隊列(worker queues)完成。
基於委託編程。

1. 集成

  • 使用cocoapods
    只需要在podfile文件中加入pod 'SocketRocket',然後執行pod install就可以了
  • 不使用cocoapods
    1. 添加文件
      把下面的三個文件拖入項目中

    2. 添加依賴庫
      在Build Phases -> Link Binary With Libraries里加入如下frameworks:

      • libicucore.dylib
      • CFNetwork.framework
      • Security.framework
      • Foundation.framework

2. 使用

2.1 添加引用
#import "SocketRocket.h"
2.2 寫代理方法
@interface ViewController ()<SRWebSocketDelegate>
2.3 寫成屬性
@property (strong, nonatomic) SRWebSocket *socket;
2.4 初始化

這裏的server_ip爲宏定義static NSString *const server_ip = @"ws://"; 存放後臺提供的ws地址, 調用open方法即開啓長連接

//初始化 WebSocket
- (void)initWebSocket{
    if (_socket) {
        return;
    }
    //Url
    NSURL *url = [NSURL URLWithString:server_ip];
    //請求
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
    //初始化請求`
    _socket = [[SRWebSocket alloc] initWithURLRequest:request];
    //代理協議`
    _socket.delegate = self;
    // 實現這個 SRWebSocketDelegate 協議啊`
    //直接連接`
    [_socket open];    // open 就是直接連接了
}
2.5 代理方法的實現

這裏需要注意
①如果沒有連接成功就先調用send方法會崩潰進入斷言, 一定要等webSocketDidOpen回調完成在發送文本幀/數據包
②和後臺協商好發包的格式, 如果沒有統一會被關閉連接, 一般爲JSON格式的二進制流, 音頻是PCM數據流

#pragma mark -- SRWebSocketDelegate
//收到服務器消息是回調
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{
    NSLog(@"收到服務器返回消息:%@",message);
}

//連接成功
- (void)webSocketDidOpen:(SRWebSocket *)webSocket{
    NSLog(@"連接成功,可以立刻登錄你公司後臺的服務器了,還有開啓心跳");
    [self initHeart];   //開啓心跳
    
    if (self.socket != nil) {
        // 只有 SR_OPEN 開啓狀態才能調 send 方法啊,不然要崩
        if (_socket.readyState == SR_OPEN) {
            NSString *jsonString = @"{\"sid\": \"13b313a3-fea9-4e28-9e56-352458f7007f\"}";
            [_socket send:jsonString];  //發送數據包

        } else if (_socket.readyState == SR_CONNECTING) {
            NSLog(@"正在連接中,重連後其他方法會去自動同步數據");
            // 每隔2秒檢測一次 socket.readyState 狀態,檢測 10 次左右
            // 只要有一次狀態是 SR_OPEN 的就調用 [ws.socket send:data] 發送數據
            // 如果 10 次都還是沒連上的,那這個發送請求就丟失了,這種情況是服務器的問題了,小概率的
            // 代碼有點長,我就寫個邏輯在這裏好了
            
        } else if (_socket.readyState == SR_CLOSING || _socket.readyState == SR_CLOSED) {
            // websocket 斷開了,調用 reConnect 方法重連
        }
    } else {
        NSLog(@"沒網絡,發送失敗,一旦斷網 socket 會被我設置 nil 的");
        NSLog(@"其實最好是發送前判斷一下網絡狀態比較好,我寫的有點晦澀,socket==nil來表示斷網");
    }
}

//連接失敗的回調
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{
    NSLog(@"連接失敗,這裏可以實現掉線自動重連,要注意以下幾點");
    NSLog(@"1.判斷當前網絡環境,如果斷網了就不要連了,等待網絡到來,在發起重連");
    NSLog(@"2.判斷調用層是否需要連接,例如用戶都沒在聊天界面,連接上去浪費流量");
    //關閉心跳包
    [webSocket close];
    
    [self reConnect];
}

//連接斷開的回調
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
{
    NSLog(@"Close");
}
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload;
{
    NSLog(@"Pong");
}
2.6 心跳包(定時器實現)
//保活機制  探測包
- (void)initHeart{
    __weak typeof(self) weakSelf = self;
    _heatBeat = [NSTimer scheduledTimerWithTimeInterval:3*60 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf.socket send:@"heart"];
        NSLog(@"已發送");
    }];
    [[NSRunLoop currentRunLoop] addTimer:_heatBeat forMode:NSRunLoopCommonModes];
}

如果開啓了心跳記得在合適的地方銷燬定時器, 避免內存泄漏

//斷開連接時銷燬心跳
- (void)destoryHeart{
   
}
2.7 重連機制
- (void)reConnect{
    //每隔一段時間重連一次
    //規定64不在重連,2的指數級
    if (_reConnectTime > 60) {
        return;
    }
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        self.socket = nil;
        [self initWebSocket];
    });
    
    if (_reConnectTime == 0) {
        _reConnectTime = 2;
    }else{
        _reConnectTime *= 2;
    }
}

參考文獻:
SocketRocket的簡單使用
socketRocket 封裝,添加重連機制,block回調
SocketRocket源碼分析

Demo下載地址:
https://github.com/gaoyuGood/SocketRocket

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章