Preface:
Some time ago, I used it in my company’s projectWebSocket
There was no time to tidy up properly.
Recently, while I had time, I combed it outWebSocket
Relevant knowledge.
This article will introduce the following contents:
1. What isWebSocket
?
2,WebSocket
Usage scenarios
3,WebSocket
Underlying Principles (Protocol)
4,iOS
In theWebSocket
Related framework of
5, useStarscream
(Swift
) Complete long chain requirements (client)
6, use,Golang
Complete long chain requirements (server side)
What is WebSocket?
WebSocket = HTTP first handshake +TCP
A network protocol for “full duplex” communication.
Main process:
- First of all, through
HTTP
The first handshake ensures a successful connection. - Second, pass again
TCP
Implement full duplex between browser and server (full-duplex
Communication). (By constantly sendingping
Packages,pang
Packets keep the heartbeat)
Finally, the “server” has the ability to “proactively” send messages to the “client”.
Here are a few highlights:
WebSocket
Is based onTCP
The upper application layer network protocol.- It depends on the
HTTP
After a successful first handshakeTCP
Two-way communication.
Two, WebSocket application scenarios
1. IM
Typical examples: wechat, QQ, etc. Of course, if the number of users is very large, it is certainly not enough to rely on WebSocket only, and major factories should also have their own optimization plans and measures. However, WebSocket is a good solution for instant messaging with small users.
2. Games (multiplayer)
Typical example: King of Glory, etc.
3. Collaborative editing (sharing documents)
When multiple people edit the same document at the same time, they can see each other’s actions in real time. In this case, WebSocket is used.
Live/video chat
High real time requirement for audio/video.
5. Stock/fund and other financial trading platforms
For stock/fund trading, the price can change from second to second.
6. IoT (Internet of Things/Smart Home)
For example, our App needs to obtain the data and status of smart devices in real time. In this case, you need to use WebSocket.
. Etc., etc.
As long as some of the “real time” requirements are relatively high, may use WebSocket.
Third, the underlying principle of WebSocket
WebSocket is an application layer protocol on the network. It relies on the first handshake of HTTP. After the handshake is successful, data is transmitted through TCP/IP.
WebSocket consists of the handshake phase and the data transmission phase, that is, the TCP connection with HTTP handshake + duplex.
1. Handshake phase
First, the client sends a message: (in this case, a local service written in Golang)
GET /chatHTTP / 1.1Host: 127.0.0.1:8000
OriginWs: / / 127.0.0.1:8000Qi-WebSocket-Version: 0.0.1
Sec-WebSocket-Version13:Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: aGFjb2hlYW9rd2JtdmV5eA==
Copy the code
The server then returns the message (in this case, received by a client written in Swift)
"Upgrade": "websocket"."Connection": "Upgrade"."Sec-WebSocket-Accept": "NO+pj7z0cvnNj//mlwRuAnCYqCE="
Copy the code
Base64 (HSA1 (SEC-websocket-key + 258eAFa5-E914-47DA-95CA-C5AB0DC85b11))
- If the
Sec-WebSocket-Accept
If the calculation is incorrect, the browser will prompt:Sec-WebSocket-Accept dismatch
; - If success is returned,
Websocket
Will the callbackonopen
The event
2. Transmission phase
WebSocket transmits data in the form of a frame. For example, a message is divided into frames and transmitted in sequence.
This has several benefits:
- Large data can be transmitted in fragments without considering the insufficient length flag bits caused by data size.
- and
HTTP
thechunk
Similarly, messages can be delivered while generating data, which increases transmission efficiency.
The packets used in WebSocket transmission are as follows:
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 ... | +---------------------------------------------------------------+Copy the code
The parameters are described as follows:
-
FIN (1 bit) : indicates the last frame of the message. Flag is the identifier. PS: Of course, the first message fragment could also be the last message fragment;
-
RSV1, RSV2, and RSV3 (all 1 bit) : the default value is 0. If there is a custom protocol, the value is not 0. Generally, the value is 0. (Protocol extension)
-
Opcode (4 bit) : Defines the payload data. If an unknown Opcode is received, the connection must also be broken. Here is the Opcode defined:
opcode | meaning |
---|---|
%x0 | Continuous message fragment |
%x1 | Text message fragment |
%x2 | Binary message fragment |
%x3-7 | (reserved bit) An opcode reserved for future non-control message fragments. |
%x8 | Connection is closed |
%x9 | Heartbeat Check Ping |
%xA | Heartbeat check PONG |
%xB-F | (reserved bit) Reserved opcodes for future control message fragments. |
-
Mask (1 bit) : Indicates whether to add a Mask for data transmission. If the value is 1, the mask must be placed in the masking-key area. (More on that later) Note: The Mask value of messages sent from the client to the server is 1.
-
Payload Length: The Payload field stores the length of transmitted data.
The Payload field size can be 7 bits, 7+16 bits, or 7+64 bits.
Type 1:7 bits: 0000000 to 1111101 (0 to 125), which indicates the length of the current data (the maximum length of the small data is 125).
Second: (7+16) bit: the first seven bits are 1111110 (126). 126 represents the unsigned number followed by two bytes, which is used to store the length of the data (the minimum length is 126 and the maximum length is 65 535).
The third type: (7+64) bit: the first 7 bits are 1111111 (127), which is followed by 8 bytes of unsigned number. 127 is used to store the length of data (the minimum length is 65536, the maximum is 2^16-1).
Payload Packet Length | The size range of data transferred |
---|---|
7 bit | [0, 125] |
7 +16 bit | [126, 65535] |
7 + 64 bit | [] 2 ^ 16-65536),” |
Description: The length of the transmitted data, expressed in bytes: 7 bits, 7+16 bits, or 7+64 bits. 1) If the value is in the range 0-125 in bytes, the value represents the length of the data transferred; 2) If the value is 126, the following two bytes represent a hexadecimal unsigned number that represents the length of the transmitted data; 3) If the value is 127, it is followed by an 8-byte representation of a 64-bit unsigned number, which is used to indicate the length of the data being transferred.
- Masking-key (0 bit / 4 bit) :
0 bit
: indicates that the mask value is not1
, no mask.4 bit
: indicates that the mask value is1
To add a mask.
PS: Mask is 1 when the client sends data to the server. At the same time, Masking-key stores a 32-bit mask.
-
Payload Data (X + Y byte) : Payload data is the sum of extended data and application data.
-
Extension Data (X byte) : If there is no special contract between the client and the server, the length of the Extension data is always 0. Any Extension must specify the length of the Extension data, or how to calculate the length, and how to determine the correct handshake when shaking hands. If there is extended data, it is included in the length of the payload data.
-
Application Data (Y Byte) : Indicates any Application data placed after the extension data. Application data length = Payload data length – Extended data length Application data = Payload data-extension data
4. WebSocket framework in iOS
WebSocket (iOS client) :
-
Starscream (Swift) : Websockets in Swift for iOS and OSX. (STAR 5K +)
-
Note: SocketRocket (Objective-C) : A conforming Objective-C WebSocket client library. (Star: 8K +)
-
SwiftWebSocket (Swift) : Fast Websockets in iOS and OSX. (STAR: 1K +)
Socket (iOS client) :
-
CocoaAsyncSocket: Asynchronous Socket Networking Library for Mac and iOS. (STAR: 11K +)
-
Socket.IO-client -swift: socket. IO-client for iOS/OS X. (Star: 4k+)
5. Use Starscream (Swift) to complete the client long chain requirements
Start with the Starscream: GitHub address
Step 1: WillStarsream
Import to the project.
Open your Podfile and add:
pod 'Starscream'.'~ > 4.0.0'
Copy the code
Next, pod Install.
Step 2: Implement WebSocket capabilities.
-
Import the header file, import Starscream
-
Initialize WebSocket to wrap some request headers (versus server)
private func initWebSocket(a) {
// Wrap the request header
var request = URLRequest(url: URL(string: Ws: / / "127.0.0.1:8000 / chat")! 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
}
Copy the code
At the same time, I simulate connect, write and disconnect respectively with three Button click events.
// Mark - Actions
/ / the connection
@objc func connetButtonClicked(a){ socketManager? .connect() }/ / communication
@objc func sendButtonClicked(a){ socketManager? .write(string:"some message.")}/ / disconnect
@objc func closeButtonCliked(a){ socketManager? .disconnect() }Copy the code
Step 3: Implement the WebSocket callback method (to receive server messages)
Comply with and implement a WebSocketDelegate.
extension ViewController: WebSocketDelegate {
// Communication (negotiate with the server)
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
/ /... Handling exception errors
print("Received data: \(String(describing: error))")}}}Copy the code
The corresponding ones are:
public enum WebSocketEvent {
case connected([String: String]) / /! < Connection successful
case disconnected(String.UInt16) / /! < Disconnection
case text(String) / /! < string communication
case binary(Data) / /! < data communication
case pong(Data?)./ /! < Handle pong package (live)
case ping(Data?)./ /! < Ping packet processing (live)
case error(Error?)./ /! < error
case viablityChanged(Bool) / /! < Feasibility change
case reconnectSuggested(Bool) / /! < reconnect
case cancelled / /! < has been cancelled
}
Copy the code
Such a simple client WebSocket demo is complete.
- The client succeeds. Log screenshot:
6. Use Golang to complete simple server long chain requirements
A client alone cannot verify WebSocket capabilities. So, let’s do a simple local server WebSocket service with Golang.
PS: Recently, I happened to learn Golang and referred to the works of some great gods.
Go directly to the code:
package main
import (
"crypto/sha1"
"encoding/base64"
"errors"
"io"
"log"
"net"
"strings"
)
func main(a) {
ln, err := net.Listen("tcp".": 8000")
iferr ! =nil {
log.Panic(err)
}
for {
log.Println("wss")
conn, err := ln.Accept()
iferr ! =nil {
log.Println("Accept err:", err)
}
for {
handleConnection(conn)
}
}
}
func handleConnection(conn net.Conn) {
content := make([]byte.1024)
_, err := conn.Read(content)
log.Println(string(content))
iferr ! =nil {
log.Println(err)
}
isHttp := false
// For the moment
if string(content[0:3= =])"GET" {
isHttp = true
}
log.Println("isHttp:", isHttp)
if isHttp {
headers := parseHandshake(string(content))
log.Println("headers", headers)
secWebsocketKey := headers["Sec-WebSocket-Key"]
// NOTE: Other validations are omitted here
guid := "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
/ / calculate the Sec - WebSocket - Accept
h := sha1.New()
log.Println("accept raw:", secWebsocketKey+guid)
io.WriteString(h, secWebsocketKey+guid)
accept := make([]byte.28)
base64.StdEncoding.Encode(accept, h.Sum(nil))
log.Println(string(accept))
response := "HTTP / 1.1 101 Switching separate Protocols \ r \ n"
response = response + "Sec-WebSocket-Accept: " + string(accept) + "\r\n"
response = response + "Connection: Upgrade\r\n"
response = response + "Upgrade: websocket\r\n\r\n"
log.Println("response:", response)
if lenth, err := conn.Write([]byte(response)); err ! =nil {
log.Println(err)
} else {
log.Println("send len:", lenth)
}
wssocket := NewWsSocket(conn)
for {
data, err := wssocket.ReadIframe()
iferr ! =nil {
log.Println("readIframe err:", err)
}
log.Println("read data:".string(data))
err = wssocket.SendIframe([]byte("good"))
iferr ! =nil {
log.Println("sendIframe err:", err)
}
log.Println("send data")}}else {
log.Println(string(content))
// Read directly}}type WsSocket struct {
MaskingKey []byte
Conn net.Conn
}
func NewWsSocket(conn net.Conn) *WsSocket {
return &WsSocket{Conn: conn}
}
func (this *WsSocket) SendIframe(data []byte) error {
// Only data <125 is processed here
if len(data) >= 125 {
return errors.New("send iframe data error")
}
lenth := len(data)
maskedData := make([]byte, lenth)
for i := 0; i < lenth; i++ {
ifthis.MaskingKey ! =nil {
maskedData[i] = data[i] ^ this.MaskingKey[i%4]}else {
maskedData[i] = data[i]
}
}
this.Conn.Write([]byte{0x81})
var payLenByte byte
ifthis.MaskingKey ! =nil && len(this.MaskingKey) ! =4 {
payLenByte = byte(0x80) | byte(lenth)
this.Conn.Write([]byte{payLenByte})
this.Conn.Write(this.MaskingKey)
} else {
payLenByte = byte(0x00) | byte(lenth)
this.Conn.Write([]byte{payLenByte})
}
this.Conn.Write(data)
return nil
}
func (this *WsSocket) ReadIframe(a) (data []byte, err error) {
err = nil
// First byte: FIN + rsv1-3 + OPCODE
opcodeByte := make([]byte.1)
this.Conn.Read(opcodeByte)
FIN := opcodeByte[0] > >7
RSV1 := opcodeByte[0] > >6 & 1
RSV2 := opcodeByte[0] > >5 & 1
RSV3 := opcodeByte[0] > >4 & 1
OPCODE := opcodeByte[0] & 15
log.Println(RSV1, RSV2, RSV3, OPCODE)
payloadLenByte := make([]byte.1)
this.Conn.Read(payloadLenByte)
payloadLen := int(payloadLenByte[0] & 0x7F)
mask := payloadLenByte[0] > >7
if payloadLen == 127 {
extendedByte := make([]byte.8)
this.Conn.Read(extendedByte)
}
maskingByte := make([]byte.4)
if mask == 1 {
this.Conn.Read(maskingByte)
this.MaskingKey = maskingByte
}
payloadDataByte := make([]byte, payloadLen)
this.Conn.Read(payloadDataByte)
log.Println("data:", payloadDataByte)
dataByte := make([]byte, payloadLen)
for i := 0; i < payloadLen; i++ {
if mask == 1 {
dataByte[i] = payloadDataByte[i] ^ maskingByte[i%4]}else {
dataByte[i] = payloadDataByte[i]
}
}
if FIN == 1 {
data = dataByte
return
}
nextData, err := this.ReadIframe()
iferr ! =nil {
return
}
data = append(data, nextData...)
return
}
func parseHandshake(content string) map[string]string {
headers := make(map[string]string.10)
lines := strings.Split(content, "\r\n")
for _, line := range lines {
if len(line) >= 0 {
words := strings.Split(line, ":")
if len(words) == 2 {
headers[strings.Trim(words[0]."")] = strings.Trim(words[1]."")}}}return headers
}
Copy the code
When done, execute locally:
go run WebSocket_demo.go
Copy the code
The local service can be started.
Access the WS ://127.0.0.1:8000/chat interface to invoke the long-chain service.
- Server, success log screenshot:
Related reference link: “Wechat,QQ such IM APP how to do — Talk about Websocket” (Frost big guy) “Websocket implementation principle”