IOS面试专题

GCDAsyncSocket 简单使用

2018-08-25  本文已影响1167人  Boothlee

注册了这么久简书账号,今天终于决定把自己的总结发出来。第一篇文章诞生了!

项目中monitor数据上报,消息推送均使用了socket长连接,技术上使用GCDAsyncSocket 并做了二次封装。

一、GCDAsyncSocket 总结

在Podfile文件中,只要加上这句话就可以导入了

pod 'CocoaAsyncSocket'

1)首先初始化socket 源码提供了四种初始化方法

- (instancetype)init;
- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq;
- (instancetype)initWithDelegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq;
- (instancetype)initWithDelegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq;

You MUST set a delegate AND delegate dispatch queue before attempting to use the socket, or you will get an error

这里的delegate和dq是必须要有的。

If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue.
If you choose to provide a socket queue, the socket queue must not be a concurrent queue.

2)初始化socket之后,需要跟服务器建立连接

- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr;

如果建连成功之后,会收到socket成功的回调

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;

如果失败了,会受到以下回调

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

3)发送数据

[self.socket writeData:data withTimeout:-1 tag:0];

发送数据的回调

- (void)socket:(GCDAsyncSocket*)sock didWriteDataWithTag:(long)tag;

4)读取数据回调

- (void)socket:(GCDAsyncSocket*)sock didReadData:(NSData*)data withTag:(long)tag;

5)断开连接、重连

[self.socket disconnect];
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
//这里可以做重连操作
}

二.采坑攻略

1)主动读取消息

在发送消息后,需要主动调取didReadDataWithTimeOut方法读取消息
,这样才能收到你发出请求后从服务器那边收到的数据

- (void)socket:(GCDAsyncSocket*)sock didWriteDataWithTag:(long)tag
{
    [self.socket readDataWithTimeout:-1 tag:tag];
}

2)tag 参数的理解

tag 参数,乍一看可能会以为在writeData到readData一次传输过程中保持一致。看似结果是这样,但是tag参数并没有加在数据传输中。
tag 是为了在回调方法中匹配发起调用的方法的,不会加在传输数据中

调用write方法,收到didWriteData 回调 调用writeDataWithTimeOut 读取数据。收到消息后,会回调didReadData的delegate方法。这是一次数据发送,在接受服务端回应的过程。

- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag;

writeData方法中的tag 和 DidWriteData代理回调中的tag是对应的。源码中tag的传递是包含在当前写的数据包 GCDAsyncWritePacket currentWrite 中。

同理

- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag;

readData 方法中的tag 和 readDataWithTimeout 代理回调中的tag是一致的
tag 传递包含在GCDAsyncReadPacket * currentRead 数据包中。

需要注意:根据tag做消息回执的标识,可能会出现错乱的问题

以read为例分析:

- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;

上面的方法会生成一个数据类:AsyncReadPacket,此类中包含tag,并把此对象放入数组 readQueue中。
(先进先出,比如read了了三次,分别为1,2,3,那么回调的tag会依次是1,2,3)
在CFStream中的回调方法中,会取readQueue最新的一个,在回调方法中取得tag,并将tag传给回调方法:

- (void)onSocket:(AsyncSocket *)sock didReadData:(long)tag;

这样看似tag 传递了下去。但是看下面的读取数据部分源码:

//用偏移量 maxLength 读取数据
- (void)readDataWithTimeout:(NSTimeInterval)timeout
                     buffer:(NSMutableData *)buffer
               bufferOffset:(NSUInteger)offset
                  maxLength:(NSUInteger)length
                        tag:(long)tag
{
    if (offset > [buffer length]) {
        LogWarn(@"Cannot read: offset > [buffer length]");
        return;
    }
    
    GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
                                                              startOffset:offset
                                                                maxLength:length
                                                                  timeout:timeout
                                                               readLength:0
                                                               terminator:nil
                                                                      tag:tag];
    
    dispatch_async(socketQueue, ^{ @autoreleasepool {
        
        LogTrace();
        
        if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
        {
            //往读的队列添加任务,任务是包的形式
            [readQueue addObject:packet];
            [self maybeDequeueRead];
        }
    }});
    
    // Do not rely on the block being run in order to release the packet,
    // as the queue might get released without the block completing.
}

读取数据时的packet实际是是根据readDataWithTimeOut方法传进来的tag重新alloc出来的消息,假如服务端回执消息异常,相同tag对应的消息回执就会不匹配。这一点需要注意。实际业务中,上报消息后会根据服务端的回执消息做逻辑处理,倘若回执消息丢失,根据tag匹配到消息回执就会造成错乱。

官方解释

In addition to this you've probably noticed the tag parameter. The tag you pass during the read/write operation is passed back to you via the delegate method once the read/write operation completes. It does not get sent over the socket or read from the socket. It is designed to help simplify the code in your delegate method. For example, your delegate method might look like this:

#define TAG_WELCOME 10
#define TAG_CAPABILITIES 11
#define TAG_MSG 12

... 

- (void)socket:(AsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
    if (tag == TAG_WELCOME)
    {
        // Ignore welcome message
    }
    else if (tag == TAG_CAPABILITIES)
    {
        [self processCapabilities:data];
    }
    else if (tag == TAG_MSG)
    {
        [self processMessage:data];
    }
}
上一篇下一篇

猜你喜欢

热点阅读