iOS使用GCDAsyncSocket实现消息推送

最近公司项目需要用到推送,但是公司的开发者账号没有申请好,无法使用远程推送,加上后台大大说用第三方推送不方便调试,于是一起商定使用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是需要手动调用读数据的方法并设定超时等待才能读取到数据的,个人建议在发送数据的时候就调用一次读数据的方法并设定好合适的超时等待时间。

你可能感兴趣的:(iOS使用GCDAsyncSocket实现消息推送)