iOS 開源庫源碼分析之GCDAsyncSocket

iOS 開源庫源碼分析之GCDAsyncSocket

GCDAsyncSocket

連接

  • 預連接,檢查delegate,delegateQueue,是否已經連接,支持IPv4/IPv6,
  • lookupHost,獲取server地址
  • lookup,建立連接
  • connectWithAddress4:address6:,通過lookup調用
  • 調用createSocket,創建客戶端socket
  • connectSocket,調用connect()函數連接服務器

服務器開始監聽(acceptOnPort)

這裡最終調用的是acceptOnInterface方法

  • 通過getInterfaceAddress4:address6:獲取本地的IPv4/IPv5 地址以及端口
  • 創建IPv4/IPv6的socket,並且進行bind本機地址
  • 調用listen函數進行監聽這個socket
  • 獲取客戶端連接回調,創一個DISPATCH_SOURCE_TYPE_READ的source並且啟動,然後設置event hanlder,這裡面會調用doAccept方法,在這個方法,通過accept()函數獲取客戶端新連接的socket,然後會異步到delegateQueue,調用代理方法socket:didAcceptNewSocket:來獲取新的連接,

發送數據

發送數據的對象是GCDAsyncWritePacket

發送的方法是writeData:

  • 異步到socketQueue,把GCDAsyncWritePacket對象加入到writeQueue
  • 調用maybeDequeueWrite,取出writeQueue第一個數據為currentWrite
  • 寫入數據有三種方式
  • 通過write()函數正常寫入
  • TLS方式,通過CFWriteStreamWrite寫入
  • SSL方式,通過SSLWrite寫入,在這裡面如果遇到了I/O阻塞(errSSLWouldBlock),會把數據放入緩衝區進行再次寫入

TCP粘包

TCP是面向連接的傳輸層協議,TCP連接只能是一對一的,它提供可靠的交付服務,也就是說,通過TCP連接傳送的數據,無差錯、不丟失、不重複、並且按序到達,TCP提供全雙工通信,TCP是面向字節流的,無消息保護邊界,TCP把應用程序交下來的數據塊看成無結構的字節流,TCP不保證接收方應用程序收到的數據塊和發送方應用程序所發出的數據塊具有對應的大小關係(例如,發送方應用程序交給發送方TCP共10個數據塊,但接收方的TCP可能只用4個數據塊就把收到的字節流交付給了上層的應用程序,但接收方應用程序收到的字節流必須和發送方應用程序發出的字節流完全一樣)。

基於上面TCP所以會有粘包的問題,而UDP基於數據包則不會出現粘包。但是由於UDP傳輸不可靠,會出現丟包,無序的問題,而TCP則不會有。

解決思路:

  • 包尾加上\r\n分隔符,作為消息保護邊界,缺點是內容中有\r\n會有誤判
  • 使用包頭,包頭內加上包體長度

包頭可以是一個字典,裡面加上包體的長度的key/value即可。

在包頭後面加上[GCDAsyncSocket CRLFData]就是\r\n了,作為分隔符,HTTP就是使用\r\n作為包頭分隔符的。

在服務端可以在接受連接的時候

[newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:110];

來讀取一個包頭。

獲取到包頭後,拿到大小,通過下面的方法就可以拿到包體了。

[sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1

tag:110];

作為ios開發者學習環境很重要,這是小編 iOS學習交流群 786735421,水群勿擾 !!!水群勿擾!!!內有iOS企業案例學習資料 面試資料和 零基礎入門教程 ,歡迎各位小夥伴入群學習交流。

心跳

心跳機制是判斷客戶端與服務端雙方是否存活。

好處是:斷了之後客戶端可以重新建連,而對於服務端來說清理無效連接。

心跳客戶端和服務器都可以發起,一般來說客戶端發,一般實現步驟如下

  • 客戶端每隔一個時間發送一個包給服務器,並且設置超時時間
  • 服務器收到後回應一個包
  • 客戶端如果收到包則說明正常,超時的話則說明掛了

客戶端負載均衡

負載均衡(Load Balance)一般指服務端負載均衡,意思是將負載(工作任務,訪問請求)進行平衡、分攤到多個操作單元(服務器,組件)上進行執行。對於服務端負載均衡一般都會維護一個服務器清單,當客戶端請求到來時負載均衡服務器會從清單中選出一臺處理請求。

而客戶端負載均衡最大的區別在於服務器清單是在客戶端維護。

實現步驟:

  • 獲取服務器列表並且緩存在本地
  • 對每個服務器進行連接並且發送測速包
  • 當全部拿到測速結果(一次來回的時間等)後,找到最優的服務器進行連接

TLS

https://github.com/qinlaoban/Scoket_ssl

關於ssl的握手過程可以參考我之前寫的SSL。

可以看一下下面方法,表示開啟TLS

- (void)startTLS:(NSDictionary *)tlsSettings

裡面有很多key,比如

  • GCDAsyncSocketManuallyEvaluateTrust,如果是YES,表示需要手動驗證證書,在socket:didReceiveTrust:completionHandler: delegate方法裡面去實現,可以參考 stackoverflow 的實現
  • GCDAsyncSocketUseCFStreamForTLS,僅限iOS,使用CFStream的TLS

startTLS中首先根據tlsSettings構造GCDAsyncSpecialPacket,把packet加入讀寫隊列,這裡最終調用的是maybeStartTLS方法,如果不是使用SecureTransport,則調用cf_startTLS方法,如果使用SecureTransport,則調用ssl_startTLS方法。

在ssl_startTLS中

  • 初始化SSL上下文
  • 設置SSL讀寫的回調
  • 建立SSL連接
  • 當然還有一些其他可選項,比如如果是GCDAsyncSocketManuallyEvaluateTrust的話,會調用SSLSetSessionOption()函數
  • 之後調用ssl_continueSSLHandshake開始握手,調用SSLHandshake()函數進行握手


分享到:


相關文章: