GCDAsyncSocket 简单使用

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

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

  • CocoaAsyncSocket为Mac和iOS提供了易于使用且强大的异步通信库。CocoaAsyncSocket是支持tcp和udp的,利用它可以轻松实现建立连接、断开连接、发送socket业务请求、重连这四个基本功能。

一、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;
  • aDelegate就是socket的代理 dq是delegate的线程

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

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

  • sq是socket的线程,这个是可选的设置,如果你写null,GCDAsyncSocket内部会帮你创建一个它自己的socket线程,如果你要自己提供一个socket线程的话,千万不要提供一个并发线程,在频繁socket通信过程中,可能会阻塞掉,个人建议是不用创建

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;
  • host是主机地址,port是端口号

如果建连成功之后,会收到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];
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 转载:http://www.cocoachina.com/ios/20170615/19529.html 参考:h...
    F麦子阅读 3,983评论 3 2
  • 前言 本文会用实例的方式,将iOS各种IM的方案都简单的实现一遍。并且提供一些选型、实现细节以及优化的建议。 注:...
    maTianHong阅读 2,342评论 4 12
  • 本人有若干成套学习视频, 可试看! 可试看! 可试看, 重要的事情说三遍 包含Java, 数据结构与算法, iOS...
    小冰山口阅读 1,032评论 5 4
  • 一间酒吧 一个下午 一杯酒 一个人 忘记了另一个人…
    冶一阅读 170评论 0 2
  • 今早在讀企業文化時「到2020年培養出系統300位訓練導師」,突然心生嚮往,從前我從來沒覺得這句話和我有什麼關係,...
    Lucie陸陸阅读 124评论 0 2