美文网首页iOS-Developer-OCiOS DeveloperiOS
iOS使用GCDAsyncSocket实现消息推送

iOS使用GCDAsyncSocket实现消息推送

作者: 白云心城 | 来源:发表于2017-03-11 14:45 被阅读794次

最近公司项目需要用到推送,但是公司的开发者账号没有申请好,无法使用远程推送,加上后台大大说用第三方推送不方便调试,于是一起商定使用socket实现消息推送。

实现原理

利用socket与远程服务器建立长连接进行消息通讯,服务端通过检测与客户端的连接状态进行消息推送,客户端收到数据后给服务器发送应答消息让服务器知道消息的投递情况。优点:调试方便,使用灵活。缺点:无法实现App后台接收消息。

主要实现代码如下

  • 创建socket
// 属性定义
// 超时等待时长
static NSUInteger timeout = 45;
// socket
@property(nonatomic,strong)GCDAsyncSocket *socket;
// 定时器
@property(nonatomic,strong)NSTimer *timer;
// 时间计数
@property(nonatomic,assign)NSUInteger count;
// 连接状态标识
@property(nonatomic,assign)BOOL isConnect;
// 登录状态标识
@property(nonatomic,assign)BOOL isLogin;
// 重连计数
@property(nonatomic,assign)NSUInteger reconnectCount;

// 懒加载方法
- (GCDAsyncSocket *)socket {
    if (_socket == nil) {
        _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    }
    return _socket;
}
- (NSTimer *)timer {
    if (_timer == nil) {
        _timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
        [_timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:0]];
        [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    }
    return _timer;
}
  • 建立连接
- (void)startConnectWithURLStr:(NSString *)urlStr {
    // 开启定时器
    self.timer.fireDate = [NSDate dateWithTimeIntervalSinceNow:0];
    // 处理URL字符串
    NSRange range = [urlStr rangeOfString:@":"];
    NSString *host = [urlStr substringToIndex:range.location];
    NSString *port = [urlStr substringFromIndex:range.location+range.length];
    // 连接(如果已经连接则调用登录方法)
    if (self.socket.isConnected == NO) {
        [self.socket connectToHost:host onPort:port.integerValue error:nil];
        [self timer];
        NSLog(@"开始连接主机:%@ 端口号:%@",host,port);
    }
    else {
        [self startLogin];
    }
}
  • 向服务器发起登录请求
// 登录方法
- (void)startLogin {
    NSLog(@"开始登录。。。");
    // 获取登录报文数据
    NSData *data = [self getLoginData];
    
    // 发送数据
    NSLog(@"发送登录数据:%@",data);
    [self.socket writeData:data withTimeout:timeout tag:0];
    // 启动socket读数据等待(必须调用此方法才能收到服务器传输的数据)
    [self.socket readDataWithTimeout:timeout tag:0];
    // 重置定时器计数
    _count = 0;
}
  • 登录成功定时发送心跳包
// 定时器回调方法
- (void)timerAction {
// 计时递增
    _count += 1;
    if (_isConnect) {       //连接成功
        if (_isLogin) {     //登录成功
            if (_count >= 40) {  //每40秒进行一次操作
                // 发送心跳包
                [self sendHeartBeatData];
                _count = 0;
            }
        }
        else {              //登录失败
            if (_count > timeout) {
                // 重登录
                [self startLogin];
                NSLog(@"socket重新登录!");
                _count = 0;
            }
        }
    }
    else {                  //连接失败
        if (_count >= timeout) {
            // 重连
            [self startConnect];
            _reconnectCount+=1;
            NSLog(@"socket重新连接!--%zd次-",_reconnectCount);
            _count = 0;
        }
    }
}
  • 代理方法(GCDAsyncSocketDelegate)
