收藏iosiOS面试题网络

iOS websocket(SocketRocket)

2019-05-30  本文已影响2人  后浪普拉斯

项目工程中需要对服务端的一些硬件操作,之后等待服务端回调,思来想去只能使用websocket了。

什么是websocket?

我们先来一下HTTP,为什么有何还要使用websocket呢?
这就要有HTTP协议的局限性决定的,通信只能由客户端发起。
那么我们需要等待服务端回调的时候就出现了问题,我们无法获取服务端的回调信息,我们能做的只有两种方式:

1、按间隔不停的向服务端发送请求,直到收到服务端的结果。
2、HTTP连接始终打开连接

HTTP长连接和短连接区别?
这两种方式归根到底都浪费了资源,于是就有了webSocket,我们看一下HTTP和WebSocket的区别。

icon.png

webSocket的特点

1、建立在 TCP 协议之上,服务器端的实现比较容易。
2、与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
3、数据格式比较轻量,性能开销小,通信高效。
4、可以发送文本,也可以发送二进制数据。
5、没有同源限制,客户端可以与任意服务器通信。
6、协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
ws://example.com:80/some/path

心跳机制

心跳就是用来查看TCP连接双方是否可用,而TCP的KeepAlive则是保证连接的存在,并不能保证双方的可用性。
心跳ping 有客户端发起,假如在指定时间内未收到回调,那此时就判断此时连接不可用,我们应该主动断开连接,当然服务端也维护一份scoket心跳,在未收到客户端的心跳之后,服务端任务连接失效,也会主动断开。

SocketRocket

SocketRocket 是facebook下对websocket的封装,具体源码就需要在github上看了。
我们直接通过pod 引入就好了:

platform :ios, '9.0'
target 'iosWebSocket' do
  pod 'SocketRocket'
  pod 'AFNetworking','~>3.1.0'
end

iOS客户端部分的步骤:

1、建立连接

-(void)connectServer{
    if(self.webScoket){
        return;
    }
    
    self.webScoket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:@"ws://127.0.0.1:7272"]];
    self.webScoket.delegate = self;
    [self.webScoket open];
}

2、建立连接之后我们需要向服务端发送心跳,此时我们需要在SRWebSocketDelegate的delegate中等待成功之后去发送心跳请求。

心跳的方法就不展开了,后面去看源码或者demo吧,其实就是调用SRWebSocket 的 sendPing方法

//已经连接
-(void)webSocketDidOpen:(SRWebSocket *)webSocket{
    NSLog(@"已经连接,开启心跳");
    self.isConnect = YES;
    self.socketStatus = WebSocketStatusConnect;
    [self initHeartBeat];//开始心跳
}

3、发送数据

注意:我们在sendData的时候,我们一定要判断webScoket.readyState 是SR_OPEN,此时才能发送数据,否则会crash的。

//发送数据给服务器
-(void)sendDataToServer:(NSString *)data{
    [self.sendDataArray addObject:data];
    
    //没有网络
    if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){
        //开启网络检测定时器
        [self noNetWorkStartTesting];
    }else{
        if (self.webScoket != nil) {
            //只有长连接OPEN开启状态才能调用send方法
            if (self.webScoket.readyState == SR_OPEN) {
                [self.webScoket send:data];
            }else if (self.webScoket.readyState == SR_CONNECTING){
                //正在连接
                NSLog(@"正在连接中,重连后会去自动同步数据");
            }else if(self.webScoket.readyState == SR_CLOSING || self.webScoket.readyState == SR_CLOSED){
                //调用 reConnectServer 方法重连,连接成功后 继续发送数据
                [self reConnectServer];
            }
        }else{
            [self connectServer];//连接服务器
        }
    }
}

4、服务端的回调

服务端的回调,我们只需要监听didReceiveMessage方法就好了。
自己定义delegate方法,之后在使用的地方实现代理协议,这样我们就能获取到服务端的回调了。

//接收消息
-(void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{
    NSLog(@"接收消息 ---- %@", message);
    if (self.delegate && [self.delegate respondsToSelector:@selector(webSocketDidReceiveMessage:)]) {
        [self.delegate webSocketDidReceiveMessage:message];
    }
}

5、关闭webSocket

//重新连接
-(void)reConnectServer{
    
    //关闭之前的连接
    [self webSocketClose];
    
    //重连10次 2^10 = 1024
    if (self.reConnectTime > 1024) {
        self.reConnectTime = 0;
        return;
    }
    
    __weak typeof(self)ws = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (ws.webScoket.readyState == SR_OPEN && ws.webScoket.readyState == SR_CONNECTING) {
            return ;
        }
        
        [ws connectServer];
        NSLog(@"重新连接......");
        if (ws.reConnectTime == 0) {//重连时间2的指数级增长
            ws.reConnectTime = 2;
        }else{
            ws.reConnectTime *= 2;
        }
    });
}

WebSocketManager.h

