文字游戲就不說了..那些定義什么TCP.UDP,三次握手啊,,也就是面試需要問.這里直接介紹使用方法
這里使用了facebook的SocketRocket框架進行二次封裝直接替換ipurl和心跳包就可以使用了
#import <Foundation/Foundation.h>
#import "SRWebSocket.h"
NS_ASSUME_NONNULL_BEGIN
@interface WebSocketManager : NSObject
@property (nonatomic, strong) SRWebSocket *webSocket;
+ (instancetype)sharedSocketManager;//單例
- (void)connectServer;//建立長連接
- (void)SRWebSocketClose;//關閉長連接
- (void)sendDataToServer:(NSDictionary*)data;//發(fā)送數(shù)據(jù)給服務器
//主線程異步隊列
#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
#define WS(f) __weak __typeof(self) f = self
#define ipurl @"ws://112.74.63.194:8080/websocket"
#define HEART @"heart"
#import "WebSocketManager.h"
@interface WebSocketManager()<SRWebSocketDelegate>
@property (nonatomic, strong) NSTimer *heartBeatTimer; //心跳定時器
@property (nonatomic, strong) NSTimer *netWorkTestingTimer; //沒有網(wǎng)絡的時候檢測網(wǎng)絡定時器
@property (nonatomic, strong) dispatch_queue_t queue; //數(shù)據(jù)請求隊列(串行隊列)
@property (nonatomic, assign) NSTimeInterval reConnectTime; //重連時間
@property (nonatomic, strong) NSMutableArray *sendDataArray; //存儲要發(fā)送給服務端的數(shù)據(jù)
@property (nonatomic, assign) BOOL isActivelyClose; //用于判斷是否主動關閉長連接,如果是主動斷開連接,連接失敗的代理中,就不用執(zhí)行 重新連接方法
@end
@implementation WebSocketManager
//單例
+ (instancetype)sharedSocketManager
{
static WebSocketManager *_instace = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
_instace = [[self alloc] init];
});
return _instace;
}
- (instancetype)init
{
self = [super init];
if(self)
{
self.reConnectTime = 0;
self.isActivelyClose = NO;
self.queue = dispatch_queue_create("BF",NULL);
self.sendDataArray = [[NSMutableArray alloc] init];
}
return self;
}
#pragma mark - NSTimer
//初始化心跳
- (void)initHeartBeat
{
//心跳沒有被關閉
if(self.heartBeatTimer)
{
return;
}
[self destoryHeartBeat];
WS(weakSelf);
dispatch_main_async_safe(^{
weakSelf.heartBeatTimer = [NSTimer timerWithTimeInterval:10 target:weakSelf selector:@selector(senderheartBeat) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop]addTimer:weakSelf.heartBeatTimer forMode:NSRunLoopCommonModes];
});
}
//取消心跳
- (void)destoryHeartBeat
{
WS(weakSelf);
dispatch_main_async_safe(^{
if(weakSelf.heartBeatTimer)
{
[weakSelf.heartBeatTimer invalidate];
weakSelf.heartBeatTimer = nil;
}
});
}
//沒有網(wǎng)絡的時候開始定時 -- 用于網(wǎng)絡檢測
- (void)noNetWorkStartTestingTimer
{
WS(weakSelf);
dispatch_main_async_safe(^{
weakSelf.netWorkTestingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(noNetWorkStartTesting) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:weakSelf.netWorkTestingTimer forMode:NSDefaultRunLoopMode];
});
}
//取消網(wǎng)絡檢測
- (void)destoryNetWorkStartTesting
{
WS(weakSelf);
dispatch_main_async_safe(^{
if(weakSelf.netWorkTestingTimer)
{
[weakSelf.netWorkTestingTimer invalidate];
weakSelf.netWorkTestingTimer = nil;
}
});
}
#pragma mark - private -- webSocket相關方法
//發(fā)送心跳
- (void)senderheartBeat
{
//和服務端約定好發(fā)送什么作為心跳標識,盡可能的減小心跳包大小
WS(weakSelf);
dispatch_main_async_safe(^{
if(weakSelf.webSocket.readyState == SR_OPEN)
{
[weakSelf.webSocket sendPing:[HEART dataUsingEncoding:NSUTF8StringEncoding]];
}
});
}
//定時檢測網(wǎng)絡
- (void)noNetWorkStartTesting
{
//有網(wǎng)絡
if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable)
{
//關閉網(wǎng)絡檢測定時器
[self destoryNetWorkStartTesting];
//開始重連
[self reConnectServer];
}
}
//建立長連接
- (void)connectServer
{
self.isActivelyClose = NO;
if(self.webSocket)
{
self.webSocket = nil;
}
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:ipurl]];
self.webSocket = [[SRWebSocket alloc] initWithURLRequest:request];
self.webSocket.delegate = self;
[self.webSocket open];
}
//重新連接服務器
- (void)reConnectServer
{
if(self.webSocket.readyState == SR_OPEN)
{
return;
}
if(self.reConnectTime > 1024) //重連10次 2^10 = 1024
{
self.reConnectTime = 0;
return;
}
WS(weakSelf);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if(weakSelf.webSocket.readyState == SR_OPEN && weakSelf.webSocket.readyState == SR_CONNECTING)
{
return;
}
[weakSelf connectServer];
DLog(@"正在重連......");
if(weakSelf.reConnectTime == 0) //重連時間2的指數(shù)級增長
{
weakSelf.reConnectTime = 2;
}
else
{
weakSelf.reConnectTime *= 2;
}
});
}
//關閉連接
- (void)SRWebSocketClose;
{
self.isActivelyClose = YES;
[self webSocketClose];
//關閉心跳定時器
[self destoryHeartBeat];
//關閉網(wǎng)絡檢測定時器
[self destoryNetWorkStartTesting];
}
//關閉連接
- (void)webSocketClose
{
if(self.webSocket)
{
[self.webSocket close];
self.webSocket = nil;
}
}
//發(fā)送數(shù)據(jù)給服務器
- (void)sendDataToServer:(NSDictionary*)data
{
NSData *data= [NSJSONSerialization dataWithJSONObject: data options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[self.sendDataArray addObject: jsonString];
[self sendeDataToServer];
}
- (void)sendeDataToServer
{
WS(weakSelf);
//把數(shù)據(jù)放到一個請求隊列中
dispatch_async(self.queue, ^{
//沒有網(wǎng)絡
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable)
{
//開啟網(wǎng)絡檢測定時器
[weakSelf noNetWorkStartTestingTimer];
}
else //有網(wǎng)絡
{
if(weakSelf.webSocket != nil)
{
// 只有長連接OPEN開啟狀態(tài)才能調(diào) send 方法,不然會Crash
if(weakSelf.webSocket.readyState == SR_OPEN)
{
if (weakSelf.sendDataArray.count > 0)
{
NSString *data = weakSelf.sendDataArray[0];
[weakSelf.webSocket send:data]; //發(fā)送數(shù)據(jù)
[weakSelf.sendDataArray removeObjectAtIndex:0];
if([weakSelf.sendDataArray count] > 0)
{
[weakSelf sendeDataToServer];
}
}
}
else if (weakSelf.webSocket.readyState == SR_CONNECTING) //正在連接
{
DLog(@"正在連接中,重連后會去自動同步數(shù)據(jù)");
}
else if (weakSelf.webSocket.readyState == SR_CLOSING || weakSelf.webSocket.readyState == SR_CLOSED) //斷開連接
{
//調(diào)用 reConnectServer 方法重連,連接成功后 繼續(xù)發(fā)送數(shù)據(jù)
[weakSelf reConnectServer];
}
}
else
{
[weakSelf connectServer]; //連接服務器
}
}
});
}
#pragma mark - SRWebSocketDelegate -- webSockect代理
//連接成功回調(diào)
- (void)webSocketDidOpen:(SRWebSocket *)webSocket
{
DLog(@"webSocket === 連接成功");
[self initHeartBeat]; //開啟心跳
//如果有尚未發(fā)送的數(shù)據(jù),繼續(xù)向服務端發(fā)送數(shù)據(jù)
if ([self.sendDataArray count] > 0){
[self sendeDataToServer];
}
}
//連接失敗回調(diào)
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error
{
//用戶主動斷開連接,就不去進行重連
if(self.isActivelyClose)
{
return;
}
[self destoryHeartBeat]; //斷開連接時銷毀心跳
DLog(@"連接失敗,這里可以實現(xiàn)掉線自動重連,要注意以下幾點");
DLog(@"1.判斷當前網(wǎng)絡環(huán)境,如果斷網(wǎng)了就不要連了,等待網(wǎng)絡到來,在發(fā)起重連");
DLog(@"3.連接次數(shù)限制,如果連接失敗了,重試10次左右就可以了");
//判斷網(wǎng)絡環(huán)境
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) //沒有網(wǎng)絡
{
[self noNetWorkStartTestingTimer];//開啟網(wǎng)絡檢測定時器
}
else //有網(wǎng)絡
{
[self reConnectServer];//連接失敗就重連
}
}
//連接關閉,注意連接關閉不是連接斷開,關閉是 [socket close] 客戶端主動關閉,斷開可能是斷網(wǎng)了,被動斷開的。
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean
{
// 在這里判斷 webSocket 的狀態(tài) 是否為 open , 大家估計會有些奇怪 ,因為我們的服務器都在海外,會有些時間差,經(jīng)過測試,我們在進行某次連接的時候,上次重連的回調(diào)剛好回來,而本次重連又成功了,就會誤以為,本次沒有重連成功,而再次進行重連,就會出現(xiàn)問題,所以在這里做了一下判斷
if(self.webSocket.readyState == SR_OPEN || self.isActivelyClose)
{
return;
}
DLog(@"被關閉連接,code:%ld,reason:%@,wasClean:%d",code,reason,wasClean);
[self destoryHeartBeat]; //斷開連接時銷毀心跳
//判斷網(wǎng)絡環(huán)境
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) //沒有網(wǎng)絡
{
[self noNetWorkStartTestingTimer];//開啟網(wǎng)絡檢測
}
else //有網(wǎng)絡
{
[self reConnectServer];//連接失敗就重連
}
}
//該函數(shù)是接收服務器發(fā)送的pong消息,其中最后一個參數(shù)是接受pong消息的
-(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData*)pongPayload
{
NSString* reply = [[NSString alloc] initWithData:pongPayload encoding:NSUTF8StringEncoding];
DLog(@"reply === 收到后臺心跳回復 Data:%@",reply);
}
//收到服務器發(fā)來的數(shù)據(jù)
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message
{
DLog(@"%@",message);
// NSMutableDictionary *dataDic = [NSMutableDictionary dictionaryWithJsonString:message];
// NSMutableDictionary *dataDic = [NSMutableDictionary diction]
/*根據(jù)具體的業(yè)務做具體的處理*/
}
@end