美文网首页IOSiOS学习笔记程序员
WebSocket:SocketRocket封装

WebSocket:SocketRocket封装

作者: 马戏团小丑 | 来源:发表于2018-05-30 12:14 被阅读35次

    WebSocket

    • 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是应用层协议。

    WebSocket的框架SocketRocket

    SocketRocket

    • 工具类封装
    #import <Foundation/Foundation.h>
    #import <SocketRocket.h>
    
    extern NSString * const kWebSocketDidOpenNote;
    extern NSString * const kWebSocketDidCloseNote;
    extern NSString * const kWebSocketdidReceiveMessageNote;
    
    @interface SocketRocketUtility : NSObject
    
    // 获取连接状态
    @property (nonatomic,assign,readonly) SRReadyState socketReadyState;
    
    + (SocketRocketUtility *)instance;
    
    -(void)SRWebSocketOpenWithURLString:(NSString *)urlString;//开启连接
    -(void)SRWebSocketClose;//关闭连接
    - (void)sendData:(id)data;//发送数据
    
    @end
    
    #import "SocketRocketUtility.h"
    
    #define dispatch_main_async_safe(block)\
    if ([NSThread isMainThread]) {\
    block();\
    } else {\
    dispatch_async(dispatch_get_main_queue(), block);\
    }
    
    NSString * const kWebSocketDidOpenNote           = @"kWebSocketDidOpenNote";
    NSString * const kWebSocketDidCloseNote          = @"kWebSocketDidCloseNote";
    NSString * const kWebSocketdidReceiveMessageNote = @"kWebSocketdidReceiveMessageNote";
    
    @interface SocketRocketUtility()<SRWebSocketDelegate>
    
    @property (nonatomic,strong) SRWebSocket *socket;
    
    @property (nonatomic,strong) NSTimer *heartBeat;
    
    @property (nonatomic,assign) NSTimeInterval reConnectTime;
    
    @property (nonatomic,copy) NSString *urlString;
    
    @end
    
    @implementation SocketRocketUtility
    
    +(SocketRocketUtility *)instance{
        static SocketRocketUtility *Instance = nil;
        static dispatch_once_t predicate;
        dispatch_once(&predicate, ^{
            Instance = [[SocketRocketUtility alloc] init];
        });
        return Instance;
    }
    -(void)dealloc{
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    #pragma mark - 方法
    -(void)SRWebSocketOpenWithURLString:(NSString *)urlString {
        //如果是同一个url return
        if (self.socket) {
            return;
        }
        if (!urlString) {
            return;
        }
        self.urlString = urlString;
        self.socket = [[SRWebSocket alloc] initWithURLRequest:
                       [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
        
    //    NSLog(@"请求的websocket地址:%@",self.socket.url.absoluteString);
    
        self.socket.delegate = self;
        [self.socket open];     //开始连接
    }
    
    -(void)SRWebSocketClose{
        if (self.socket){
            [self.socket close];
            self.socket = nil;
            //断开连接时销毁心跳
            [self destoryHeartBeat];
        }
    }
    
    #define WeakSelf(ws) __weak __typeof(&*self)weakSelf = self
    - (void)sendData:(id)data {
    //    NSLog(@"socketSendData --------------- %@",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 方法重连
    //                NSLog(@"重连");
                    [self reConnect];
                }
            } else {
    //            NSLog(@"没网络,发送失败,socket=nil");
            }
        });
    }
    
    //重连机制
    - (void)reConnect
    {
        [self SRWebSocketClose];
        //超过一分钟就不再重连 所以只会重连5次 2^5 = 64
    //    if (self.reConnectTime > 64) {
    //        //您的网络状况不是很好,请检查网络后重试
    //        return;
    //    }
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.socket = nil;
            [self SRWebSocketOpenWithURLString:self.urlString];
    //        NSLog(@"重连");
        });
        
        //重连时间2的指数级增长
        if (self.reConnectTime == 0) {
            self.reConnectTime = 2;
        }else{
            self.reConnectTime *= 2;
        }
    }
    
    //取消心跳
    - (void)destoryHeartBeat{
        dispatch_main_async_safe(^{
            if (self.heartBeat) {
                if ([self.heartBeat respondsToSelector:@selector(isValid)]){
                    if ([self.heartBeat isValid]){
                        [self.heartBeat invalidate];
                        self.heartBeat = nil;
                    }
                }
            }
        })
    }
    
    //初始化心跳
    - (void)initHeartBeat{
        dispatch_main_async_safe(^{
            [self destoryHeartBeat];
            self.heartBeat = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(sentheart) userInfo:nil repeats:YES];
            [[NSRunLoop currentRunLoop] addTimer:self.heartBeat forMode:NSRunLoopCommonModes];
        })
    }
    
    -(void)sentheart{
        //发送心跳
        [self sendData:[@"1" dataUsingEncoding:NSUTF8StringEncoding]];
    }
    
    #pragma mark - delegate
    - (void)webSocketDidOpen:(SRWebSocket *)webSocket {
        //每次正常连接的时候清零重连时间
        self.reConnectTime = 0;
        //开启心跳
        [self initHeartBeat];
        if (webSocket == self.socket) {
            NSLog(@"socket连接成功");
            [[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketDidOpenNote object:nil];
        }
    }
    
    - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
        if (webSocket == self.socket) {
    //        NSLog(@"socket连接失败");
            _socket = nil;
            [self reConnect];
        }
    }
    
    - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
        if (webSocket == self.socket) {
            NSLog(@"socket连接断开 被关闭连接,code:%ld,reason:%@,wasClean:%d",(long)code,reason,wasClean);
           // [self SRWebSocketClose];
            //[[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketDidCloseNote object:nil];
             [self reConnect];
        }
    }
    
    -(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload{
        NSString *reply = [[NSString alloc] initWithData:pongPayload encoding:NSUTF8StringEncoding];
        NSLog(@"Pong = %@",reply);
    }
    
    - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message  {
        if (webSocket == self.socket) {
            NSLog(@"socket收到数据message:%@",message);
            [[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketdidReceiveMessageNote object:message];
        }
    }
    
    #pragma mark - setter getter
    - (SRReadyState)socketReadyState{
        return self.socket.readyState;
    }
    @end
    
    封装的步骤逻辑
    • 1.开启连接
      -(void)SRWebSocketOpenWithURLString:(NSString *)urlString
    • 2.开启心跳

    像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。所以心跳就是一个定时器heartBeat,每3秒发送一次。
    心跳的每一次发送其实就是一个ping消息,其服务器返回的是pong消息,-(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload,接收不到pong那就是心跳断开了。

    • 3.重连机制

    当自己网络断开时候,需要重连;
    当后台主动断开,正常断开,会调用- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean,处理可以是关闭连接SRWebSocketClose,也可以是重连,一般是后者;
    当后台是网络不好,或者崩溃,调用- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error,会重新连接;

    相关文章

      网友评论

        本文标题:WebSocket:SocketRocket封装

        本文链接:https://www.haomeiwen.com/subject/jiutsftx.html