//
//  WebSocketManager.h
//  iosWebSocket
//
//  Created by yanglele on 2019/5/30.
//  Copyright © 2019. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "SRWebSocket.h"

NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, WebSocketStatus){
    WebSocketStatusDefault = 0, //初始状态,未连接
    WebSocketStatusConnect,     //已连接
    WebSocketStatusDisConnect,  //断开连接
};

@protocol WebSocketManagerDelegate<NSObject>

-(void)webSocketDidReceiveMessage:(NSString *)string;

@end


@interface WebSocketManager : NSObject

@property(nonatomic, strong) SRWebSocket *webScoket;
@property(nonatomic, weak) id<WebSocketManagerDelegate> delegate;
@property(nonatomic, assign) BOOL isConnect; //是否连接
@property(nonatomic, assign) WebSocketStatus socketStatus;

+(instancetype)shared;
-(void)connectServer;//建立长连接
-(void)reConnectServer;//重新连接
-(void)webSocketClose;//关闭连接
-(void)sendDataToServer:(NSString *)data; //向服务器发送数据

@end

NS_ASSUME_NONNULL_END

WebSocketManager.m

//
//  WebSocketManager.m
//  iosWebSocket
//
//  Created by yanglele on 2019/5/30.
//  Copyright © 2019. All rights reserved.
//

#import "WebSocketManager.h"
#import "AFNetworking.h"

#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}

@interface WebSocketManager ()<SRWebSocketDelegate>

@property(nonatomic, strong) NSTimer *headerBeatTimer; //心跳定时器
@property(nonatomic, strong) NSTimer *networkTestingTimer; //没有网络的时候检测定时器
@property(nonatomic, assign) NSTimeInterval reConnectTime; //重连时间
@property(nonatomic, strong) NSMutableArray *sendDataArray; //存储要发送给服务器的数据
@property(nonatomic, assign) BOOL isActiveClose; //用于判断是否主动关闭长连接,如果是主动断开连接,连接失败的代理中,就不用执行 重新连接方法

@end


@implementation WebSocketManager

+(instancetype)shared{
    static WebSocketManager *__instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        __instance = [[WebSocketManager alloc] init];
    });
    return __instance;
}

-(instancetype)init{
    self = [super init];
    if (self) {
        self.reConnectTime = 0;
        self.isActiveClose = NO;
        self.sendDataArray = [[NSMutableArray alloc] init];
    }
    return self;
}

//建立长连接
-(void)connectServer{
    if(self.webScoket){
        return;
    }
    
    self.webScoket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:@"ws://127.0.0.1:7272"]];
    self.webScoket.delegate = self;
    [self.webScoket open];
}

-(void)sendPing:(id)sender{
    NSLog(@"sendPing heart");
//    NSString *heart = @"heart";
    NSData *heartData = [[NSData alloc] initWithBase64EncodedString:@"heart" options:NSUTF8StringEncoding];
    [self.webScoket sendPing:heartData];
    //    [self.webScoket sendPing:nil error:NULL];
}

//关闭长连接
-(void)webSocketClose{
    self.isActiveClose = YES;
    self.isConnect = NO;
    self.socketStatus = WebSocketStatusDefault;
    
    if (self.webScoket) {
        [self.webScoket close];
        self.webScoket = nil;
    }
    //关闭心跳定时器
    [self destoryHeartBeat];
    //关闭网络检测定时器
    [self destoryNetWorkStartTesting];
}

#pragma mark socket delegate
//已经连接
-(void)webSocketDidOpen:(SRWebSocket *)webSocket{
    NSLog(@"已经连接,开启心跳");
    self.isConnect = YES;
    self.socketStatus = WebSocketStatusConnect;
    [self initHeartBeat];//开始心跳
}

