内容简介:级别:★★☆☆☆标签:「WebSocket」「Starscream」「Golang」作者:
级别:★★☆☆☆
标签:「WebSocket」「Starscream」「Golang」
作者: 647
审校: 沐灵洛
上一篇: 《今天我们来聊一聊WebSocket》
主要介绍了WebSocket的原理、应用场景等等。
本篇将介绍WebSocket的双端实战( Client
、 Server
)。
分为两部分:
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
就算完成了。
- 客户端成功,日志截图:
二、使用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
接口,即可调用长链服务。
- 服务端,成功日志截图:
小编微信:可加并拉入《QiShare技术交流群》。
关注我们的途径有:
QiShare(微信公众号)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- vue项目实践004~~~一篮子的实践技巧
- HBase实践 | 阿里云HBase数据安全实践
- Spark 实践:物化视图在 SparkSQL 中的实践
- Spark实践|物化视图在 SparkSQL 中的实践
- HBase实践 | 数据人看Feed流-架构实践
- Kafka从上手到实践-实践真知:搭建Zookeeper集群
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
算法竞赛入门经典
刘汝佳 / 清华大学出版社 / 2009-11 / 24.00元
《算法竞赛入门经典》是一本算法竞赛的入门教材,把C/C++语言、算法和解题有机地结合在了一起,淡化理论,注重学习方法和实践技巧。全书内容分为11章,包括程序设计入门、循环结构程序设计、数组和字符串、函数和递归、基础题目选解、数据结构基础、暴力求解法、高效算法设计、动态规划初步、数学概念与方法、图论模型与算法,覆盖了算法竞赛入门所需的主要知识点,并附有大量习题。书中的代码规范、简洁、易懂,不仅能帮助......一起来看看 《算法竞赛入门经典》 这本书的介绍吧!