Preface:

Some time ago, I used it in my company’s projectWebSocketThere was no time to tidy up properly.

Recently, while I had time, I combed it outWebSocketRelevant knowledge.


This article will introduce the following contents:

1. What isWebSocket?

2,WebSocketUsage scenarios

3,WebSocketUnderlying Principles (Protocol)

4,iOSIn theWebSocketRelated framework of

5, useStarscream(Swift) Complete long chain requirements (client)

6, use,GolangComplete long chain requirements (server side)


What is WebSocket?

WebSocket = HTTP first handshake +TCPA network protocol for “full duplex” communication.

Main process:

  • First of all, throughHTTPThe first handshake ensures a successful connection.
  • Second, pass againTCPImplement full duplex between browser and server (full-duplexCommunication). (By constantly sendingpingPackages,pangPackets keep the heartbeat)

Finally, the “server” has the ability to “proactively” send messages to the “client”.

Here are a few highlights:

  1. WebSocketIs based onTCPThe upper application layer network protocol.
  2. It depends on theHTTPAfter a successful first handshakeTCPTwo-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 theSec-WebSocket-AcceptIf the calculation is incorrect, the browser will prompt:Sec-WebSocket-Accept dismatch;
  • If success is returned,WebsocketWill the callbackonopenThe 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.
  • andHTTPthechunkSimilarly, 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 is1To 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: WillStarsreamImport 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”