//连接失败
-(void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{
    NSLog(@"连接失败");
    self.isConnect = NO;
    self.socketStatus = WebSocketStatusDisConnect;
    NSLog(@"连接失败,这里可以实现掉线自动重连,要注意以下几点");
    NSLog(@"1.判断当前网络环境,如果断网了就不要连了,等待网络到来,在发起重连");
    NSLog(@"2.判断调用层是否需要连接,不需要的时候不k连接,浪费流量");
    NSLog(@"3.连接次数限制,如果连接失败了,重试10次左右就可以了");
    //判断网络环境
    if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {
        //没有网络,开启网络监测定时器
        [self noNetWorkStartTesting];//开启网络检测定时器
    }else{
        [self reConnectServer];//连接失败,重新连接
    }
    
}

//接收消息
-(void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{
    NSLog(@"接收消息 ---- %@", message);
    if (self.delegate && [self.delegate respondsToSelector:@selector(webSocketDidReceiveMessage:)]) {
        [self.delegate webSocketDidReceiveMessage:message];
    }
}

//关闭连接
-(void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{
    self.isConnect = NO;
    if (self.isActiveClose) {
        self.socketStatus = WebSocketStatusDefault;
        return;
    }else{
        self.socketStatus = WebSocketStatusDisConnect;
    }
    NSLog(@"被关闭连接,code:%ld,reason:%@,wasClean:%d",code,reason,wasClean);
    
    [self destoryHeartBeat];  //断开时销毁心跳
    
    //判断网络
    if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {
        //没有网络,开启网络监测定时器
        [self noNetWorkStartTesting];
    }else{
        //有网络
        NSLog(@"关闭网络");
        self.webScoket = nil;
        [self reConnectServer];
    }
}


/**
 接受服务端发生Pong消息,我们在建立长连接之后会建立与服务器端的心跳包
 心跳包是我们用来告诉服务端:客户端还在线,心跳包是ping消息,于此同时服务端也会返回给我们一个pong消息
 */
-(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongData{
    NSLog(@"接受ping 数据  --> %@",pongData);
}

#pragma mark NSTimer
//初始化心跳
-(void)initHeartBeat{
    if (self.headerBeatTimer) {
        return;
    }
    [self destoryHeartBeat];
    dispatch_main_async_safe(^{
        self.headerBeatTimer = [NSTimer timerWithTimeInterval:10 target:self selector:@selector(senderheartBeat) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:self.headerBeatTimer forMode:NSRunLoopCommonModes];
    });
}

//重新连接
-(void)reConnectServer{
    
    //关闭之前的连接
    [self webSocketClose];
    
    //重连10次 2^10 = 1024
    if (self.reConnectTime > 1024) {
        self.reConnectTime = 0;
        return;
    }
    
    __weak typeof(self)ws = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (ws.webScoket.readyState == SR_OPEN && ws.webScoket.readyState == SR_CONNECTING) {
            return ;
        }
        
        [ws connectServer];
        NSLog(@"重新连接......");
        if (ws.reConnectTime == 0) {//重连时间2的指数级增长
            ws.reConnectTime = 2;
        }else{
            ws.reConnectTime *= 2;
        }
    });
}

//发送心跳
-(void)senderheartBeat{
    NSLog(@"senderheartBeat");
    //和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小
    __weak typeof (self) ws = self;
    dispatch_main_async_safe(^{
        if (ws.webScoket.readyState == SR_OPEN) {
            [ws sendPing:nil];
        }else if (ws.webScoket.readyState == SR_CONNECTING){
            NSLog(@"正在连接中");
            [ws reConnectServer];
        }else if (ws.webScoket.readyState == SR_CLOSED || ws.webScoket.readyState == SR_CLOSING){
            NSLog(@"断开,重连");
            [ws reConnectServer];
        }else{
            NSLog(@"没网络,发送失败,一旦断网 socket 会被我设置 nil 的");
        }
    });
}

//取消心跳
-(void)destoryHeartBeat{
    __weak typeof(self) ws = self;
    dispatch_main_async_safe(^{
        if (ws.headerBeatTimer) {
            [ws.headerBeatTimer invalidate];
            ws.headerBeatTimer = nil;
        }
    });
}

//没有网络的时候开始定时 -- 用于网络检测
-(void)noNetWorkStartTestingTimer{
    __weak typeof(self)ws = self;
    dispatch_main_async_safe(^{
        ws.networkTestingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(noNetWorkStartTesting) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:ws.networkTestingTimer forMode:NSDefaultRunLoopMode];
    });
}

//定时检测网络
-(void)noNetWorkStartTesting{
    if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable) {
        //关闭网络检测定时器
        [self destoryNetWorkStartTesting];
        //重新连接
        [self reConnectServer];
    }
}
//取消网络检测
-(void)destoryNetWorkStartTesting{
    __weak typeof(self) ws = self;
    dispatch_main_async_safe(^{
        if (ws.networkTestingTimer) {
            [ws.networkTestingTimer invalidate];
            ws.networkTestingTimer = nil;
        }
    });
}

//发送数据给服务器
-(void)sendDataToServer:(NSString *)data{
    [self.sendDataArray addObject:data];
    
    //没有网络
    if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){
        //开启网络检测定时器
        [self noNetWorkStartTesting];
    }else{
        if (self.webScoket != nil) {
            //只有长连接OPEN开启状态才能调用send方法
            if (self.webScoket.readyState == SR_OPEN) {
                [self.webScoket send:data];
            }else if (self.webScoket.readyState == SR_CONNECTING){
                //正在连接
                NSLog(@"正在连接中,重连后会去自动同步数据");
            }else if(self.webScoket.readyState == SR_CLOSING || self.webScoket.readyState == SR_CLOSED){
                //调用 reConnectServer 方法重连,连接成功后 继续发送数据
                [self reConnectServer];
            }
        }else{
            [self connectServer];//连接服务器
        }
    }
}
@end

websocket服务端

1、安装node

npm install node

2、安装ws模块

npm install ws

3、找到服务端 websocketService 程序,执行

node websocketService.js

至此我们开启了服务端的服务,开始监听客户端的连接。

demo

上一篇 下一篇

猜你喜欢

热点阅读