iOS 长连接-GCDAsyncSocket 使用

本篇文章仅介绍本人在公司项目中使用GCDAsyncSocket建立socket连接中使用的一些方法和心得体会。对GCDAsyncSocket方法不熟悉的同学可以先查看GCDAsyncSocket API简介这篇文章。

初始化

initWithDelegate:delegateQueue:

初始化,设置委托和委托队列。
内部会在初始化时保存传入的委托对象及委托对垒,创建套接字队列,初始化socket4FD(本地IPV4Socket)、socket6FD(本地IPV6Socket)、socketUN(unix域的套接字)、socketUrl(unix域 服务端 url)、stateIndex(状态Index)、readQueue(读队列)、currentRead(当前读入数据包)、writeQueue(写队列)、currentWrite(当前写入数据包)、preBuffer(公用缓冲区)、alternateAddressDelay(连接备选服务端地址的延时 ,另一个IPV4或IPV6,默认0.3S)等参数。

初始化完成,开启连接(如果是服务端,就需要去bind端口,并且accept,等待客户端的连接。)

连接

connectToHost:onPort:withTimeout:error:

1.host及端口校验
2.代理队列校验、是否开始连接(kSocketStarted)、是否支持IPV4 IPV6、清空读写队列
3.标记Socket为开始连接(kSocketStarted)进行一下异步连接并开启连接超时设置
4.根据host port,去获取server地址信息(异步,DNS解析,NSData类型)。
5.创建server地址连接(耗时,异步,完成后回调)
根据第4步中地址创建socket(返回socket的文件描述符,int类型,scoket其实就是Int类型)
->Socket绑定本机地址(本项目无特定端口,此步骤在连接服务器步骤connect自动完成端口绑定)
->连接服务器

成功:关闭无用socket->添加‘已连接’连接状态(kConnected)->关闭连接超时设置->创建读写流及读写回调注册
->回调成功代理(socket:didConnectToHost:port:返回数据为根据socket的文件描述符获取的服务器IP及端口)
->本机socket设置相关参数->开启读写

失败:关闭当前socket并置空->清空读写队列->退出读写监听->标记Socket连接状态为0->回调断开代理(socketDidDisconnect:withError:,error不为空,且socket已开始连接)

PS:当然连接过程不仅上述过程(如:连接超时设置等)且具体步骤未详细描述,本处列出的为本项目连接过程或需引起注意过程。

注意:连接之前判断当前socket连接状态,避免重复连接。

  • 个人疑点

断开就清空读写队列???

断开

disconnect
立即断开连接(同步)。所有挂起的读取或写入操作都将被丢弃。

_localAsyncSocket.delegate = nil;
[_localAsyncSocket disconnect];
_localAsyncSocket = nil;

readDataWithTimeout:tag:

  • 个人思考

由于项目中长连接模块有心跳业务且心跳有返回值,所以通过设置超时时间来间接判断长连接通道通畅性。

writeData: withTimeout: tag:

  • 个人思考

GCDAsyncSocket的更强大功能之一是其排队的体系结构。这使您可以在方便时控制套接字,而不是在套接字告知您已准备就绪时对其进行控制。 ——引用至Reference_GCDAsyncSocket

虽然GCDAsyncSocket认为其读写排队的体系结构是一项很强大的功能之一,然而本人却并不这样认为,而且直接使用其读写队列还可能引起一系列的问题。例如IM类项目,必须要保证每条消息的成功发送,此时如果我们直接使用writeData: withTimeout: tag:方法,如果当前网络不佳导致socket断开(socketDidDisconnect:withError:),此时会清空未处理的读写队列,这样必然会导致消息的丢失。

个人总感觉GCDAsyncSocket开发者在架构中未考虑重连的情况。

连接判断

isDisconnected

GCDAsyncSocket内部判断是否标记连接状态为kSocketStarted:已标记,返回NO,表示未断开;未标记,返回YES,表示已断开。
项目中在开启连接(connectToHost:onPort:withTimeout:error:)之前调用此方法判断是否已开启连接,做容错处理。
不推荐外部使用。

isConnected