// 即将连接到主机
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    NSLog(@"--socket--连接成功");
    // 重置重连接计数
    _reconnectCount = 0;
    // 置位连接状态标识
    _isConnect = YES;
    // 重设置定时计数
    _count = 0;
    // 启动登录
    [self startLogin];
}
// socket连接断开
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
    if (err.code == 4) {
        // 读取超时
        NSLog(@"获取socket服务器回应超时");
        // 手动断开并重连
        [self stopConnect];
        [self startConnect];
        return;
    }
    else if (err.code == 60) {
        NSLog(@"socket连接超时");
        // 手动断开并重连
        [self stopConnect];
        [self startConnect];
    }
    else {
        NSLog(@"--socket--连接失败--err:%@",err);
        if (err != nil) {
        // 重置标识位,定时重连
            _isConnect = NO;
            _isLogin = NO;
        }
    }
}
// 读取到服务端发送的数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    
    NSLog(@"--socket--Read:%@",data);
    // 调用数据解析方法解析数据
    [self syntacticReadData:data];
    // 进行下一次数据读取
    [self.socket readDataWithTimeout:timeout tag:0];
}

  • 手动断开连接
// 手动断开连接方法
- (void)stopConnect {
    // 停止定时器
    self.timer.fireDate = [NSDate distantFuture];
    // 释放socket连接
    [self.socket disconnect];
    // 设置检查标识
    _isConnect = NO;
    _isLogin = NO;
    // 重设重连计数
    _reconnectCount = 0;
    _count = 0;
    NSLog(@"断开socket连接");
}

个人总结
第一次使用GCDAsyncSocket进行开发,刚开始以为创建socket之后设置好代理就可以从代理方法中读取到服务器的回应数据,可是一直都是可以发送数据给服务器而没有收到服务器给的回应数据(读取数据的哪个代理方法根本就不走),于是经过一番百度查找才知道原来GCDAsyncSocket是需要手动调用读数据的方法并设定超时等待才能读取到数据的,个人建议在发送数据的时候就调用一次读数据的方法并设定好合适的超时等待时间。

相关文章

网友评论

  • 9527_7677:Soket 做连接推送后台无法推送 要走后台推送的话必须要自身服务器链接apns做推送对吧 大佬!
    白云心城:@9527_7677 根据当时我调试的情况,应用进入后台一段时间后就无法发送心跳包了,链接会因此断开
    9527_7677:@白云心城 如果为iOS 设置心跳包进入后台这个心跳依旧会停止吗!
    白云心城:@9527_7677 嗯,是的
  • iOS_小张:我想问一下,你发送纯文本消息和发送图片,有什么不同的地方么?
    白云心城:我这个的数据是按照二进制进行传输和处理的,用的协议是后台自定义的一个简单的通讯协议,发送图片不会使用这个来发送。至于不同类型的消息,都是按照协议上去拟定处理方式的。
  • 6b1c2f837c82:你好,我这里添加了那个读取数据的方法了,但是读取的回调方法还是不会走。。。。你知道什么原因吗?
    白云心城:抱歉最近不是常看简书,你问题解决了吗?
  • 开发全靠xib:感觉这个确实不是推送..而且后台压力貌似也不小
    白云心城:@开发全靠xib 都瘫了:joy:
    开发全靠xib:@白云心城 是服务器瘫了还是人瘫了:joy::joy::joy::joy:
    白云心城:@开发全靠xib这个不是我能决定的啊:scream:,而且我们后台最近好像累瘫了:flushed:
  • 龙伟17:这叫 不叫推送吧
    白云心城:@龙红伟 :sweat:
  • 9229d79ec840:有没有demo 呀?
    白云心城:@小白攻城师 嗯,是的,这个其实和即时通信有点像
    9229d79ec840:@白云心城 无法实现App后台接收消息?那这个推送就只能在玩app时候推送?是这个意思吗?原谅小白。。。。。。大神勿喷
    白云心城:@zxqiOS :sweat:直接写的项目,没有写Demo

本文标题:iOS使用GCDAsyncSocket实现消息推送

本文链接:https://www.haomeiwen.com/subject/xdyagttx.html