iOS 今天咱們來聊一聊WebSocket

前言:
前段時間,在公司的項目中用到了WebSocket,當時沒有時間好好整理。
最近,趁着有時間,就好好梳理了一下WebSocket的相關知識。
html


本篇將介紹如下內容:
一、什麼是WebSocket
二、WebSocket使用場景
三、WebSocket底層原理(協議)
四、iOSWebSocket的相關框架
五、使用StarscreamSwift)完成長鏈需求
git


1、什麼是 WebSocket ?

WebSocket = 「HTTP第1次握手」 + TCP的「全雙工「通訊 的網絡協議。github

主要過程:web

  • 首先,經過HTTP第一次握手保證鏈接成功。
  • 其次,再經過TCP實現瀏覽器與服務器全雙工(full-duplex)通訊。(經過不斷髮ping包、pang包保持心跳)

最終,使得 「服務端」 擁有 「主動」 發消息給 「客戶端」 的能力。objective-c

這裏有幾個重點:swift

  1. WebSocket是基於TCP的上部應用層網絡協議。
  2. 它依賴於HTTP的第一次握手成功 + 以後的TCP雙向通訊。

2、WebSocket 應用場景

1. IM(即時通信)

典型例子:微信、QQ等
固然,用戶量若是很是大的話,僅僅依靠WebSocket確定是不夠的,各大廠應該也有本身的一些優化的方案與措施。但對於用戶量不是很大的即時通信需求,使用WebSocket是一種不錯的方案。瀏覽器

2. 遊戲(多人對戰)

典型例子:王者榮耀等(應該都玩過)ruby

3. 協同編輯(共享文檔)

多人同時編輯同一份文檔時,能夠實時看到對方的操做。
這時,就用上了WebSocket服務器

4. 直播/視頻聊天

對音頻/視頻須要較高的實時性。微信

5. 股票/基金等金融交易平臺

對於股票/基金的交易來講,每一秒的價格可能都會發生變化。

6. IoT(物聯網 / 智能家居)

例如,咱們的App須要實時的獲取智能設備的數據與狀態。 這時,就須要用到WebSocket

...... 等等等等

只要是一些對 「實時性」 要求比較高的需求,可能就會用到WebSocket


3、WebSocket 底層原理

WebSocket是一個網絡上的應用層協議,它依賴於HTTP協議的第一次握手,握手成功後,數據就經過TCP/IP協議傳輸了。

WebSocket分爲握手階段和數據傳輸階段,即進行了HTTP一次握手 + 雙工的TCP鏈接。

一、握手階段

首先,客戶端發送消息:

GET /chat HTTP/1.1
Host: server.qishare.org
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://qishare.org
Sec-WebSocket-Version: 13
複製代碼

而後,服務端返回消息:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
複製代碼

這裏值得注意的是Sec-WebSocket-Accept的計算方法: base64(hsa1(sec-websocket-key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))

  • 若是這個Sec-WebSocket-Accept計算錯誤,瀏覽器會提示:Sec-WebSocket-Accept dismatch
  • 若是返回成功,Websocket就會回調onopen事件

二、傳輸階段

WebSocket是以 frame 的形式傳輸數據的。 好比會將一條消息分爲幾個frame,按照前後順序傳輸出去。

這樣作會有幾個好處:

  • 較大的數據能夠分片傳輸,不用考慮到數據大小致使的長度標誌位不足夠的狀況。
  • HTTPchunk同樣,能夠邊生成數據邊傳遞消息,即提升傳輸效率。

WebSocket傳輸過程使用的報文,以下所示:

0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+
複製代碼

具體的參數說明以下:

  • FIN(1 bit): 表示信息的最後一幀,flag,也就是標記符。 PS:固然第一個消息片段也多是最後的一個消息片段;

  • RSV一、RSV二、RSV3(均爲1 bit): 默認均爲0。若是有約定自定義協議則不爲0,通常均爲0。(協議擴展用)

  • Opcode(4 bit): 定義有效負載數據,若是收到了一個未知的操做碼,鏈接也必須斷掉,如下是定義的操做碼:

操做碼 含義
%x0 連續消息片段
%x1 文本消息片段
%x2 二進制消息片段
%x3-7 (預留位)爲未來的非控制消息片段保留的操做碼。
%x8 鏈接關閉
%x9 心跳檢查ping
%xA 心跳檢查pong
%xB-F (預留位)爲未來的控制消息片段的保留操做碼。
  • Mask(1 bit):
    是否傳輸數據添加掩碼。
    若爲1,掩碼必須放在masking-key區域。(後面會提到..)
    注:客戶端給服務端發消息Mask值均爲1

  • Payload length:
    Payload字段用來存儲傳輸數據的長度。

自己Payload報文字段的大小可能有三種狀況:7 bit7+16 bit7+64 bit

第一種:7 bit,表示從0000000 ~ 1111101(即0~125),表示當前數據的length大小(較小數據,最大長度爲125)。