GCDAsyncSocket内部判断是否标记连接状态为kConnected:已标记,返回YES,表示已成功连接;未标记,返回NO,表示未成功连接。
由于项目要求实时性比较高,所以在发送之前会使用本方法做通道通畅性的判断:YES,发送;NO,不发送,根据业务做相应处理。
推荐外部使用,如项目中连接状态便是使用此方法判断。

tag

  • 个人思考

通过上面读写方法的介绍可知在每个读写方法中都会传入一个tag值,传递的tag值会在代理中回传给使用者。因此可以在写入(writeData: withTimeout: tag:)时为消息标记不同的tag值,然后通过代理方法(socket: didWriteDataWithTag:)中返回的tag值来判断消息完成写入。

有时我们会写入请求类消息,服务器收到请求后会返回某些信息,虽然我们可以在写入完成的代理方法(socket: didWriteDataWithTag:)中设置读消息(readDataWithTimeout:tag)相同的tag值,但是依然不能通过tag值来判断读入的消息为写入消息的返回数据,因为此时服务器很可能会主动推一条与本次写入的请求类消息毫无关联的信息。

重连

我们在代理回调的断开(socketDidDisconnect:withError:)方法中判断本次断开返回的error值是否为空,如果不为空则表示本次断开为异常断开,开启一次延时重连。注意在主动断开连接的方法中要取消本次延时重连。

ping

我们在异常断开时会调用RealReachability进行ping操作,主要用于判断异常断开时是否由网路异常引起,以及网路可用时是否能够正常重连。

  • 具体实现
    设置hostForPing值为长连接地址,hostForCheck为"www.apple.com",同时根据需求对RealReachability库进行了部分修改,使其在ping结果的block中返回该次ping是否成功,是否使用VPN,网络状况,ping地址。返回结果如下:

    1.是否有可用网络
    否:返回ping失败,未使用VPN,网络状态,hostForPing
    2.是否使用VPN
    是:返回ping失败,使用VPN,网络状态,hostForPing
    3.ping hostForPing
    成功:返回ping成功,未使用VPN,网络状态,hostForPing
    失败:进行4步骤
    4.是否使用VPN
    是:返回ping失败,使用VPN,网络状态,hostForCheck
    5.ping hostForCheck(延时1S)
    返回ping结果,未使用VPN,网络状态,hostForCheck

通过对比对应的ping结果及当时的网络状况对比可以得出该次断开是否有网络引起。

同时记录本次为第几次连接(从发起到连接成功算一次,在连接成功的方法中进行+1操作)及本次连接失败后重连次数(需在连接成功的方法中将该参数置0),通过两个参数值与网络状态对比,可以判断网路可用时是否能够正常重连。

通过日志分析得出:

1.长连接断开后,ping失败,无可用网络,占比80%左右。
2.长连接断开后,ping成功(后续重连成功),占比20%左右。
3.长连接断开后,ping成功,但是持续异常断开,偶现。
异常断开code:60,61。均为电信网络,安卓用户也存在类似情况,怀疑网络服务引起。


参考资料

感谢涂耀辉Cooci_和谐学习_不急不躁两位大神,两位大神在其博客上有关于GCDAsyncSocket连接、读写、断开、粘包等功能详细的文档(下面的链接)介绍。
强烈建议各位同学在着手开发之前先认真阅读两位大神的文档,对GCDAsyncSocket有整体的了解,这样可以充分利用GCDAsyncSocket已有的功能,避免开发过程中遇到疑难的问题,同时也方便后续bug的修改。而本人也是这样做的。

iOS即时通讯进阶 - CocoaAsyncSocket源码解析(Connect篇)
iOS即时通讯进阶 - CocoaAsyncSocket源码解析(Connect篇终)
iOS即时通讯下数据粘包、断包处理实例(基于CocoaAsyncSocket)
iOS即时通讯进阶 - CocoaAsyncSocket源码解析(Read篇)
iOS即时通讯进阶 - CocoaAsyncSocket源码解析(Read篇终)
CocoaAsyncSocket源码分析---Write
CocoaAsyncSocket源码解析---终
CocoaAsyncSocket源码注释(2017)

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