前言:
前段时间,在公司的项目中用到了WebSocket
,当时没有时间好好整理。
最近,趁着有时间,就好好梳理了一下WebSocket
的相关知识。html
本篇将介绍如下内容:
一、什么是WebSocket
?
二、WebSocket
使用场景
三、WebSocket
底层原理(协议)
四、iOS
中WebSocket
的相关框架
五、使用Starscream
(Swift
)完成长链需求git
WebSocket = “HTTP第1次握手” + TCP
的“全双工“通讯 的网络协议。github
主要过程:web
HTTP
第一次握手保证链接成功。TCP
实现浏览器与服务器全双工(full-duplex
)通讯。(经过不断发ping
包、pang
包保持心跳)最终,使得 “服务端” 拥有 “主动” 发消息给 “客户端” 的能力。objective-c
这里有几个重点:swift
WebSocket
是基于TCP
的上部应用层网络协议。HTTP
的第一次握手成功 + 以后的TCP
双向通讯。典型例子:微信、QQ等
固然,用户量若是很是大的话,仅仅依靠WebSocket
确定是不够的,各大厂应该也有本身的一些优化的方案与措施。但对于用户量不是很大的即时通信需求,使用WebSocket
是一种不错的方案。浏览器
典型例子:王者荣耀等(应该都玩过)ruby
多人同时编辑同一份文档时,能够实时看到对方的操做。
这时,就用上了WebSocket
。服务器
对音频/视频须要较高的实时性。微信
对于股票/基金的交易来讲,每一秒的价格可能都会发生变化。
例如,咱们的App须要实时的获取智能设备的数据与状态。 这时,就须要用到WebSocket
。
...... 等等等等
只要是一些对 “实时性” 要求比较高的需求,可能就会用到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
,按照前后顺序传输出去。
这样作会有几个好处:
HTTP
的chunk
同样,能够边生成数据边传递消息,即提升传输效率。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 bit
、7+16 bit
、7+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位无符号数,这个数用来表示传输数据的长度。
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
Starscream(swift): Websockets in swift for iOS and OSX.(star 5k+
)
SocketRocket(objective-c): A conforming Objective-C WebSocket client library.(star:8k+
)
SwiftWebSocket(swift): Fast Websockets in Swift for iOS and OSX.(star:1k+
)
CocoaAsyncSocket: Asynchronous socket networking library for Mac and iOS.(star:11k+
)
socket.io-client-swift: Socket.IO-client for iOS/OS X.(star:4k+
)
首先附上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的实现原理》 `