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_指定以下字段:
ReadBufferSize
,WriteBufferSize
。 (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
评论(0)