首页
Preview

Golang中的WebSocket API

WebSocket概述

WebSocket流程

WebSocket API是一种先进的技术,可以在用户浏览器和服务器之间打开双向交互式通信会话。使用此API,你可以向服务器发送消息并接收事件驱动的响应,而无需轮询服务器以获得响应。——mdn

简单来说,WebSocket是客户端和服务器之间的持久双向全双工TCP连接。

工作原理

WebSocket不使用http://https://协议,而使用新的协议ws:(或安全WebSocket的wss:)。 URI的剩余部分与http相同。

WebSocket连接通过升级HTTP请求/响应对来建立。支持WebSocket并想要建立连接的客户端将发送一个请求,其中包括一些必需的标头:

  • 连接:Upgrade

此标头的常见值为keep-alive,以确保连接是持久的,以允许对同一服务器进行后续请求。在WebSocket握手期间,我们将标头设置为“Upgrade”,表示我们希望保持连接活动,并将其用于非HTTP请求。

  • 升级:websocket

Upgrade标头由客户端用于要求服务器切换到列出的协议之一,按降序偏好顺序。我们在此处指定websocket,以表示客户端想要建立WebSocket连接。

  • Sec-Websocket-Version:13

WebSocket协议的唯一接受版本是13。此标头中列出的任何其他版本都无效。

  • Sec-Websocket-Key:FhBXNPgT88rvuYQjlTynfQ==

Sec-WebSocket-Key是客户端生成的一次性随机值(nonce)。该值是随机选择的16字节值,已进行base64编码。

使用此标头将GET请求发送到服务器以进行连接升级。

服务器的回复必须具有HTTP 101 Switching Protocols响应代码。 HTTP 101 Switching Protocols响应表示服务器正在切换到客户端在其“Upgrade”请求标头中请求的协议。

连接升级示例:

请求

请求信息

请求标头

响应

响应状态码

响应标头

客户端接收到服务器响应后,WebSocket连接已打开以开始传输数据。

Golang中的简单WebSocket实现

我们将在Golang中实现WebSocket服务器,并将一个简单的Web应用程序作为客户端使用HTML(Javascript)。

注意:我欢迎你更改服务器和客户端的实现。出于简单和理解目的,我只是使实现非常简单。

服务器实现

我们将使用Gorilla websocket包在Golang中实现WebSocket服务器。

创建项目根目录并初始化Go模块。

go mod init <project_root_dir>

创建名为server的文件夹,其中包含服务器代码。

我们将使用http包进行HTTP连接。

样板代码。

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/websocket" // used for the WebSocket communication
)

func rootfunc(w http.ResponseWriter, req *http.Request) {
  fmt.Println(req)
 // implemenetation for http
}

func getWSConn(w http.ResponseWriter, req *http.Request) {
  fmt.Println(req)
 // implementation for WebSocket
}

func main() {
	http.HandleFunc("/", rootfunc)
	http.HandleFunc("/ws", getWSConn)

	log.Fatal(http.ListenAndServe(":8090", nil))

}

运行go mod tidy以获取_gorilla websocket_包。

rootfunc

我已使用http协议处理了我们服务器的根页面。

仅仅是一个简单的响应。

func rootfunc(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"status": "Success", "Desc": "RootPage Endpoint Hitted"})
}

getWSConn

点击路径/ws的客户端将被升级到WebSocket连接,如果客户端支持WebSocket连接。

为了升级连接,我们将使用已导入的websocket包。

var wsUpgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    CheckOrigin:     func(r *http.Request) bool { return true }, 
// */* accept all
}

我们有一个类型为_websocket.Upgrader_的wsUpgrader变量,用于将http连接升级为websocket。我们可以为类型_websocket.Upgrader_指定以下字段:

  • ReadBufferSizeWriteBufferSize。 (ReadBufferSize和WriteBufferSize指定以字节为单位的I/O缓冲区大小。)
  • HandshakeTimeout。 (HandshakeTimeout指定握手完成的持续时间。)
  • CheckOrigin func(r *http.Request) bool 。 (如果请求Origin标头可接受,则CheckOrigin返回true。 CheckOrigin函数应仔细验证请求来源,以防止跨站点请求伪造。)

我已实现getWSConn函数以升级连接,然后从客户端读取消息并回显相同的消息,每隔10秒服务器将向已连接的客户端发送消息。

getWSConn实现

var wsUpgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin:     func(r *http.Request) bool { return true }, // */* accept all
}

