在做項(xiàng)目過(guò)程中有個(gè)需求,需要在請(qǐng)求接口后建立長(zhǎng)連接,并且一直接收傳過(guò)來(lái)的數(shù)據(jù),我是用的facebook的SocketRocket框架實(shí)現(xiàn)的,可以用cocoapods導(dǎo)入。
Demo下載地址:https://github.com/huangqizai/webSocket
不多說(shuō)上代碼:
以下代碼最好在單利里面實(shí)現(xiàn)...
#pragma mark 輸入接口地址,開(kāi)始連接
-(void)SRWebSocketOpenWithURLString:(NSString *)urlString {
//如果是同一個(gè)url return
if (self.socket) {
return;
}
if (!urlString) {
return;
}
self.urlString = urlString;
self.socket = [[SRWebSocket alloc] initWithURLRequest:
[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
NSLog(@"請(qǐng)求的websocket地址:%@",self.socket.url.absoluteString);
self.socket.delegate = self; //SRWebSocketDelegate 協(xié)議
[self.socket open]; //開(kāi)始連接
}
-(void)SRWebSocketClose{
if (self.socket){
[self.socket close];
self.socket = nil;
//斷開(kāi)連接時(shí)銷(xiāo)毀心跳
[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 開(kāi)啟狀態(tài)才能調(diào) send 方法啊,不然要崩
if (weakSelf.socket.readyState == SR_OPEN) {
[weakSelf.socket send:data]; // 發(fā)送數(shù)據(jù)
} else if (weakSelf.socket.readyState == SR_CONNECTING) {
NSLog(@"正在連接中,重連后其他方法會(huì)去自動(dòng)同步數(shù)據(jù)");
// 每隔2秒檢測(cè)一次 socket.readyState 狀態(tài),檢測(cè) 10 次左右
// 只要有一次狀態(tài)是 SR_OPEN 的就調(diào)用 [ws.socket send:data] 發(fā)送數(shù)據(jù)
// 如果 10 次都還是沒(méi)連上的,那這個(gè)發(fā)送請(qǐng)求就丟失了,這種情況是服務(wù)器的問(wèn)題了,小概率的
// 代碼有點(diǎn)長(zhǎng),我就寫(xiě)個(gè)邏輯在這里好了
[self reConnect];
} else if (weakSelf.socket.readyState == SR_CLOSING || weakSelf.socket.readyState == SR_CLOSED) {
// websocket 斷開(kāi)了,調(diào)用 reConnect 方法重連
NSLog(@"重連");
[self reConnect];
}
} else {
NSLog(@"沒(méi)網(wǎng)絡(luò),發(fā)送失敗,一旦斷網(wǎng) socket 會(huì)被我設(shè)置 nil 的");
NSLog(@"其實(shí)最好是發(fā)送前判斷一下網(wǎng)絡(luò)狀態(tài)比較好,我寫(xiě)的有點(diǎn)晦澀,socket==nil來(lái)表示斷網(wǎng)");
}
});
}
#pragma mark - **************** private mothodes
//重連機(jī)制
- (void)reConnect
{
[self SRWebSocketClose];
//超過(guò)一分鐘就不再重連 所以只會(huì)重連5次 2^5 = 64
if (reConnectTime > 64) {
//您的網(wǎng)絡(luò)狀況不是很好,請(qǐng)檢查網(wǎng)絡(luò)后重試
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.socket = nil;
[self SRWebSocketOpenWithURLString:self.urlString];
NSLog(@"重連");
});
//重連時(shí)間2的指數(shù)級(jí)增長(zhǎng)
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];
//心跳設(shè)置為3分鐘,NAT超時(shí)一般為5分鐘
heartBeat = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(sentheart) userInfo:nil repeats:YES];
//和服務(wù)端約定好發(fā)送什么作為心跳標(biāo)識(shí),盡可能的減小心跳包大小
[[NSRunLoop currentRunLoop] addTimer:heartBeat forMode:NSRunLoopCommonModes];
})
}
-(void)sentheart{
//發(fā)送心跳 和后臺(tái)可以約定發(fā)送什么內(nèi)容 一般可以調(diào)用ping 我這里根據(jù)后臺(tái)的要求 發(fā)送了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 {
//每次正常連接的時(shí)候清零重連時(shí)間
reConnectTime = 0;
//開(kāi)啟心跳
[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連接斷開(kāi)************************** ");
NSLog(@"被關(guān)閉連接,code:%ld,reason:%@,wasClean:%d",(long)code,reason,wasClean);
[self SRWebSocketClose];
[[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketDidCloseNote object:nil];
}
}
/*該函數(shù)是接收服務(wù)器發(fā)送的pong消息,其中最后一個(gè)是接受pong消息的,
在這里就要提一下心跳包,一般情況下建立長(zhǎng)連接都會(huì)建立一個(gè)心跳包,
用于每隔一段時(shí)間通知一次服務(wù)端,客戶(hù)端還是在線,這個(gè)心跳包其實(shí)就是一個(gè)ping消息,
我的理解就是建立一個(gè)定時(shí)器,每隔十秒或者十五秒向服務(wù)端發(fā)送一個(gè)ping消息,這個(gè)消息可是是空的
*/
-(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收到數(shù)據(jù)了************************** ");
NSLog(@"我這后臺(tái)約定的 message 是 json 格式數(shù)據(jù)收到數(shù)據(jù),就按格式解析吧,然后把數(shù)據(jù)發(fā)給調(diào)用層");
NSLog(@"message:%@",message);
[[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketdidReceiveMessageNote object:message];
}
}
#pragma mark - **************** setter getter
- (SRReadyState)socketReadyState{
return self.socket.readyState;
}
調(diào)用單利里面的webSocket實(shí)現(xiàn)長(zhǎng)連接并返回?cái)?shù)據(jù)
[[SocketRocketUtility instance] SRWebSocketOpenWithURLString:@"http://172.24.120.26:8510/sync"];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SRWebSocketDidOpen) name:kWebSocketDidOpenNote object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SRWebSocketDidReceiveMsg:) name:kWebSocketdidReceiveMessageNote object:nil];