WebSocket:SocketRocketパッケージ

7613 ワード

WebSocket
  • WebSocketは、HTML 5が提供し始めた単一のTCP接続上でフルデュプレクス通信を行うプロトコルである.
  • HTTPプロトコルは、無状態、無接続、一方向のアプリケーション層プロトコルである.リクエスト/レスポンスモデルを採用しています.通信要求はクライアントからのみ開始され,サービス側は要求に対して応答処理を行う.この通信モデルには、HTTPプロトコルでは、サーバがクライアントに積極的にメッセージを送信することができないという弊害がある.このような一方向要求の特徴は,サーバが連続的に状態が変化すると,クライアントが知るのは非常に面倒であることに決まっている.ほとんどのWebアプリケーションでは、頻繁な非同期JavaScriptとXML(AJAX)リクエストによって長いポーリングが実現されます.ポーリングの効率が低く、リソースが非常に浪費されます(接続を停止しないか、HTTP接続を常に開く必要があるため).
  • WebSocket接続により、クライアントとサーバとの間でフルデュプレクス通信が可能になり、どちらも確立された接続によってデータを他端にプッシュできます.WebSocketは1回の接続を確立するだけで、接続状態を維持することができます.これは、ポーリング方式による接続の継続的な確立に比べて、明らかに効率が大幅に向上します.

  • WebSocketとSocketの関係
    Socketは実際にはプロトコルではなく、TCPやUDPの使用を容易にするために抽象化された層であり、アプリケーション層と伝送制御層の間に位置するインタフェースのセットである.アプリケーション層とTCP/IPプロトコルファミリー通信の中間ソフトウェア抽象層であり、インタフェースのセットである.設計モードでは、Socketは複雑なTCP/IPプロトコルファミリーをSocketインタフェースの後ろに隠し、ユーザーにとって簡単なインタフェースのセットがすべてであり、Socketにデータを組織させ、指定されたプロトコルに合致させる.2台のホストが通信する場合はSocketで接続する必要があり、SocketはTCP/IPプロトコルを利用してTCP接続を確立する.TCP接続は下位層のIPプロトコルに依存し,IPプロトコルの接続はリンク層などより下位層に依存する.WebSocketは典型的なアプリケーション層プロトコルです.違いはSocketがトランスポート制御層プロトコル,WebSocketがアプリケーション層プロトコルである.
    WebSocketのフレームワークSocketRocket
    SocketRocket
  • ツールクラスパッケージ
  • #import 
    #import 
    
    extern NSString * const GQNotification_SocketRocketDidOpen;
    extern NSString * const GQNotification_SocketRocketDidClose;
    extern NSString * const GQNotification_SocketRocketDidReceive;
    
    @interface GQSocketRocketManager : NSObject
    + (instancetype)gq_shareInstance;
    
    /**        */
    @property (nonatomic, assign, readonly) SRReadyState socketReadyState;
    
    /**      */
    - (void)gq_openWithURLString:(NSString *)urlString;
    /**      */
    - (void)gq_close;
    /**      */
    - (void)gq_sendData:(id)data;
    
    @end
    
    #import "GQSocketRocketManager.h"
    #import "GQKit.h"
    NSString * const GQNotification_SocketRocketDidOpen = @"GQNotification_SocketRocketDidOpen";
    NSString * const GQNotification_SocketRocketDidClose = @"GQNotification_SocketRocketDidClose";
    NSString * const GQNotification_SocketRocketDidReceive = @"GQNotification_SocketRocketDidReceive";
    
    @interface GQSocketRocketManager()
    
    @property (nonatomic,strong) SRWebSocket *socket;
    
    @property (nonatomic,strong) NSTimer *heartBeat;
    
    @property (nonatomic,assign) NSTimeInterval reConnectTime;
    
    @property (nonatomic,copy) NSString *urlString;
    @end
    
    @implementation GQSocketRocketManager
    
    + (instancetype)gq_shareInstance {
        static GQSocketRocketManager *instance;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            instance = [[GQSocketRocketManager alloc] init];
        });
        return instance;
    }
    
    - (void)gq_openWithURLString:(NSString *)urlString {
        if (self.socket) {
            return;
        }
        if (!urlString) {
            return;
        }
        self.urlString = urlString;
        self.socket = [[SRWebSocket alloc] initWithURLRequest:
                       [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
        
        self.socket.delegate = self;
        [self.socket open];
    }
    
    - (void)gq_close {
        if (self.socket){
            [self.socket close];
            self.socket = nil;
            [self destoryHeartBeat];
        }
    }
    
    - (void)gq_sendData:(id)data {
        __weak __typeof(self) weakSelf = self;
        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) {
                    GQLog(@"     ");
                    [self reConnect];
                } else if (weakSelf.socket.readyState == SR_CLOSING || weakSelf.socket.readyState == SR_CLOSED) {
                    [self reConnect];
                }
            } else {
                
            }
        });
    }
    
    //    
    - (void)reConnect {
        [self gq_close];
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.socket = nil;
            [self gq_openWithURLString:self.urlString];
        });
        
        if (self.reConnectTime == 0) {
            self.reConnectTime = 2;
        }
    }
    
    //    
    - (void)destoryHeartBeat {
        GQ_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 {
        GQ_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 gq_sendData:[@"1" dataUsingEncoding:NSUTF8StringEncoding]];
    }
    
    #pragma mark - delegate
    - (void)webSocketDidOpen:(SRWebSocket *)webSocket {
        //               
        self.reConnectTime = 0;
        //    
        [self initHeartBeat];
        if (webSocket == self.socket) {
            GQLog(@"socket    ");
            [[NSNotificationCenter defaultCenter] postNotificationName:GQNotification_SocketRocketDidOpen object:nil];
        }
    }
    
    - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
        if (webSocket == self.socket) {
            GQLog(@"socket    ");
            _socket = nil;
            [self reConnect];
        }
    }
    
    - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
        if (webSocket == self.socket) {
            GQLog(@"socket          ,code:%ld,reason:%@,wasClean:%d",(long)code,reason,wasClean);
            [self reConnect];
        }
    }
    
    /*            pong  ,         pong   ,
                ,                   ,
                    ,       ,           ping  ,
     */
    - (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload {
    //    NSString *reply = [[NSString alloc] initWithData:pongPayload encoding:NSUTF8StringEncoding];
        
    }
    
    - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
        if (webSocket == self.socket) {
            GQLog(@"socket    message:%@",message);
            [[NSNotificationCenter defaultCenter] postNotificationName:GQNotification_SocketRocketDidReceive object:message];
        }
    }
    
    - (SRReadyState)socketReadyState {
        return self.socket.readyState;
    }
    
    @end
    

    カプセル化されたステップロジック
  • 1.オープン接続- (void)gq_openWithURLString:(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が呼び出され、再接続されます.