SocketRocket

SocketRocket是Facebook开源的一个用于 iOS, macOS and tvOS客户端的websocket框架。

  1. 集成

使用cocoapods 只需要在podfile文件中加入

pod 'SocketRocket'

然后执行pod install就可以了

不使用cocoapods 添加文件 把下面的三个文件拖入项目中

其中,SocketRocket.h文件是用来引入框架的所有类的文件,就像

<UIKit/UIkit.h>

目前,这个文件只引入了一个类,所以只有一行代码

<SocketRocket/SRWebSocket.h>

这样的话,编译会报错,因为并没有一个SocketRocket的framework,需要把这一行代码改为

#import "SRWebSocket.h"

添加依赖库 在Build Phases -> Link Binary With Libraries里加入如下frameworks:

libicucore.dylib CFNetwork.framework Security.framework Foundation.framework

  1. 使用

初始化 初始化方法分为两类: 通过传入NSURLRequest的对象进行初始化

-(id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates;
-(id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols;
-(id)initWithURLRequest:(NSURLRequest *)request;

通过传入NSURL的对象进行初始化

-(id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates;
-(id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols;
-(id)initWithURL:(NSURL *)url;

其中,使用NSURLRequest进行初始化可以自定义请求超时时间

[NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:timeoutInterval]

打开连接

使用第一步创建出来的SRWebSocket对象(以下简称‘对象’)调用- (void)open来与服务器建立连接。这里需要注意

// SRWebSockets are intended for one-time-use only. Open should be called once and only once.

一个对象只能调用一次

- (void)open

调用过open后,对象的readyState就为SR_CONNECTING,如果再调用open,就会

NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once");

发送数据

使用对象调用

- (void)send:(id)data

这个data可以是一个UTF8的字符串或者NSData对象

关闭连接

使用对象调用

- (void)close

来关闭一个连接。

回调

以delegate的方式进行回调,包括下面几个回调函数

@protocol SRWebSocketDelegate <NSObject>
-(void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;
@optional
-(void)webSocketDidOpen:(SRWebSocket *)webSocket;
-(void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
-(void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
-(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload;
-(BOOL)webSocketShouldConvertTextFrameToString:(SRWebSocket *)webSocket;
@end

其中,

  • webSocketDidOpen表示与服务器的连接建立成功,这时就可以与服务器进行交互了。

  • didFailWithError表示连接出现错误,包括连接没有成功建立的错误、网络错误等。如果需要进行重连,应该在这里进行,可以采用Demo里面的方式进行重连:把以后的对象置空->重新初始化->调用open。应用切到后台2分钟以后才会出现连接错误的回调。

SocketRocket已经对消息的收发进行了处理。我们收到和发出的消息都是根据socket头进行分隔的,所以不会出现消息不完整或者多条消息同时收发的问题。

上面是在使用SocketRocket的过程中的总结,通过这些应该可以把这个框架使用起来,以后有时间会继续钻研这个框架的源码。SocketRocket还是挺好用的。

一个工具类

SocketRocketUtility.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 SocketRocketUtility : NSObject

// 获取连接状态
@property (nonatomic,assign,readonly) SRReadyState socketReadyState;

+ (SocketRocketUtility *)instance;

-(void)SRWebSocketOpen;//开启连接
-(void)SRWebSocketClose;//关闭连接
- (void)sendData:(id)data;//发送数据

@end

SocketRocketUtility.w

#import "SocketRocketUtility.h"

#define SERVER_URL @"ws://192.168.5.130:3000"

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

NSString * const kNeedPayOrderNote               = @"kNeedPayOrderNote";
NSString * const kWebSocketDidOpenNote           = @"kWebSocketdidReceiveMessageNote";
NSString * const kWebSocketDidCloseNote          = @"kWebSocketDidCloseNote";
NSString * const kWebSocketdidReceiveMessageNote = @"kWebSocketdidReceiveMessageNote";

@interface SocketRocketUtility()<SRWebSocketDelegate>
{
    int _index;
    NSTimer * heartBeat;
    NSTimeInterval reConnectTime;
}

@property (nonatomic,strong) SRWebSocket *socket;

@end

@implementation SocketRocketUtility

+(SocketRocketUtility *)instance{
    static SocketRocketUtility *Instance = nil;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        Instance = [[SocketRocketUtility 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:SERVER_URL]]];//这里填写你服务器的地址

    NSLog(@"请求的websocket地址:%@",self.socket.url.absoluteString);

    self.socket.delegate = self;   //SRWebSocketDelegate 协议

    [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

使用工具类连接服务端

#import "ViewController.h"
#import "SocketRocketUtility.h"

@interface ViewController ()


@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [[SocketRocketUtility instance] SRWebSocketOpen];//打开soket
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SRWebSocketDidOpen) name:@"kWebSocketDidOpenNote" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SRWebSocketDidReceiveMsg:) name:@"kWebSocketdidReceiveMessageNote" object:nil];

//    [[SocketRocketUtility instance] SRWebSocketClose]; 在需要得地方 关闭socket

}

- (void)SRWebSocketDidOpen {
    NSLog(@"开启成功");
    //在成功后需要做的操作。。。

}

- (void)SRWebSocketDidReceiveMsg:(NSNotification *)note {
    //收到服务端发送过来的消息
    NSString * message = note.object;
    NSLog(@"%@",message);
}

@end

results matching ""

    No results matching ""