func readAndEcho(ws *websocket.Conn) {
	for {
		msgType, msg, err := ws.ReadMessage()
		if err != nil {
			log.Println(err)
			return
		}
		log.Printf("The Received Message type is %v \n", msgType)
		log.Printf("The Received Message is %v \n", msg)
		log.Println("Echo the same messgae")
		// echo the same msg
		if err := ws.WriteMessage(msgType, []byte(fmt.Sprintf("Echo : %s", msg))); err != nil {
			log.Println(err)
		}
		// type ResJson struct {
		// 	Msg string
		// }
		// ws.WriteJSON(ResJson{Msg: "Message"})

	}
}

func writeMsg(ws *websocket.Conn) {
	for {
		time.Sleep(time.Second * 10)
		log.Println("Writing the message to the client")
		nowTime := time.Now()
		msg := nowTime.UTC()
		log.Println(msg)
		if err := ws.WriteMessage(1, []byte(msg.String())); err != nil {
			log.Println(err)
			return
		}
	}
}

func getWSConn(w http.ResponseWriter, req *http.Request) {
	log.Println(req)
	log.Println("################Request Headers################")
	for h := range req.Header {
		fmt.Printf("%s : %s\n", h, req.Header[h])
	}
	log.Println("Received new Client connection")
	ws, err := wsUpgrader.Upgrade(w, req, nil)
	if err != nil {
		log.Println(err)
		return
	}
	log.Println("Client upgrade to WS success")
	go writeMsg(ws)
	go readAndEcho(ws)

}

writeMsg函数用于将消息写入客户端(ws连接)。

readAndEcho函数用于从客户端读取消息,然后回显相同的消息。

完整的服务器代码

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
)

var wsUpgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin:     func(r *http.Request) bool { return true }, // */* accept all
}

func rootfunc(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{"status": "Success", "Desc": "RootPage Endpoint Hitted"})

}

func readAndEcho(ws *websocket.Conn) {
	for {
		msgType, msg, err := ws.ReadMessage()
		if err != nil {
			log.Println(err)
			return
		}
		log.Printf("The Received Message type is %v \n", msgType)
		log.Printf("The Received Message is %v \n", msg)
		log.Println("Echo the same messgae")
		// echo the same msg
		if err := ws.WriteMessage(msgType, []byte(fmt.Sprintf("Echo : %s", msg))); err != nil {
			log.Println(err)
		}
		// type ResJson struct {
		// 	Msg string
		// }
		// ws.WriteJSON(ResJson{Msg: "Message"})

	}
}

func writeMsg(ws *websocket.Conn) {
	for {
		time.Sleep(time.Second * 10)
		log.Println("Writing the message to the client")
		nowTime := time.Now()
		msg := nowTime.UTC()
		log.Println(msg)
		if err := ws.WriteMessage(1, []byte(msg.String())); err != nil {
			log.Println(err)
			return
		}
	}
}

func getWSConn(w http.ResponseWriter, req *http.Request) {
	log.Println(req)
	log.Println("################Request Headers################")
	for h := range req.Header {
		fmt.Printf("%s : %s\n", h, req.Header[h])
	}
	log.Println("Received new Client connection")
	ws, err := wsUpgrader.Upgrade(w, req, nil)
	if err != nil {
		log.Println(err)
		return
	}
	log.Println("Client upgrade to WS success")
	go writeMsg(ws)
	go readAndEcho(ws)

}

func main() {
	http.HandleFunc("/", rootfunc)
	http.HandleFunc("/ws", getWSConn)

	log.Fatal(http.ListenAndServe(":8090", nil))

}

客户端实现

我们还可以使用Postman作为WebSocket客户端。

我已创建一个简单的Web应用程序,使用HTML和Javascript连接到我们的服务器并将连接升级到WebSocket。

在JS中,WebSocket()构造函数返回一个新的WebSocket对象。

new WebSocket(url)

我使用了该WebSocket对象的事件属性,并实现了我们的简单客户端。## 客户端代码

免责声明:我既不是Web开发人员,也没有从事过Web开发,但我只是为了满足自己的兴趣构建了这个客户端,如果客户端的编码风格不好,请忽略。但它完成了我所需的工作。

<!DOCTYPE html>
<head>
  <title>WebSocket</title>
<style>
* {
  box-sizing: border-box;
}
/* Clear floats after the columns */
.row:after {
  content: "";
  display: table;
  clear: both;
}

.column {
  float: left;
  width: 50%;
  padding: 10px;
}

