國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法

國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
WebSocket協議是基於TCP的一種新的網絡協議。它實現了瀏覽器與服務器全雙工(full-duplex)通信——可以通俗的解釋爲服務器主動發送信息給客戶端。

區別於MQTT、XMPP等聊天的應用層協議,它是一個傳輸通訊協議。它有着自己一套連接握手,以及數據傳輸的規範。

而本文要講到的SRWebSocket就是iOS中使用websocket必用的一個框架,它是用Facebook提供的。

關於WebSocket起源與發展,是怎麼由:輪詢、長輪詢、再到websocket的,可以看看冰霜這篇文章:

微信,QQ這類IM app怎麼做——談談Websocket

大家可以關注小編的羣:656315826 可以獲取相關視頻教程和源碼哦
二. SRWebSocket的對外的業務流程:

首先貼一段SRWebSocket的API調用代碼:國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
要簡單使用起來,總共就4行代碼,並且實現你需要的代理即可,整個業務邏輯非常簡潔。

但是就這麼幾個對外的方法,SRWebSocket.m裏面用了2000行代碼來進行封裝,那麼它到底做了什麼?我們接着往下看:

三. SRWebSocket的初始化以及連接流程:

1首先我們初始化:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
會初始化一些屬性:

包括對schem進行斷言,只支持ws/wss/http/https四種。

當前socket狀態,是正在連接,還是已連接、斷開等等。

初始化工作隊列,以及流回調線程等等。

初始化讀寫緩衝區:_readBuffer、_outputBuffer。

  1. 輸入輸出流的創建及綁定:
    國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
    在這裏,我們根據傳進來的url,類似ws://localhost:80,進行輸入輸出流CFStream的創建及綁定。

[圖片上傳失敗...(image-677c80-1534401345579)]

到這裏,初始化工作就完成了,接着我們調用了open開始建立連接:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法

open方法定義了一個超時,如果超時了還在SR_CONNECTING,則報錯,並且斷開連接,清除一些已經初始化好的參數。
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法

開始連接主要是給輸入輸出流綁定了一個runloop,說到這個runloop,不得不提一下SRWebSocket線程的問題:

一開始初始化我們提過SRWebSocket有一個工作隊列:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
這個工作隊列是串行的,所有和控制有關的操作,除了一開始初始化和open操作外,所有後續的回調操作,數據寫入與讀取,出錯連接斷開,清除一些參數等等這些操作,全部是在這個_workQueue中進行的。

而這裏的runloop:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
是新創建了一個NSThread的線程,然後起了一個runloop,這個是以單例的形式創建的,所以networkThread作爲屬性是一直存在的,而且起了一個runloop,這個runloop沒有調用過退出的邏輯,所以這個networkThread是個常駐線程,即使socket連接斷開,即使SRWebSocket對象銷燬,這個常駐線程仍然存在。

可能很多朋友會覺得,那我都不用websocket了,什麼都置空了,憑什麼還有一個常駐線程,不停的空轉,給內存和CPU造成一定開銷呢?

樓主的理解是,作者這麼做,可能考慮的是既然用戶有長連接的需求,肯定斷開連接甚至清空websocket對象只是一時的選擇,肯定是很快會重新初始化並且重連的,這樣這個常駐線程就可以得到複用,省去了重複創建,以及獲取runloop等開銷。

那麼SRWebSocket總共就有一個串行的_workQueue和一個常駐線程networkThread,前者用來控制連接,後者用來註冊輸入輸出流,那麼爲什麼這些操作不在一個常駐線程中去做呢?

我覺得這裏就涉及一個線程的任務調度問題了,試想,如果控制邏輯和輸入輸出流的回調都是在同一個線程,對於輸入輸出流來說,回調是會非常頻繁的,首先寫_outputStream是在當前流NSStreamEventHasSpaceAvailable還有空間可寫的時候,一直會回調,而讀_inputStream則在有數據到達時候,也會不停的回調,試想如果這時候,控制邏輯需要做什麼處理,是不是會有很大的延遲?它需要等到排在它前面插入線程中的任務調度完畢,才能輪得到這些控制邏輯的執行。所以在這裏,把控制邏輯放在一個串行隊列,而數據流的回調放在一個常駐線程,兩個線程不會互相污染,各司其職。

接着主流程往下走,我們open了輸入輸出流後,就調用到了流的代理方法了:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
這裏如果我們一開始初始化的url是 wss/https,會做SSL認證,認證流程基本和樓主之前講的CocoaAsyncSocket,這裏就不贅述了,認證失敗,會斷開連接,

最終SSL或者非SSL都會走到這麼一個方法:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
這個方法有點長,大家都知道,WebSocket建立連接前,都會以http請求作爲握手的方式,這個方法就是在構造http的請求頭。

我們來看看RFC規範的標準客戶端請求頭:

GET /chat HTTP/1.1

Host: server.example.com

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

Origin: http://example.com

Sec-WebSocket-Protocol: chat, superchat

Sec-WebSocket-Version: 13

標準的服務端響應頭:

HTTP/1.1 101 Switching Protocols

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Sec-WebSocket-Protocol: chat

這裏需要講的是這Sec-WebSocket-Key和Sec-WebSocket-Accept這一對值,前者是我們客戶端自己生成一個16字節的隨機data,然後經過base64轉碼後的一個隨機字符串。

大家可以關注小編的羣:656315826 可以獲取相關視頻教程和源碼哦

而後者則是服務端返回回來的,我們需要用一開始的Sec-WebSocket-Key與服務端返回的Sec-WebSocket-Accept進行校驗:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
服務端這個Accept會用這麼一個字符串拼接加密:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
這個字符串是RFC規範定死的,至於爲什麼是這麼一串,樓主也不知所以然。

