使用go语言开发web服务器时,常常需要web端发送请求给服务端,如果碰到需要长连接的情况,服务端处理http请求往往占用大量资源,而websocket则能使web端和服务端维持长连接。除此之外,建立长连接亦可以使服务端主动向web端推送消息,从而为项目提供更加丰富的功能。
本文面向初次使用go开发web服务端的读者,建立并完成一个完整的web端与服务端实时通信项目。并在文章末尾贴出了开发中的踩坑点,供初学者参考。
OS:Windows 10 专业版(21H1)
编辑器:vscode
服务端:Go(1.17)
web端:HTML5+JavaScript
浏览器:Edge
目标:
web端发起websocket连接,服务端侦听到连接,并处理前端的请求。
背景:
JavaScript完全支持完整的websocket通信,仅需要使用相应函数即可实现通信需求;
golang对websocket无原生支持,需要以将通信发起之初的http连接升级为websocket连接,其原理相关介绍很多,由于需求场景很多,golang有维护较好的第三方库可供方便使用,本文仅针对需快速实现连接功能需求的读者参考。
目标:
一个显示服务器时间的段落,一个触发websocket连接的按钮
代码:
TestWebsocket
服务器时间:
2022-03-02 20:00:00
按F12可显示console.log内容。
目标:
监听web发来的请求,升级为websocket通讯协议,发送打招呼信息,循环发送当前时间
golang的官方websocket库未得到很好的维护,且功能及稳定性不如第三方开源的gorilla
golang原生支持http连接,websocket连接从http连接升级而来,每次创建websocket连接前,需要首先升级在http连接上,之后可维持websocket连接。
golang需要使用http.HandleFunc来开启http监听,它将在收到http请求后执行函数。同时,升级websocket连接也需要在函数开始执行后才能创建。所以在go中会有net/http包来分配及启动监听,gorrila/websocket包来创建websocket连接。
代码:
package main
import (
"fmt"
"net/http"
"time"
"./websocket-master" // "github.com/gorrila/websocket"连接不上,我直接下载了源码
)
var upgrader = websocket.Upgrader{
// 解决跨域问题
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func main() {
// 注册服务器事件
http.HandleFunc("/getTime", SendTime) // 支持多端同时访问,相当于收到不同请求后未每个请求分别创建线程处理
fmt.Println("已向 DefaultServeMux 注册 SendTime 事件")
// 打开要侦听的前端请求连接端口
err := http.ListenAndServe(":13000", nil) // 这里是接受对本机所有的通过13000的请求,本机有多个IP则可在端口号前加IP地址区分,如"10.11.100.123:13000"
if err != nil {
fmt.Println("端口侦听错误:", err)
}
}
func SendTime(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Print("升级websocket连接错误:", err)
return
}
defer c.Close()
// 接受消息
mt, message, err := c.ReadMessage()
if err != nil {
fmt.Println("接收错误:", err)
} else {
fmt.Println("接收到前端消息:", string(message))
// 发送问候
var sendbuff string = "Hello Client!"
c.WriteMessage(mt, []byte(sendbuff)) // 为阅读方便,省去错误处理
// 不停地循环发送时间
fmt.Println("开始发送时间")
for {
sendbuff = time.Now().Format("2006-01-02 15:04:05")
err = c.WriteMessage(mt, []byte(sendbuff))
if err != nil {
fmt.Println("发送错误:", err)
break
}
fmt.Println("发送时间:", sendbuff)
time.Sleep(1 * time.Second) // 间隔1s,降低资源消耗,如果想提高实时性可减少时间间隔
}
fmt.Println("停止发送时间")
}
}
先启动服务器,再打开test.html并打开调试,点击网页上的“连接”按钮,观察控制台与服务器运行窗口(这里是vscode的集成终端)。
浏览器显示
服务器运行
问题表现:
问题细节:
问题原因:
web端的websocket参数与服务端不一致,请求的连接没有触发服务端的事件响应
http.HandleFunc("/echo", echo)
// 函数的第一个传入参数为事件名称(从http请求中读取),第二个传入参数为事件
// 事件名称默认为"/"
// 函数格式(附):
func http.HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
// 原始代码
Server_Com = new WebSocket("ws://127.0.0.1:12033");
解决:
修改服务端或web端代码均可,以使两端事件统一
// 修改为
http.HandleFunc("/", echo)
// 修改
Server_Com = new WebSocket("ws://127.0.0.1:12033/echo");