/* Responsive layout - makes a two column-layout instead of four columns */
@media screen and (max-width: 900px) {
  .column  {
    width: 50%;
  }
}

/* Responsive layout - makes the two columns stack on top of each other instead of next to each other */
@media screen and (max-width: 600px) {
  .column  {
    width: 100%;
  }
}
.p-container{
  display: flex;
  gap: .5rem;
}

.button {
    text-align: center;
    display: block;
    margin: 0 auto;
}

.sentNotify {
  margin-left: 20px;
  color: #000000;
}
.receiveNotify {
  margin-left: 20px;
  color: #000000;
}

.sentNotify em {
  color: #0db55a;
}
.receiveNotify em {
  color: #766aff;
}

p {
  font-size: 18px;
}

</style>
</head>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<body>

<h1 style="background-color:#ffffff;justify-content: center;display: flex;">Simple WebSocket client</h1>
    	
    <div id="root" class="row">
      <p style="margin-left: 15px; position: sticky;top: 0; background-color:#ffffff">Server <b>localhost:8090/ws</b></p>
    	<div id="statuscolumn" class="column p-container" style="position: sticky;top: 0; background-color:#ffffff">
                <p id="sts"><b>Connection Status :</b></p>
                <p id="wsStatus" style="color:#065256"><b>Connecting...</b></p>
        </div>
        <div id="sendmsgcolumn" class="column" style="padding-left: 10px;position: sticky;top: 0">
        	<form action="" class="w3-container" onkeydown="return event.key != 'Enter';" style="background-color:rgb(247, 231, 255); padding: 10px;">
                <label for="tosndmsg">Message to send</label>
                <input class="w3-input" type="text" id="tosndmsg" name="msgtosnd"><br><br>
                <button type="button" class="button" onclick="handleinputmessage()">Send</button>
            </form>
        </div>
        
    </div>
    <script>
        let socket = new WebSocket("ws://localhost:8090/ws")
        const root = document.getElementById('root');
        socket.onopen = () => {
            console.log("Successfully connected")
            let initialMsg = "Hi Server. Im JS Client"
            socket.send(initialMsg)
            const stscolumn = document.getElementById('statuscolumn');
            const wsStatusp = document.getElementById('wsStatus');
            const newElement = document.createElement('p');
            newElement.setAttribute("id","wsStatus")
            newElement.setAttribute("style","color: #0a9511")
            newElement.innerHTML = '<b>Connected</b>';
            stscolumn.replaceChild(newElement, wsStatusp);
            
            const newMsgElement = document.createElement('p');
            newMsgElement.setAttribute("class","sentNotify")
            newMsgElement.innerHTML=`<p><em>Sent: </em> ${initialMsg}</p>`
            root.appendChild(newMsgElement);
        }
        
        socket.onclose = (event) =>{
            console.log("Disconnected", event)
            const stscolumn = document.getElementById('statuscolumn');
            const wsStatusp = document.getElementById('wsStatus');
            const newElement = document.createElement('p');
            newElement.setAttribute("id","wsStatus")
            newElement.setAttribute("style","color: #ef1316")
            newElement.innerHTML = '<b>Disconnected</b>';
            stscolumn.replaceChild(newElement, wsStatusp);
        }
        
        socket.onmessage = (event) =>{
            console.log("received msg", event)
            console.log("Data", event["data"])
            const newElement = document.createElement('p');
            newElement.setAttribute("class","receiveNotify")
            newElement.innerHTML=`<p><em>Received: </em> ${event["data"]}</p>`
            root.appendChild(newElement);
        }
        
        handleinputmessage = () =>{
            var input = document.getElementById("tosndmsg").value;
            console.log(input);
            if (socket.readyState === WebSocket.OPEN) {
                socket.send(input)
                const newElement = document.createElement('p');
                newElement.setAttribute("class","sentNotify")
                newElement.innerHTML=`<p><em>Sent: </em> ${input}</p>`
                root.appendChild(newElement);
              }
            else{
              alert("The Connection to the Websocket is not opened")
            }
            
        }
    </script>
</body>

工作演示

启动服务器。

我们的服务器已经启动并运行,我们可以通过访问根页面来确认这一点。

服务器根页面

现在,让我们打开我们的Web客户端。

我们的WS客户端

参考资料

希望你学到了关于Websocket及其在Golang中的基本实现。

你可以在我的Github上找到这个项目。

译自:https://towardsdev.com/websocket-api-in-golang-8fc43c3e6192

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
菜鸟一只
你就是个黄焖鸡,又黄又闷又垃圾。

评论(0)

添加评论