使用 Golang 在 Knative 上构建 WebSocket 应用 ¶
发布日期:2024-09-11
使用 Golang 在 Knative 上构建 WebSocket 应用 ¶
作者:Matthias Weßendorf,红帽高级首席软件工程师
Knative 服务支持多种协议来运行无服务器工作负载,例如 HTTP
、HTTP/2
、gRPC
或 WebSocket
。 WebSocket 协议 允许在客户端应用程序和服务器之间通过单个 TCP 连接进行全双工通信。WebSocket 协议以 HTTP “升级”请求开始,然后将连接从 HTTP 切换到 WebSocket 协议,允许对等体之间进行实时双向通信。本文介绍了如何使用 Knative 构建可扩展的 WebSocket 服务器。
注意
此处讨论的示例应用程序可以在 Knative 文档存储库 中找到。
HTTP 升级 ¶
由于每个 WebSocket 应用程序都以 HTTP 升级请求开始,因此我们需要定义一个基于 Golang 的 Web 服务器来处理经典的 HTTP 请求。对于 WebSocket 实现,使用了 gorilla/websocket
包,该包提供了一种快速、经过良好测试且广泛使用的 Golang WebSocket 实现。以下是示例应用程序的 HTTP 部分
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket"
"github.com/knative/docs/code-samples/serving/websockets-go/pkg/handlers"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
}
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Error upgrading to websocket: %v", err)
return
}
handlers.OnOpen(conn)
go func() {
defer handlers.OnClose(conn)
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
handlers.OnError(conn, err)
break
}
handlers.OnMessage(conn, messageType, message)
}
}()
}
func main() {
http.HandleFunc("/ws", handleWebSocket)
fmt.Println("Starting server on :8080...")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Server error: %v", err)
}
}
main
函数在 /ws
上下文中启动 Web 服务器,并注册一个 handleWebSocket
处理函数
func main() {
http.HandleFunc("/ws", handleWebSocket)
fmt.Println("Starting server on :8080...")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Server error: %v", err)
}
}
handleWebSocket
执行协议升级,并分配各种 WebSocket 处理函数,例如 OnOpen
或 OnMessage
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Error upgrading to websocket: %v", err)
return
}
handlers.OnOpen(conn)
go func() {
defer handlers.OnClose(conn)
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
handlers.OnError(conn, err)
break
}
handlers.OnMessage(conn, messageType, message)
}
}()
}
注意
为了更好地分离关注点,不同的 On...
处理程序位于单独的 handlers
包中。
WebSocket 处理程序 ¶
WebSocket 应用程序逻辑位于 pkg/handlers/handlers.go
文件中,并包含针对每个 WebSocket 事件的回调
func OnOpen(conn *websocket.Conn) {
log.Printf("WebSocket connection opened: %v", conn.RemoteAddr())
}
func OnMessage(conn *websocket.Conn, messageType int, message []byte) {
log.Printf("Received message from %v: %s", conn.RemoteAddr(), string(message))
if err := conn.WriteMessage(messageType, message); err != nil {
log.Printf("Error sending message: %v", err)
}
}
func OnClose(conn *websocket.Conn) {
log.Printf("WebSocket connection closed: %v", conn.RemoteAddr())
conn.Close()
}
func OnError(conn *websocket.Conn, err error) {
log.Printf("WebSocket error from %v: %v", conn.RemoteAddr(), err)
}
在每个处理程序中,我们记录连接对等体的远程地址,以及通过 OnMessage
处理程序接收到的任何传入消息,并将该消息回显给发送者。
测试 WebSocket 服务器 ¶
一旦 Knative 服务应用程序部署到 Kubernetes 集群中,您就可以对其进行测试。为此,您需要接收 WebSocket 应用程序服务的实际 URL
kubectl get ksvc
NAME URL LATESTCREATED LATESTREADY READY REASON
websocket-server http://websocket-server.default.svc.cluster.local websocket-server-00001 websocket-server-00001 True
现在,我们需要一个客户端来连接到我们的服务。为此,我们在 Kubernetes 内部启动一个 Linux 容器,并运行 wscat 来连接到我们的 WebSocket 服务器应用程序
kubectl run --rm -i --tty wscat --image=monotykamary/wscat --restart=Never -- -c ws://websocket-server.default.svc.cluster.local/ws
之后,您可以像这样与 WebSocket 服务器聊天
```If you don't see a command prompt, try pressing enter.
```connected (press CTRL+C to quit)
```> Hello
```< Hello
```>
以上内容扩展到恰好一个 Pod,因为只有一个客户端连接。由于 Knative 服务允许您进行动态扩展,因此一定数量的并发连接会导致一定数量的 Pod。
以下是当对应用程序运行大量并发请求时,应用程序扩展的示例
k get pods
NAME READY STATUS RESTARTS AGE
websocket-server-00001-deployment-f785cbd65-42mrn 2/2 Running 0 16s
websocket-server-00001-deployment-f785cbd65-76bjr 2/2 Running 0 14s
websocket-server-00001-deployment-f785cbd65-98cwb 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-9fdbl 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-bpvjj 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-c6f47 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-fl6kk 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-gnffw 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-hqpfx 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-j4v9d 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-j72vk 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-k856w 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-kmmng 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-l4f2v 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-lpfr9 2/2 Running 0 14s
websocket-server-00001-deployment-f785cbd65-mn26w 2/2 Running 0 16s
websocket-server-00001-deployment-f785cbd65-mr98h 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-prjmj 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-v696v 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-ws5k9 2/2 Running 0 20s
websocket-server-00001-deployment-f785cbd65-xmszx 2/2 Running 0 18s
websocket-server-00001-deployment-f785cbd65-znhrr 2/2 Running 0 20s
注意
根据您拥有的目标注释(autoscaling.knative.dev/target
),您可以根据连接数进行扩展。