1,websocket
Websocket是html5提出的一個協議規範。它實現了瀏覽器與伺服器全雙工系統通訊,能更好的節省伺服器資源和頻寬,websocket約定了一個通訊的規範,通過一個握手的機制,用戶端(瀏覽器)和伺服器(webserver)之間能建立一個類似tcp的串連,從而方便c-s之間的通訊。在websocket出現之前,web互動一般是基於http協議的短串連或者長串連。WebSocket是為解決用戶端與服務端即時通訊而產生的技術。websocket協議本質上是一個基於tcp的協議,是先通過HTTP/HTTPS協議發起一條特殊的http請求進行握手後建立一個用於交換資料的TCP串連,此後服務端與用戶端通過此TCP串連進行即時通訊,它與http最大的不同是:websocket是一種雙向通訊協定,在建立串連後,websocket伺服器和用戶端都negative主動向對方發送或接收資料,就像socket一樣。
2,websocket優點
以前web server實現推送技術或者即時通訊,用的都是輪詢(polling),在特點的時間間隔(比如1秒鐘)由瀏覽器自動發出請求,將伺服器的訊息主動的拉回來,在這種情況下,我們需要不斷的向伺服器發送請求,然而HTTP request 的header是非常長的,裡麵包含的資料可能只是一個很小的值,這樣會佔用很多的頻寬和伺服器資源。
WebSocket API最偉大之處在於伺服器和用戶端可以在給定的時間範圍內的任意時刻,相互推送資訊。 瀏覽器和伺服器只需要要做一個握手的動作,在建立串連之後,伺服器可以主動傳送資料給用戶端,用戶端也可以隨時向伺服器發送資料。 此外,伺服器與用戶端之間交換的標題資訊很小。
3,websocket用法
《1》我使用的是pod匯入
pod 'SocketRocket'
《2》 匯入庫到工程中以後首先封裝一個 HHWebSocketManager 單例
//.h#import <Foundation/Foundation.h>#import <SocketRocket.h>extern NSString * const kNeedPayOrderNote;extern NSString * const kWebSocketDidOpenNote;extern NSString * const kWebSocketDidCloseNote;extern NSString * const kWebSocketdidReceiveMessageNote;@interface HHWebSocketManager : NSObject // 擷取串連狀態@property (nonatomic,assign,readonly) SRReadyState socketReadyState;+ (HHWebSocketManager *)instance;- (void)SRWebSocketOpen;//開啟串連- (void)SRWebSocketClose;//關閉串連- (void)sendData:(id)data;//發送資料@end
//.m#import "HHWebSocketManager.h"NSString * const kNeedPayOrderNote = @"kNeedPayOrderNote";NSString * const kWebSocketDidOpenNote = @"kWebSocketdidReceiveMessageNote";NSString * const kWebSocketDidCloseNote = @"kWebSocketDidCloseNote";NSString * const kWebSocketdidReceiveMessageNote = @"kWebSocketdidReceiveMessageNote";@interface HHWebSocketManager()<SRWebSocketDelegate>{ int _index; NSTimer * heartBeat; NSTimeInterval reConnectTime; }@property (nonatomic,strong) SRWebSocket *socket;@end@implementation HHWebSocketManager+(HHWebSocketManager *)instance{ static HHWebSocketManager *Instance = nil; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ Instance = [[HHWebSocketManager alloc] init]; }); return Instance;}#pragma mark - **************** public methods-(void)SRWebSocketOpen{ //如果是同一個url return if (self.socket) { return; } self.socket = [[SRWebSocket alloc] initWithURLRequest: [NSURLRequest requestWithURL:[NSURL URLWithString:@"youurl"]]];//這裡填寫你伺服器的地址 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 的"); NSLog(@"其實最好是發送前判斷一下網路狀態比較好,我寫的有點晦澀,socket==nil來表示斷網"); } });}#pragma mark - **************** private mothodes //重連機制- (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)destoryHeartBeat{ dispatch_main_async_safe(^{ if (heartBeat) { if ([heartBeat respondsToSelector:@selector(isValid)]){ if ([heartBeat isValid]){ [heartBeat invalidate]; heartBeat = nil; } } } })} //初始化心跳- (void)initHeartBeat{ dispatch_main_async_safe(^{ [self destoryHeartBeat]; //心跳設定為3分鐘,NAT逾時一般為5分鐘 heartBeat = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(sentheart) userInfo:nil repeats:YES]; //和服務端約定好發送什麼作為心跳標識,儘可能的減小心跳包大小 [[NSRunLoop currentRunLoop] addTimer:heartBeat forMode:NSRunLoopCommonModes]; })}-(void)sentheart{ //發送心跳 和後台可以約定發送什麼內容 一般可以調用ping 我這雷根據背景要求 發送了data給他 [self sendData:@"heart"];} //pingPong- (void)ping{ if (self.socket.readyState == SR_OPEN) { [self.socket sendPing:nil]; }}#pragma mark - socket delegate- (void)webSocketDidOpen:(SRWebSocket *)webSocket { //每次正常串連的時候清零重連時間 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串連斷開************************** "); NSLog(@"被關閉串連,code:%ld,reason:%@,wasClean:%d",(long)code,reason,wasClean); [self SRWebSocketClose]; [[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketDidCloseNote object:nil]; }}/*該函數是接收伺服器發送的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 { if (webSocket == self.socket) { NSLog(@"************************** socket收到資料了************************** "); NSLog(@"我這後台約定的 message 是 json 格式資料收到資料,就按格式解析吧,然後把資料發給調用層"); NSLog(@"message:%@",message); [[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketdidReceiveMessageNote object:message]; }}#pragma mark - **************** setter getter- (SRReadyState)socketReadyState{ return self.socket.readyState;}-(void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self];}@end
《3》使用方法
//在需要開啟socket的地方調用[[SocketRocketUtility instance] SRWebSocketOpen];//在需要中斷連線的時候調用[[SocketRocketUtility instance] SRWebSocketClose];
《4》使用這個架構最後一個很重要,需要注意
這個架構給我們封裝的webscoket在調用它的sendPing senddata方法之前,一定要判斷當前scoket是否串連,如果不是串連狀態,程式則會crash。
《5》添加通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SRWebSocketDidOpen) name:@"kWebSocketDidOpenNote" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SRWebSocketDidReceiveMsg:) name:@"kWebSocketdidReceiveMessageNote" object:nil];
- (void)SRWebSocketDidOpen { NSLog(@"開啟成功"); //在成功後需要做的操作。。。 }- (void)SRWebSocketDidReceiveMsg:(NSNotification *)note { //收到服務端發送過來的訊息 NSString * message = note.object; NSLog(@"%@",message);}