美文网首页
ios 即时通讯 socket 和 CocoaAsyncSock

ios 即时通讯 socket 和 CocoaAsyncSock

作者: __ENUUI | 来源:发表于2017-08-01 15:31 被阅读0次

    说到即时通讯,就一定有绕不开微信、QQ这样在国内具有统治地位的社交app。但其实,即时通讯在app中的应用还是非常广泛的。做即时通讯第三方服务的也非常多,像环信这样的厂商。但是即时通讯这种涉及自家用户数据的功能,还是掌握在自己的手里比较好。更重要的一点事,第三方服务并非免费的。所以,能够自己来,干嘛要被别人掣肘。

    本文介绍一种即时通讯的实现方式。

    一、Socket:

    自定义socket的全部核心代码都在下面图里了,不推荐这么干啊,反正大牛也不会看这里。因为github社区里有更强大、更简单的socket的API。

    
    #import "SocketDemo.h"
    
    #include
    
    #include
    
    #include
    
    @interface SocketDemo ()
    
    @property (nonatomic, assign) int fd;
    
    @end
    
    @implementation SocketDemo
    
    - (void)creatSocketClientWith:(const char *)ip port:(__uint16_t)port {
    
    int err;
    
    //创建socket
    
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    
    _fd = fd;
    
    BOOL success = (fd != -1);
    
    struct sockaddr_in addr;
    
    if (fd != -1) {
    
    memset(&addr, 0, sizeof(addr));
    
    addr.sin_len = sizeof(addr);
    
    addr.sin_family = AF_INET;
    
    addr.sin_addr.s_addr = INADDR_ANY;
    
    //建立地址和套接字的联系
    
    err = bind(fd, (const struct sockaddr *)&addr, sizeof(addr));
    
    success = (err == 0);
    
    }
    
    if (success) {
    
    struct sockaddr_in serveraddr;
    
    memset(&serveraddr, 0, sizeof(serveraddr));
    
    serveraddr.sin_len = sizeof(serveraddr);
    
    serveraddr.sin_family = AF_INET;
    
    //服务器端口
    
    serveraddr.sin_port = htons(port);
    
    //服务器的地址
    
    serveraddr.sin_addr.s_addr = inet_addr(ip);
    
    socklen_t addrLen;
    
    addrLen = sizeof(serveraddr);
    
    err = connect(fd, (struct sockaddr *)&serveraddr, addrLen);
    
    success = (err == 0);
    
    if (success) {
    
    // getsockname是对tcp连接而言。套接字socket必须是已连接套接字描述符。
    
    err = getsockname(fd, (struct sockaddr *)&addr, &addrLen);
    
    success = (err == 0);
    
    if (success) {
    
    NSLog(@"连接服务器成功");
    
    [NSThread detachNewThreadSelector:@selector(reciveMessage:) toTarget:self withObject:@(fd)];
    
    }
    
    } else{
    
    NSLog(@"connect failed");
    
    }
    
    }
    
    }
    
    - (void)reciveMessage:(id)peerfd {
    
    int fd = [peerfd intValue];
    
    char buf[1024];
    
    ssize_t bufLen;
    
    size_t len = sizeof(buf);
    
    //循环阻塞接收消息
    
    do {
    
    bufLen = recv(fd, buf, len, 0);
    
    //当返回值小于等于零时,表示socket异常或者socket关闭,退出循环阻塞接收消息
    
    if (bufLen <= 0) {
    
    break;
    
    }
    
    //接收到的信息
    
    NSString *msg = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
    
    NSLog(@"来自服务端,消息内容:%@", msg);
    
    } while (true);
    
    // 7.关闭
    
    close(fd);
    
    }
    
    - (void)sendData:(NSData *)data {
    
    const uint8_t *buffer = (const uint8_t *)[data bytes];
    
    write(_fd, buffer, (size_t)data.length);
    
    }
    
    @end
    
    

    源码:socketDemo
    参考:http://www.jb51.net/article/105715.htm

    二、CocoaAsyncSocket

    推荐使用的就是CocoaAsyncSocket理由也很简单,首先Socket是c语言库,开发起来还是有一定难度;另外就是CocoaAsyncSocket是有github这个强力社区支持的开源代码,稳定可靠性是值得信赖的,封装了很多功能,使用起来也很简单。

    首先:pod 'CocoaAsyncSocket' ,或者手动也是可以的,依赖'CFNetwork','Security'这两个库就可以啦。

    1.即时通讯时典型的可以使用单例管理的。创建管理类,并创建单例、初始化对象

    
    + (instancetype)sharedInstance {
    
    static id instance = nil;
    
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
    
    instance = [[SocketDemo alloc] init];
    
    });
    
    return instance;
    
    }
    
    - (instancetype)init {
    
    self = [super init];
    
    if (self) {
    
    _rwQueue = dispatch_queue_create("com.enuui.read_write.queue", DISPATCH_QUEUE_SERIAL);
    
    _delegateQueue = dispatch_queue_create("com.enuui.delegate.queue", DISPATCH_QUEUE_SERIAL);
    
    _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_delegateQueue];
    
    [self GCDTimer];
    
    }
    
    return self;
    
    }
    
    

    2.接下来就是连接socket啦。start、stop、和reStart是暴露出去使用的方法。host和port是不会变化的,所以用的宏定义。

    注:如果对一个已经连接的socket再次连接,会导致socket抛出异常,程序崩溃,所以在连接或者重连socket之前都要确保socket出于非连接状态。

    
    - (void)start {
    
    if (_isConnected) {
    
    return;
    
    }
    
    [self connect];
    
    }
    
    - (void)stop {
    
    [self disConnect];
    
    }
    
    - (void)reStart {
    
    [self disConnect];
    
    [self connect];
    
    }
    
    - (void)connect {
    
    //此处判断是否有身份验证信息
    
    NSError *error = nil;
    
    if (_socket.delegate == nil) {
    
    [_socket setDelegate:self]; // check delegate
    
    }
    
    if (![_socket connectToHost:CUSTOM_SOCKET_HOST onPort:CUSTOM_SOCKET_PORT error:&error]) {
    
    NSLog(@"连接失败:%@", error);
    
    _isConnected = NO;
    
    } else {
    
    _isConnected = YES;
    
    }
    
    }
    
    - (void)disConnect {
    
    [_socket setDelegate:nil];
    
    [_socket disconnect];
    
    _isConnected = NO;
    
    }
    
    

    3.连接到服务器后,就是监听代理了。

    
    //所有的代理都是在创建GCDAsyncSocket对象时传入的队列上异步执行
    
    - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    
    NSLog(@"Tcp socket Connected %@:%d", host, port);
    
    //连接到服务器,调用此代理
    
    //可在此代理用向服务器发送身份验证信息
    
    //开启心跳
    
    [self startHeartbeat];
    
    //读取
    
    [_socket readDataWithTimeout:-1 tag:0];
    
    }
    
    - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
    
    //可在此方法中更新已发送的信息状态
    
    }
    
    - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    
    //处理接收到的消息
    
    [self readData:data tag:tag];
    
    //读取
    
    [_socket readDataWithTimeout:-1 tag:0];
    
    }
    
    - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
    
    NSLog(@"socket did disconnect: %@", err);
    
    _isConnected = NO;
    
    if (err) { //异常断开连接
    
    } else { //正常断开
    
    }
    
    //断开连接后,关闭定时器
    
    [self stopHeartbear];
    
    }
    
    

    4.虽然在代理中监听了disconnect代理方法,但这个方法并不能保证在失去socket连接后一定会被执行。所以还需要其他方法来确保客户端正在连接服务器。

    
    - (void)socketDidDisconnect:(GCDAsyncSocket*)sock withError:(NSError*)err
    
    

    通常的做法就是,定时向服务器发送长连接指令(具体的指令由服务器指定),如果一段时间内没有收到服务器的返回消息,GCDAsyncSocket会得到失去连接的消息,会之行上面的失去连接的代理方法。

    
    // GCD定时器
    
    - (void)GCDTimer {
    
    NSTimeInterval period = 15.0; //设置时间间隔
    
    _hearTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _rwQueue);;
    
    dispatch_source_set_timer(_hearTimer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0);
    
    dispatch_source_set_event_handler(_hearTimer, ^{
    
    [self heartbeatCheckSocket];
    
    });
    
    }
    
    - (void)heartbeatCheckSocket {
    
    //假设与服务器约定的指令为socket_connect_check
    
    NSData *checkMsg = [@"socket_connect_check" dataUsingEncoding:NSUTF8StringEncoding];
    
    [self.socket writeData:checkMsg withTimeout:-1 tag:1];
    
    NSLog(@"heartbeat...");
    
    }
    
    - (void)startHeartbeat {
    
    dispatch_resume(_hearTimer);
    
    }
    
    - (void)stopHeartbear {
    
    dispatch_suspend(_hearTimer);
    
    }
    
    

    在连接到服务器的代理中开启心跳。当已经监听到连接断开,那么心跳就没有什么意义了,挂起就可以了。

    三、源码:

    客户端

    服务端

    相关文章

      网友评论

          本文标题:ios 即时通讯 socket 和 CocoaAsyncSock

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