第二種:(7+16) bit:前7位爲1111110(即126)126表明後面會跟着2個字節無符號數,用來存儲數據length大小(長度最小126,最大爲65 535)。

第三種:(7+64) bit:前7位爲1111111(即127)127表明後面會跟着8個字節無符號數,用來存儲數據length大小(長度最小爲65536,最大爲2^16-1)。

Payload報文長度 所傳輸的數據大小區間
7 bit [ 0, 125]
7 +16 bit [ 126 , 65535]
7 + 64 bit [ 65536, 2^16 -1]

說明:
傳輸數據的長度,以字節的形式表示:7位、7+16位、或者7+64位。
1)若是這個值以字節表示是0-125這個範圍,那這個值就表示傳輸數據的長度;
2)若是這個值是126,則隨後的2個字節表示的是一個16進制無符號數,用來表示傳輸數據的長度;
3)若是這個值是127,則隨後的是8個字節表示的一個64位無符號數,這個數用來表示傳輸數據的長度。

  • Masking-key(0 bit / 4 bit):
    0 bit:說明mask值不爲1,無掩碼。
    4 bit:說明mask值爲1,添加掩碼。

PS:客戶端發送給服務端數據時,mask均爲1。
同時,Masking-key會存儲一個32位的掩碼。

  • Payload data(x+y byte): 負載數據爲擴展數據及應用數據長度之和。

  • Extension data(x byte): 若是客戶端與服務端之間沒有特殊約定,那麼擴展數據的長度始終爲0,任何的擴展都必須指定擴展數據的長度,或者長度的計算方式,以及在握手時如何肯定正確的握手方式。若是存在擴展數據,則擴展數據就會包括在負載數據的長度以內。

  • Application data(y byte): 任意的應用數據,放在擴展數據以後。
    應用數據的長度 = 負載數據的長度 - 擴展數據的長度
    即:Application data = Payload data - Extension data


4、iOS 中 WebSocket 相關框架

WebSocket(iOS客戶端):
Socket(iOS客戶端):

5、使用Starscream(swift)完成長鏈需求

首先附上Starscream:GitHub地址

  • 第一步:將Starsream導入到項目。

打開Podfile,加上:

pod 'Starscream', '~> 4.0.0'
複製代碼

接着pod install

  • 第二步:編寫鏈接代碼

導入頭文件,import Starscream

// 包裝請求頭
    var request = URLRequest(url: URL(string: "")!)
    request.timeoutInterval = 5 // Sets the timeout for the connection
    request.setValue("some message", forHTTPHeaderField: "Qi-WebSocket-Header")
    request.setValue("some message", forHTTPHeaderField: "Qi-WebSocket-Protocol")
    request.setValue("0.0.1", forHTTPHeaderField: "Qi-WebSocket-Version")
    request.setValue("some message", forHTTPHeaderField: "Qi-WebSocket-Protocol-2")

    socketManager = WebSocket(request: request)
    socketManager?.delegate = self
    socketManager?.connect() // 鏈接
複製代碼
  • 第三步:編寫通訊模塊代碼。

遵照並實現WebSocketDelegate

extension ViewController: WebSocketDelegate {
    // 通訊(與服務端協商好)
    func didReceive(event: WebSocketEvent, client: WebSocket) {
        switch event {
        case .connected(let headers):
            isConnected = true
            print("websocket is connected: \(headers)")
        case .disconnected(let reason, let code):
            isConnected = false
            print("websocket is disconnected: \(reason) with code: \(code)")
        case .text(let string):
            print("Received text: \(string)")
        case .binary(let data):
            print("Received data: \(data.count)")
        case .ping(_):
            break
        case .pong(_):
            break
        case .viablityChanged(_):
            break
        case .reconnectSuggested(_):
            break
        case .cancelled:
            isConnected = false
        case .error(let error):
            isConnected = false
            // ...處理異常錯誤
            print("Received data: \(String(describing: error))")
        }
    }
}
複製代碼

分別對應的是:

public enum WebSocketEvent {
    case connected([String: String])  //!< 鏈接成功
    case disconnected(String, UInt16) //!< 鏈接斷開
    case text(String)                 //!< string通訊
    case binary(Data)                 //!< data通訊
    case pong(Data?)                  //!< 處理pong包(保活)
    case ping(Data?)                  //!< 處理ping包(保活)
    case error(Error?)                //!< 錯誤
    case viablityChanged(Bool)        //!< 可行性改變
    case reconnectSuggested(Bool)     //!< 從新鏈接
    case cancelled                    //!< 已取消
}
複製代碼

這樣一個簡單的客戶端WebSocket demo就算完成了。


相關參考連接:
《微信,QQ這類IM app怎麼作——談談Websocket》(冰霜大佬)
《WebSocket的實現原理》 `

相關文章
相關標籤/搜索