iOS-SocketRocket 长链接 简单使用

拟墨画扇 提交于 2020-08-06 13:44:19

基于腾讯云直播使用的SocketRocket,进行直播间的互动

一、SocketRocket

1.1 使用到的三方 SocketRocket,可以直接pod

1.2 创建SocketRocketUtility类文件,用于socket开启关闭时使用


//SocketRocketUtility.h文件

#import <Foundation/Foundation.h>
#import <SocketRocket.h>


@interface SocketRocketUtility : NSObject

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

+ (SocketRocketUtility *)shareInstance;

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

@end

//SocketRocketUtility.m文件

#import "SocketRocketUtility.h"

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

@property (nonatomic,strong) SRWebSocket *socket;

@property (nonatomic,copy) NSString *urlString;

@end

@implementation SocketRocketUtility

+(SocketRocketUtility *)shareInstance{
    static SocketRocketUtility *Instance = nil;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        Instance = [[SocketRocketUtility alloc] init];
    });
    return Instance;
}

#pragma mark - **************** public methods
-(void)SRWebSocketOpenWithURLString:(NSString *)urlString {
    
    //如果是同一个url return
    if (self.socket) {
        return;
    }
    if (!urlString) {
        return;
    }
    self.urlString = urlString;
    
    self.socket = [[SRWebSocket alloc] initWithURLRequest:
                   [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
   
    self.socket.delegate = self;   //SRWebSocketDelegate 协议
    
    [self.socket open];     //开始连接
}

-(void)SRWebSocketClose{
    if (self.socket){
        [self.socket close];
        self.socket = nil;
        //断开连接时销毁心跳
        [self destoryHeartBeat];
    }
}


- (void)sendData:(id)data {
    YNWeakSelf
    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 SRWebSocketOpenWithURLString:self.urlString];
        NSLog(@"重连");
    });
    
    //重连时间2的指数级增长
    if (reConnectTime == 0) {
        reConnectTime = 2;
    }else{
        reConnectTime *= 2;
    }
}


//取消心跳
- (void)destoryHeartBeat
{
    dispatch_main_async_safe(^{
        if (self->heartBeat) {
            if ([self->heartBeat respondsToSelector:@selector(isValid)]){
                if ([self->heartBeat isValid]){
                    [self->heartBeat invalidate];
                    self->heartBeat = nil;
                }
            }
        }
    })
}

//初始化心跳
- (void)initHeartBeat
{
    dispatch_main_async_safe(^{
        [self destoryHeartBeat];
        //心跳设置为3分钟,NAT超时一般为5分钟
        self->heartBeat = [NSTimer timerWithTimeInterval:10 target:self selector:@selector(sentheart) userInfo:nil repeats:YES];
        //和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小
        [[NSRunLoop currentRunLoop] addTimer:self->heartBeat forMode:NSRunLoopCommonModes];
    })
}

-(void)sentheart{
   
    NSString *live_id = [[NSUserDefaults standardUserDefaults] valueForKey:@"live_id"];
    
//    //发送心跳 和后台可以约定发送什么内容  一般可以调用ping  我这里根据后台的要求 发送了data给他
    if (live_id.length>0) {
        
        NSDictionary *dict = @{
            @"class":@"Index",
            @"action":@"Ping",
            @"data":@{
                @"live_id":live_id
            }
            
        };
        //传入json
        [self sendData:[YNTools YN_DictToJSON:dict]];
        
    }
}

//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 连接成功************************** ");
    }
}

- (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];

    }
}

