本篇文章僅介紹本人在公司項目中使用GCDAsyncSocket建立socket連接中使用的一些方法和心得體會惨篱。對GCDAsyncSocket方法不熟悉的同學可以先查看GCDAsyncSocket API簡介這篇文章荐操。
初始化
initWithDelegate:delegateQueue:
初始化,設置委托和委托隊列毒返。
內部會在初始化時保存?zhèn)魅氲奈袑ο蠹拔袑玖纯欤瑒?chuàng)建套接字隊列险领,初始化socket4FD
(本地IPV4Socket)凡辱、socket6FD
(本地IPV6Socket)戒职、socketUN
(unix域的套接字)、socketUrl
(unix域 服務端 url)透乾、stateIndex
(狀態(tài)Index)洪燥、readQueue
(讀隊列)、currentRead
(當前讀入數(shù)據(jù)包)乳乌、writeQueue
(寫隊列)蚓曼、currentWrite
(當前寫入數(shù)據(jù)包)、preBuffer
(公用緩沖區(qū))钦扭、alternateAddressDelay
(連接備選服務端地址的延時 ,另一個IPV4或IPV6床绪,默認0.3S)等參數(shù)客情。
初始化完成,開啟連接(如果是服務端癞己,就需要去bind
端口膀斋,并且accept
,等待客戶端的連接痹雅。)
連接
connectToHost:onPort:withTimeout:error:
1.host及端口校驗
2.代理隊列校驗仰担、是否開始連接(kSocketStarted)、是否支持IPV4 IPV6绩社、清空讀寫隊列
3.標記Socket為開始連接(kSocketStarted)進行一下異步連接并開啟連接超時設置
4.根據(jù)host port摔蓝,去獲取server地址信息(異步,DNS解析,NSData類型)愉耙。
5.創(chuàng)建server地址連接(耗時贮尉,異步,完成后回調)
根據(jù)第4步中地址創(chuàng)建socket(返回socket的文件描述符朴沿,int類型猜谚,scoket其實就是Int類型)
->Socket綁定本機地址(本項目無特定端口败砂,此步驟在連接服務器步驟connect自動完成端口綁定)
->連接服務器
成功:關閉無用socket->添加‘已連接’連接狀態(tài)(kConnected)->關閉連接超時設置->創(chuàng)建讀寫流及讀寫回調注冊
->回調成功代理(socket:didConnectToHost:port:返回數(shù)據(jù)為根據(jù)socket的文件描述符獲取的服務器IP及端口)
->本機socket設置相關參數(shù)->開啟讀寫
失敗:關閉當前socket并置空->清空讀寫隊列->退出讀寫監(jiān)聽->標記Socket連接狀態(tài)為0->回調斷開代理(socketDidDisconnect:withError:魏铅,error不為空昌犹,且socket已開始連接)
PS:當然連接過程不僅上述過程(如:連接超時設置等)且具體步驟未詳細描述,本處列出的為本項目連接過程或需引起注意過程览芳。
注意:連接之前判斷當前socket連接狀態(tài)斜姥,避免重復連接。
- 個人疑點
斷開就清空讀寫隊列路操?疾渴??
斷開
disconnect
立即斷開連接(同步)屯仗。所有掛起的讀取或寫入操作都將被丟棄搞坝。
_localAsyncSocket.delegate = nil;
[_localAsyncSocket disconnect];
_localAsyncSocket = nil;
讀
readDataWithTimeout:tag:
- 個人思考
由于項目中長連接模塊有心跳業(yè)務且心跳有返回值,所以通過設置超時時間來間接判斷長連接通道通暢性魁袜。
寫
writeData: withTimeout: tag:
- 個人思考
GCDAsyncSocket的更強大功能之一是其排隊的體系結構桩撮。這使您可以在方便時控制套接字,而不是在套接字告知您已準備就緒時對其進行控制峰弹。 ——引用至Reference_GCDAsyncSocket
雖然GCDAsyncSocket認為其讀寫排隊的體系結構是一項很強大的功能之一店量,然而本人卻并不這樣認為,而且直接使用其讀寫隊列還可能引起一系列的問題鞠呈。例如IM類項目融师,必須要保證每條消息的成功發(fā)送,此時如果我們直接使用writeData: withTimeout: tag:
方法蚁吝,如果當前網(wǎng)絡不佳導致socket斷開(socketDidDisconnect:withError:
)旱爆,此時會清空未處理的讀寫隊列,這樣必然會導致消息的丟失窘茁。
個人總感覺GCDAsyncSocket開發(fā)者在架構中未考慮重連的情況怀伦。
連接判斷
isDisconnected
GCDAsyncSocket內部判斷是否標記連接狀態(tài)為kSocketStarted
:已標記,返回NO山林,表示未斷開房待;未標記,返回YES驼抹,表示已斷開桑孩。
項目中在開啟連接(connectToHost:onPort:withTimeout:error:
)之前調用此方法判斷是否已開啟連接,做容錯處理框冀。
不推薦外部使用洼怔。
isConnected
GCDAsyncSocket內部判斷是否標記連接狀態(tài)為kConnected
:已標記,返回YES左驾,表示已成功連接镣隶;未標記极谊,返回NO,表示未成功連接安岂。
由于項目要求實時性比較高轻猖,所以在發(fā)送之前會使用本方法做通道通暢性的判斷:YES,發(fā)送域那;NO咙边,不發(fā)送,根據(jù)業(yè)務做相應處理次员。
推薦外部使用败许,如項目中連接狀態(tài)便是使用此方法判斷。
tag
- 個人思考
通過上面讀寫方法的介紹可知在每個讀寫方法中都會傳入一個tag
值淑蔚,傳遞的tag
值會在代理中回傳給使用者市殷。因此可以在寫入(writeData: withTimeout: tag:
)時為消息標記不同的tag
值,然后通過代理方法(socket: didWriteDataWithTag:
)中返回的tag值來判斷消息完成寫入刹衫。
有時我們會寫入請求類消息醋寝,服務器收到請求后會返回某些信息,雖然我們可以在寫入完成的代理方法(socket: didWriteDataWithTag:
)中設置讀消息(readDataWithTimeout:tag
)相同的tag
值带迟,但是依然不能通過tag
值來判斷讀入的消息為寫入消息的返回數(shù)據(jù)音羞,因為此時服務器很可能會主動推一條與本次寫入的請求類消息毫無關聯(lián)的信息。
重連
我們在代理回調的斷開(socketDidDisconnect:withError:
)方法中判斷本次斷開返回的error
值是否為空仓犬,如果不為空則表示本次斷開為異常斷開嗅绰,開啟一次延時重連。注意在主動斷開連接的方法中要取消本次延時重連搀继。
ping
我們在異常斷開時會調用RealReachability進行ping操作办陷,主要用于判斷異常斷開時是否由網(wǎng)路異常引起,以及網(wǎng)路可用時是否能夠正常重連律歼。
-
具體實現(xiàn)
設置hostForPing值為長連接地址,hostForCheck為"www.apple.com"啡专,同時根據(jù)需求對RealReachability庫進行了部分修改险毁,使其在ping結果的block中返回該次ping是否成功,是否使用VPN,網(wǎng)絡狀況们童,ping地址畔况。返回結果如下:1.是否有可用網(wǎng)絡
否:返回ping失敗,未使用VPN慧库,網(wǎng)絡狀態(tài)跷跪,hostForPing
2.是否使用VPN
是:返回ping失敗,使用VPN齐板,網(wǎng)絡狀態(tài)吵瞻,hostForPing
3.ping hostForPing
成功:返回ping成功葛菇,未使用VPN,網(wǎng)絡狀態(tài)橡羞,hostForPing
失斆型!:進行4步驟
4.是否使用VPN
是:返回ping失敗,使用VPN卿泽,網(wǎng)絡狀態(tài)莺债,hostForCheck
5.ping hostForCheck(延時1S)
返回ping結果,未使用VPN签夭,網(wǎng)絡狀態(tài)齐邦,hostForCheck
通過對比對應的ping結果及當時的網(wǎng)絡狀況對比可以得出該次斷開是否有網(wǎng)絡引起。
同時記錄本次為第幾次連接(從發(fā)起到連接成功算一次第租,在連接成功的方法中進行+1操作)及本次連接失敗后重連次數(shù)(需在連接成功的方法中將該參數(shù)置0)措拇,通過兩個參數(shù)值與網(wǎng)絡狀態(tài)對比,可以判斷網(wǎng)路可用時是否能夠正常重連煌妈。
通過日志分析得出:
1.長連接斷開后儡羔,ping失敗,無可用網(wǎng)絡璧诵,占比80%左右汰蜘。
2.長連接斷開后,ping成功(后續(xù)重連成功)之宿,占比20%左右族操。
3.長連接斷開后,ping成功比被,但是持續(xù)異常斷開色难,偶現(xiàn)。
異常斷開code:60等缀,61枷莉。均為電信網(wǎng)絡,安卓用戶也存在類似情況尺迂,懷疑網(wǎng)絡服務引起笤妙。
參考資料
感謝涂耀輝、Cooci_和諧學習_不急不躁兩位大神噪裕,兩位大神在其博客上有關于GCDAsyncSocket連接蹲盘、讀寫、斷開膳音、粘包等功能詳細的文檔(下面的鏈接)介紹召衔。
強烈建議各位同學在著手開發(fā)之前先認真閱讀兩位大神的文檔,對GCDAsyncSocket有整體的了解祭陷,這樣可以充分利用GCDAsyncSocket已有的功能苍凛,避免開發(fā)過程中遇到疑難的問題趣席,同時也方便后續(xù)bug的修改。而本人也是這樣做的毫深。
iOS即時通訊進階 - CocoaAsyncSocket源碼解析(Connect篇)
iOS即時通訊進階 - CocoaAsyncSocket源碼解析(Connect篇終)
iOS即時通訊下數(shù)據(jù)粘包吩坝、斷包處理實例(基于CocoaAsyncSocket)
iOS即時通訊進階 - CocoaAsyncSocket源碼解析(Read篇)
iOS即時通訊進階 - CocoaAsyncSocket源碼解析(Read篇終)
CocoaAsyncSocket源碼分析---Write
CocoaAsyncSocket源碼解析---終
CocoaAsyncSocket源碼注釋(2017)