基于GCDAsyncSocket的连接

作者: FGNeverMore | 来源:发表于2016-06-25 13:15 被阅读729次

    本篇文章主要针对聊天室的Socket连接,基于GCDAsyncSocket实现.对不太了解GCDAsyncSocket的同学请自行咨询度娘.当然一个聊天室的实现并不只有一个简单的Socket,后续还有聊天室的管理,一时整理不好头绪,之后会和大家分享一下思路.

    本类只负责了Socket的连接,记录连接状态,收到数据,发送数据,具体的解析数据等在其他的类里.依旧是废话不多说,撸代码,看思路.

    SocketLinker.h文件
    //首先是定义枚举,记录连接的状态
    typedef NS_ENUM(NSUInteger, LINKSTATE)
    {
        LINKSTATE_UNLINK   = 0, // 未连接
        LINKSTATE_LINKING  = 1, // 连接中
        LINKSTATE_LINKED   = 2, // 连接成功了
        LINKSTATE_LOGOUT   = 3 // 退出登录(退出软件用户时的情况,不需要重连)
    };
    
    //设置代理方法
    @protocol SocketLinkerDelegate <NSObject>
    //连接成功
    - (void)socketDidConnectToHost:(NSString *)host port:(uint16_t)port;
    //连接失败的代理,外界操作处理,比如停止发送心跳包,申请重连
    - (void)socketDidDisconnectWithError:(NSError *)error;
    //读取Socket数据
    - (void)socketDidResponse:(NSData *)data;
    @end
    
    @protocol ConnectStateDelegate <NSObject>
    //连接状态改变
    - (void)connectDidChangeConnectState:(LINKSTATE)newState;
    @end
    
    @interface SocketLinker : NSObject
    //连接状态,LINKSTATE
    @property (nonatomic,assign) LINKSTATE linkState;
    @property (nonatomic, weak) id<SocketLinkerDelegate> delegate;
    @property (nonatomic, weak) id <ConnectStateDelegate> connectStateDelegate;
    //根据服务器主机和端口初始化
    - (id)initWithHost:(NSString *)host port:(uint16_t)port;
    //连接
    - (void)connect;
    //断开连接
    - (void)disconnect;
    // 发送数据包
    - (void)sendMsgPacket:(NSData *)packet;
    // 读取消息包
    - (void)readMsgPacket;
    //判断Socket连接状态供外界调用
    - (BOOL)isSocketConnected;
    @end
    

    以上就是.h中的所有代码了,之后在.m中

    @interface SocketLinker()<GCDAsyncSocketDelegate>//遵循GCDAsyncSocketDelegate
    //两个线程锁
    @property (nonatomic, strong) NSObject *linkLock;
    @property (nonatomic, strong) NSObject *lockObject;
    //socket连接对象
    @property (atomic, strong) GCDAsyncSocket *asyncSocket;
    //记录服务器
    @property (nonatomic, strong) NSString *host;
    //记录端口
    @property (nonatomic, assign) uint16_t port;
    @end
    

    初始化

    - (id)initWithHost:(NSString*)host port:(uint16_t)port
    {
        self = [super init];
        if (self)
        {
            self.linkLock = [[NSObject alloc] init];
            self.lockObject = [[NSObject alloc]init];
            self.host = host;
            self.port = port;
            /**
             *  LQ~ 初始状态为未连接
             */
            self.linkState = LINKSTATE_UNLINK;
        }
        return self;
    }
    

    连接

    - (void)connect
    {
        /**
         * LQ~ 由于很可能同时多次进行socket连接,在这里使用线程锁,确保只进行一次连接
         */
        @synchronized (self.linkLock){
            if (LINKSTATE_LINKING != self.linkState)
            {
                // 把当前状态改为链接建立中,这里我们让所有的回调执行都发生在主线程的queue里,当然我们可以传一个专用的queue
                self.asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
                NSError *error = nil;
                /*LQ~ 连接服务器 */
               //CONNECT_TIMEOUT是一个宏定义,定义超时的时间,我用的是30秒
                if (![self.asyncSocket connectToHost:self.host onPort:self.port withTimeout:CONNECT_TIMEOUT error:&error])
                {
                    /*LQ~ 如果socktet连接失败,返回NO,记录连接为LINKSTATE_UNLINK */
                    self.linkState = LINKSTATE_UNLINK;
                }
                if (error != nil)
                {  
                    //当有错误的时候抛出异常错误信息
                    @throw [NSException exceptionWithName:@"GCDAsyncSocket" reason:[error localizedDescription] userInfo:nil];
                }
                //当socktet连接成功的时候,记录连接的状态,连接中
                self.linkState = LINKSTATE_LINKING;
            }
        }
    }
    

    主动的断开连接

    - (void)disconnect
    {
        @synchronized (self.linkLock)
        {
            if (self.asyncSocket != nil)
            {
                [self.asyncSocket disconnect];
            }
            self.linkState = LINKSTATE_LOGOUT;
        }
    }
    

    连接成功后GCDAsyncSocket的代理回调

    //连接成功
    - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
    {  
        //记录连接成功状态
        self.linkState = LINKSTATE_LINKED;
        if (self.delegate && [self.delegate respondsToSelector:@selector(socketDidConnectToHost:port:)])
        {  
            //代理执行连接成功后的操作
            [self.delegate socketDidConnectToHost:host port:port];
        }
        //对读数据进行设置
        [self readMsgPacket];
    }
    

    对读取数据进行设置

    - (void)readMsgPacket
    {
        if (self.linkState == LINKSTATE_LINKED && self.asyncSocket.isConnected == YES)
        {  
            //当确认连接成功后,就可以开始读服务器发送的数据了,设置超时的时间和tag值
            //告诉asyncSocket可以准备好读信息了
            [self.asyncSocket readDataWithTimeout:TIMEOUT_WRITE tag:TAG_READ_STREAM];
        }
        else
        {
           //如果连接失败了,要传给外界信号,我连接出错了,其余的事自己搞定
            if (self.delegate && [self.delegate respondsToSelector:@selector(socketDidDisconnectWithError:)])
            {
                [self.delegate socketDidDisconnectWithError:nil];
            }
        }
    }
    

    当收到服务器数据后的GCDAsyncSocket的代理回调

    - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
    {
        //收到了服务器传给的数据,相对应的读数据的类来处理收到的数据
        if (self.delegate && [self.delegate respondsToSelector:@selector(socketDidResponse:)])
        {
            [self.delegate socketDidResponse:[data mutableCopy]];
        }
    }
    

    给服务器发送数据

    - (void)sendMsgPacket:(NSData *)packet
    {
        if ((packet.length && self.linkState == LINKSTATE_LINKED) && self.asyncSocket.isConnected == YES)
        {
            //确定当前Socket是连接着的,发送数据
            [self.asyncSocket writeData:packet withTimeout:TIMEOUT_WRITE tag:TAG_WRITE_STREAM];
        }
        else
        {
            if (self.delegate && [self.delegate respondsToSelector:@selector(socketDidDisconnectWithError:)])
            {
                /*LQ~ 说明连接失败,要传给外界信号,我连接出错了,其余的事自己搞定
                [self.delegate socketDidDisconnectWithError:nil];
            }
        }
       //每次发完之后都要对读取消息进行一次设置
        [self readMsgPacket];
    }
    

    写完数据之后的回调的GCDAsyncSocket的代理回调

    - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
    {
        //在这里我并没有做其他的操作,各位可以根据需要自己做相应的设置
    }
    

    查看scoket的连接状态,供外界调用查看

    - (BOOL)isSocketConnected
    {
        return self.asyncSocket.isConnected;
    }
    

    重写linkState的setter方法

    - (void)setLinkState:(LINKSTATE)linkState
    {
        _linkState = linkState;
        if (self.connectStateDelegate && [self.connectStateDelegate respondsToSelector:@selector(connectDidChangeConnectState:)])
        {
            //连接状态发生了改变,外界代理去做相应的处理
            [self.connectStateDelegate connectDidChangeConnectState:linkState];
        }
    }
    

    当socket连接断开的时候GCDAsyncSocket的代理回调

    - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
    {
        self.asyncSocket = nil;
        // 判断连接状态不是主动断开连接的时候
        if(self.linkState != LINKSTATE_LOGOUT)
        {
            self.linkState = LINKSTATE_UNLINK;
            if (self.delegate && [self.delegate respondsToSelector:@selector(socketDidDisconnectWithError:)])
            {
                /*LQ~ 说明连接失败,要传给外界信号,我连接出错了,其余的事自己搞定
                [self.delegate socketDidDisconnectWithError:err];
            }
        }
    }
    

    以上就是聊天室的Socket连接管理类了,大体思路就是:连接服务器->连接成功了告诉自己代理->准备读写数据,之间加入了对连接状态的判断,确保socket连接.
    之后会有通讯管理类的相关写法思路.如果各位同学发现问题,请在下方留言指点,相互帮助提高.

    相关文章

      网友评论

      • changeWong:你好,我想问一下,如果我要多个客户端同时从服务器端请求数据,在服务器端要如何处理这个,才能保证服务器端发送数据时不会乱套??(服务器端和客户端都是在移动设备上实现)
      • 鬼丶白:请问你有使用socket实现好的类似QQ的聊天demo吗?
        FGNeverMore:@soime 项目用到了简单的表情和文字混编,没有发送图片,音视频的功能~是基于socket的,我写篇文章介绍一下吧,没有dome
      • iOS猿:写得太好了 爱你:sob:

      本文标题:基于GCDAsyncSocket的连接

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