网络通信即时通讯技术socket

基于GCDAsyncSocket的连接

2016-06-25  本文已影响729人  FGNeverMore

本篇文章主要针对聊天室的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连接.
之后会有通讯管理类的相关写法思路.如果各位同学发现问题,请在下方留言指点,相互帮助提高.

上一篇下一篇

猜你喜欢

热点阅读