WebSocket 双端实践(iOS/ Golang)

栏目: IT技术 · 发布时间: 6年前

内容简介:级别:★★☆☆☆标签:「WebSocket」「Starscream」「Golang」作者:

级别:★★☆☆☆

标签:「WebSocket」「Starscream」「Golang」

作者:  647

审校: 沐灵洛

上一篇: 《今天我们来聊一聊WebSocket》
主要介绍了WebSocket的原理、应用场景等等。

本篇将介绍WebSocket的双端实战( ClientServer )。

分为两部分:

1.Client:使用 Starscream(swift) 完成客户端长链需求。

2.Server:使用 Golang 完成服务端长链需求。

一、使用Starscream(swift)完成客户端长链需求

首先附上Starscream: GitHub地址

第一步:将 Starsream 导入到项目。

打开 Podfile ,加上:

pod 'Starscream', '~> 4.0.0'
复制代码

接着 pod install

第二步:实现WebSocket能力。

  • 导入头文件, import Starscream

  • 初始化 WebSocket ,把一些请求头包装一下(与服务端对好)

private func initWebSocket() {
    // 包装请求头
    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
}
复制代码

同时,我用三个Button的点击事件,分别模拟了connect(连接)、write(通信)、disconnect(断开)。

// Mark - Actions
    // 连接
    @objc func connetButtonClicked() {
        socketManager?.connect()
    }
    // 通信
    @objc func sendButtonClicked() {
        socketManager?.write(string: "some message.")
    }
    // 断开
    @objc func closeButtonCliked() {
        socketManager?.disconnect()
    }
复制代码

第三步:实现WebSocket回调方法(接收服务端消息)

遵守并实现 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 就算完成了。

  • 客户端成功,日志截图:
WebSocket 双端实践(iOS/ Golang)

二、使用Golang完成简单服务端长链需求

仅仅有客户端也无法验证 WebSocket 的能力。

因此,接下来我们用 Golang 简单做一个本地的服务端 WebSocket 服务。

PS:最近,正好在学习 Golang ,参考了一些大神的作品。

直接上代码了:

package main

import (
	"crypto/sha1"
	"encoding/base64"
	"errors"
	"io"
	"log"
	"net"
	"strings"
)

func main() {
	ln, err := net.Listen("tcp", ":8000")
	if err != nil {
		log.Panic(err)
	}
	for {
		log.Println("wss")
		conn, err := ln.Accept()
		if err != 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))
	if err != nil {
		log.Println(err)
	}
	isHttp := false
	// 先暂时这么判断
	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:这里省略其他的验证
		guid := "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
		// 计算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 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()
			if err != nil {
				log.Println("readIframe err:", err)
			}
			log.Println("read data:", string(data))
			err = wssocket.SendIframe([]byte("good"))
			if err != nil {
				log.Println("sendIframe err:", err)
			}
			log.Println("send data")
		}
	} else {
		log.Println(string(content))
		// 直接读取
	}
}

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 {
	// 这里只处理data长度<125的
	if len(data) >= 125 {
		return errors.New("send iframe data error")
	}
	lenth := len(data)
	maskedData := make([]byte, lenth)
	for i := 0; i < lenth; i++ {
		if this.MaskingKey != nil {
			maskedData[i] = data[i] ^ this.MaskingKey[i%4]
		} else {
			maskedData[i] = data[i]
		}
	}
	this.Conn.Write([]byte{0x81})
	var payLenByte byte
	if this.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() (data []byte, err error) {
	err = nil
	//第一个字节: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()
	if err != 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
}
复制代码

完成后,在本地执行:

go run WebSocket_demo.go
复制代码

即可开启本地服务。

这时候访问 ws://127.0.0.1:8000/chat 接口,即可调用长链服务。

  • 服务端,成功日志截图:
WebSocket 双端实践(iOS/ Golang)

小编微信:可加并拉入《QiShare技术交流群》。

WebSocket 双端实践(iOS/ Golang)

关注我们的途径有:

QiShare(简书)

QiShare(掘金)

QiShare(知乎)

QiShare(GitHub)

QiShare(CocoaChina)

QiShare(StackOverflow)

QiShare(微信公众号)


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

A Philosophy of Software Design

A Philosophy of Software Design

John Ousterhout / Yaknyam Press / 2018-4-6 / GBP 14.21

This book addresses the topic of software design: how to decompose complex software systems into modules (such as classes and methods) that can be implemented relatively independently. The book first ......一起来看看 《A Philosophy of Software Design》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试