我們發出這個http請求後,得到服務端的響應頭,去按照服務端的方式加密Sec-WebSocket-Key,判斷與Sec-WebSocket-Accept是否相同,相同則表明握手成功,否則失敗處理。
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
至此都成功的話,一個WebSocket連接建立完畢。

四. 接着來講講數據的讀和寫:

當建立連接成功後,就會循環調用這麼一個方法:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
記得樓主之前寫過一篇即時通訊下數據粘包、斷包處理實例(基於CocoaAsyncSocket),因此拋出一個問題,WebSocket需要處理數據的斷包和粘包麼?

答案是基本不需要。引用知乎上的一段回答:

RFC規範指出,WebSocket是一個message-based的協議,它可以自動將數據分片,並且自動將分片的數據組裝。

也就是說,WebSocket的RFC標準是不會產生粘包、斷包問題的。無需應用層開發人員關心緩存以及手工組裝message。

然而理想與現實的不一致:RFC規範與實現的不一致,現實當中有幾個問題:

每個message可以是一個或多個分片。message不記錄長度,分片才記錄長度。

message最大的長度可以達到 9,223,372,036,854,775,807 字節,是由於Payload的數據長度有63bit的限制。

很多WebSocket的實現其實並不按照標準的RFC實現完全,很多僅僅實現了50%就拿來用了。這就導致了,在WebSocket實現上的最大長度很難達到這個大小,於是,很多API的實現上是會有限制的,可能會限制你的發送的長度,也可能會把過長的數據直接以流式發送。

而SRWebSocket中實現的方式上徹底解決了數據粘包,斷包的可能。

數據是通過CFStream流的方式回調回來的,每次拿到流數據,都是先放在數據緩衝區中,然後去讀當前消息幀的頭部,得到當前數據包的大小,然後再去創建消費者對象consumer,去讀取緩衝區指定數據包大小的內容,讀完纔會回調給我們上層用戶,所以,我們如果用SRWebSocket完全不需要考慮數據斷包、粘包的問題,每次到達的數據,都是一條完整的數據。

接着我們大概來看看這個流程:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
上面這個方法就是一個讀取頭部的方法,之前我寫過斷包粘包的文章就是用一個\r\n來分割頭部和正文,這裏是用了\r\n\r\n,每次讀到這個標識符爲止,就是讀取了一個完整的WebSocket的消息幀頭部。

這裏我們先需要說清楚的是,數據一到達,就在stream的代理中回調中,寫到了我們的_readBuffer緩衝區中去了:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
接着我們來看添加消費者這個方法:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
其實就是添加了一個stream_scanner類型的對象,到我們的_consumers數組中去了,以後我們讀取數據,都會先取出_consumers中的消費者,要讀取多少,就給你從_readBuffer裏去讀多少數據。
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
這個方法就是做這麼一件事,根據consumer的要求,循環去_readBuffer中讀取數據。

至於讀的過程,大家可以自己去看下吧,樓主提供的源碼註釋裏已經寫的很清楚了,有點略長,這裏就不放代碼了,方法如下:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
至此我們講了握手的頭部信息的讀取,與判斷是否握手成功,然後數據到達是怎麼從stream到_readBuffer中去的,並且簡單介紹了_pumpScanner會根據消費者對象,去從_readBuffer中讀取數據,讀取完成並且回調consumer的handler

現在我們來講講一個數據從頭部開始,到內容的讀取過程:

每次我們讀取新的一幀數據,都會調用這麼個方法:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
會清空上一幀的一些信息,然後開始當前幀的讀取,我們來簡單看看一個WebSocket消息幀裏包含什麼:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
就是這麼一張圖,大家應該經常見,這個圖是RFC的標準規範。簡單的說明下這些標識着什麼:

FIN 1bit 表示信息的最後一幀,flag,也就是標記符

RSV 1-3 1bit each 以後備用的 默認都爲 0

Opcode 4bit 幀類型,稍後細說

Mask 1bit 掩碼,是否加密數據,默認必須置爲1

Payload 7bit 數據的長度 (2^7 -1 最大到127)

Masking-key 1 or 4 bit 掩碼 //用來編碼數據

Payload data (x + y) bytes 數據 //

Extension data x bytes 擴展數據

Application data y bytes 程序數據

更詳細的可以看看:WebSocket數據幀規範

接着我們讀取消息,會用到其中的一些字段,包括FIN、 MASK、Payload len等等。

然後來看看這個讀取當前消息幀的方法:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
這個方法是先去讀取了當前消息幀的前2個字節,大概就是這麼一部分:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
然後會去對頭部信息進行一些判斷,但是最主要的還是去獲取payload,也就是真實數據的長度,然後還是調用:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
去讀取真實數據的長度,然後會在下面這個方法中判斷當前幀的數據是否讀取完成:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
如果沒讀取完成,會繼續去讀取,否則就調用完成的方法,在完成的方法中會回調暴露給我們的代理:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
並且繼續去讀下一幀的數據
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
整個數據讀取過程就完成了。

接着我們來看看數據的寫:
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
國內作戰指揮學院畢業的程序員解析:美國國防、銀行和支付的加密算法
基本上非常簡單,區別於之前CocoaAsyncSocket,讀和寫都沒多少代碼,原因是因爲CocoaAsyncSocket整篇都用的是CFStream等相對上層的API。

SRWebSocket全篇代碼註釋地址:SRWebSocket註釋。

大家可以關注小編的羣:656315826 可以獲取相關視頻教程和源碼哦

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章