Painted painted levels: fostered fostered fostered

Tags: “WebSocket” “Starscream” “Golang


* Previous post: Today we are going to talk about Websockets

It mainly introduces the principle and application scenarios of WebSocket. *

This article will introduce WebSocket two – end combat (Client, Server).

There are two parts: 1.Client: Use Starscream (Swift) to complete long chain Client requirements. 2.Server: Use Golang to complete the long chain requirements on the Server side.

1. Use Starscream (Swift) to fulfill 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:

The Demo source code


2. Use Golang to complete the 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:

The Demo source code


Related references:

“Wechat,QQ, such as IM APP how to do — Talk about Websocket” (Frost big guy) “Websocket implementation principle”


Recommended articles:

Today we are going to talk about WebSocket (iOS/Golang) using Swift for Bessel curve drawing Swift 5.1 (11) – Method Swift 5.1 (10) – Properties iOS App background keep live Strange dance weekly