/*该函数是接收服务器发送的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:%@",message);
        
         [[NSNotificationCenter defaultCenter] postNotificationName:@"YN_WebSocket_ReceiveMessage" object:message];
    }
}

#pragma mark - **************** setter getter
- (SRReadyState)socketReadyState{
    return self.socket.readyState;
}

-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

注意:

1.3 在所需要的页面导入头文件  #import "SocketRocketUtility.h"

二、腾讯云直播

2.1 直播SDK TXLiteAVSDK_Professional(专业版),直接pod

2.2 在AppDelegate的didFinishLaunchingWithOptions中设置sdk的licence下载url和key

    //设置sdk的licence下载url和key
    [TXLiveBase setLicenceURL:LiteAV_licenceURL key:LiteAV_licenceKey];

2.3 美颜功能可以直接从源码里扒出来,如图

2.4 导入头文件并遵守代理

#import "TXLivePush.h"//直播
#import "TCBeautyPanel.h"//美颜和滤镜 
#import "ThemeConfigurator.h"//颜色

代理
BeautyLoadPituDelegate 和 TXLivePushListener


@property (nonatomic, strong) TXLivePush *pusher;
@property (nonatomic, strong) UIView *localView; //本地预览
@property (nonatomic, assign) BOOL appIsInActive;
@property (nonatomic, assign) BOOL appIsBackground;
- (TXLivePush *)pusher
{
    if (!_pusher) {
        TXLivePushConfig *config = [[TXLivePushConfig alloc] init];
        config.pauseFps = 10;
        config.pauseTime = 300;
        config.homeOrientation = HOME_ORIENTATION_RIGHT;
        config.pauseImg = [UIImage imageNamed:@"pause_publish"];
        config.enableNearestIP = YES;
        
        // 推流器初始化
        _pusher = [[TXLivePush alloc] initWithConfig:config];
        // 设置delegate
        [_pusher setDelegate:self];
    }
    return _pusher;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    _appIsInActive = NO;
    _appIsBackground = NO;

    if (!_localView) {
        _localView = [[UIView alloc]init];
    }
    [self.view insertSubview:self.localView atIndex:0];
    self.localView.backgroundColor = [UIColor clearColor];
    
    [self.localView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.bottom.right.offset(0);
        make.left.offset(0);
    }];
    
    //预览上部view
    [self setUpStareView];
    
    _appIsInActive = NO;
    _appIsBackground = NO;
    
    
    //启动本地摄像头预览(需要添加预览view)
    [self.pusher startPreview:self.localView];
    //用于心跳连接
    [[NSUserDefaults standardUserDefaults] setValue:self.liveID forKey:@"live_id"];

}

进行直播时,连接scoket进入直播间并开启推流状态

连接scoket

 //连接scoket
[[SocketRocketUtility shareInstance] SRWebSocketOpenWithURLString:url];

进入直播间

 NSDictionary *dict = @{
                
                @"class":@"Push",
                @"action":@"Joinlive",
                @"data":@{
                        @"live_id":self.liveID,
                        @"user_id":user_id
                        
                }
                
            };
            
[[SocketRocketUtility shareInstance] sendData:[YNTools YN_DictToJSON:dict]];

开始推流

 //开始推流
[self.pusher startPush:self.roomModel.pushstream_address];

在直播过程中进行的一些操作:发消息、发公告、打赏、购买商品等都可以走socket连接,如

//发消息

                //发消息
                NSDictionary *dict = @{
                    
                    @"class":@"Push",
                    @"action":@"Comment",
                    @"data":@{
                            @"live_id":self.liveID,
                            @"content":self.commentView.textView.text,
                            @"type":@"3",//评论类型(1 评论 2打赏 3主播 4管理员)  根据发起评论用户类型而定可由后端返回
                            @"user_id":user_id
                            
                    }
                    
                };
                
                [[SocketRocketUtility shareInstance] sendData:[YNTools YN_DictToJSON:dict]];

  结束直播

//停止推流
[self stopPushLive];
//关闭socket连接
[[SocketRocketUtility shareInstance] SRWebSocketClose];


-(void)stopPushLive
{
    if (_pusher) {
        [_pusher setDelegate:nil];
        [_pusher stopPreview];
        [_pusher stopPush];
    }
}

直播过程中接收到的socket消息,可对其监听并处理

//socket接收到消息
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveMessage:) name:@"YN_WebSocket_ReceiveMessage" object:nil];

处理收到的消息

//TODO:接收到消息
-(void)receiveMessage:(NSNotification *)noti
{
    //json转字典
    NSDictionary *item = [YNTools YN_JSONToDict:noti.object];
    
    NSLog(@"------item:%@",item);
}

对于在直播过程中程序进入后台以及从后台重新激活,会有页面卡顿,因此也要进行处理

添加直播监听

//直播监听
-(void)pushLiveNotification
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidEnterBackGround:) name:UIApplicationDidEnterBackgroundNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
}

- (void)onAppWillResignActive:(NSNotification *)notification {
    _appIsInActive = YES;
    //暂停摄像头采集并暂停推流
    [_pusher pausePush];
}

- (void)onAppDidBecomeActive:(NSNotification *)notification {
    _appIsInActive = NO;
    if (!_appIsBackground && !_appIsInActive) {
        //恢复摄像头采集并继续推流
        [_pusher resumePush];
        
    }
}

- (void)onAppDidEnterBackGround:(NSNotification *)notification {
    [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        
    }];
    _appIsBackground = YES;
    //暂停摄像头采集并暂停推流
    [_pusher pausePush];
}

- (void)onAppWillEnterForeground:(NSNotification *)notification {
    _appIsBackground = NO;
    
    if (!_appIsBackground && !_appIsInActive) {
        //恢复摄像头采集并继续推流
        [_pusher resumePush];
        
    }
}

最后上一张直播间的图

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!