Golang 中,结合使用 RPC(远程过程调用)与 WebSocket
在 Golang 中,结合使用 RPC(远程过程调用)与 WebSocket 是一种极佳的实时全双工通信方案。它不仅克服了传统 gRPC 对浏览器支持不佳的痛点,还能利用 WebSocket 的长连接特性消除频繁建立 HTTP 连接的开销。 [1, 2, 3]
常见的实现方案主要基于 JSON-RPC 2.0 协议,并配合经典的 gorilla/websocket(或现代的 nhooyr.io/websocket)库。 [1, 2, 3, 4, 5]
🛠️ 核心架构与原理
握手阶段:客户端发起标准 HTTP 请求,通过
Upgrade头将连接升级为 WebSocket。传输阶段:双端复用此 TCP 长连接,将带有
id、method和params的 JSON-RPC 载荷封装在 WebSocket 文本/二进制帧中发送。 [1, 2, 3, 4, 5]
💻 完整代码实现
以下是一个基于 JSON-RPC 标准的高效生产级示例:
go
package main
import (
"crypto/rand"
"encoding/json"
"fmt"
"log"
"math/big"
"net/http"
"github.com/gorilla/websocket"
)
// 定义 RPC 请求与响应结构体
type RPCRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
ID interface{} `json:"id"`
}
type RPCResponse struct {
JSONRPC string `json:"jsonrpc"`
Result interface{} `json:"result,omitempty"`
Error interface{} `json:"error,omitempty"`
ID interface{} `json:"id"`
}
// 定义服务业务参数
type MathArgs struct {
A, B int
}
var upgrader = websocket.Upgrader{
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.Println("升级连接失败:", err)
return
}
defer conn.Close()
for {
// 读取客户端发送的 WebSocket 消息
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("读取消息失败:", err)
break
}
var req RPCRequest
if err := json.Unmarshal(message, &req); err != nil {
sendError(conn, nil, "解析 JSON 失败")
continue
}
// 简单的路由分发处理器 (RPC Dispatcher)
var result interface{}
switch req.Method {
case "Math.Multiply":
var args MathArgs
json.Unmarshal(req.Params, &args)
result = args.A * args.B
case "Crypto.GenerateID":
nBig, _ := rand.Int(rand.Reader, big.NewInt(100000))
result = nBig.Int64()
default:
sendError(conn, req.ID, "未找到该方法")
continue
}
// 返回成功响应
resp := RPCResponse{JSONRPC: "2.0", Result: result, ID: req.ID}
respBytes, _ := json.Marshal(resp)
conn.WriteMessage(websocket.TextMessage, respBytes)
}
}
func sendError(conn *websocket.Conn, id interface{}, msg string) {
resp := RPCResponse{
JSONRPC: "2.0",
Error: map[string]string{"message": msg},
ID: id,
}
respBytes, _ := json.Marshal(resp)
conn.WriteMessage(websocket.TextMessage, respBytes)
}
func main() {
http.HandleFunc("/rpc", handleWebSocket)
log.Println("RPC WebSocket 服务器已在 :8080/rpc 启动...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
请谨慎使用此类代码。
2. 客户端实现 (client.go)
go
package main
import (
"encoding/json"
"fmt"
"log"
"net/url"
"github.com/gorilla/websocket"
)
func main() {
u := url.URL{Scheme: "ws", Host: "localhost:8080", Path: "/rpc"}
log.Printf("正在连接到 %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("连接失败:", err)
}
defer c.Close()
// 构造 RPC 调用参数
params, _ := json.Marshal(map[string]int{"A": 6, "B": 7})
req := map[string]interface{}{
"jsonrpc": "2.0",
"method": "Math.Multiply",
"params": json.RawMessage(params),
"id": 1,
}
reqBytes, _ := json.Marshal(req)
err = c.WriteMessage(websocket.TextMessage, reqBytes)
if err != nil {
log.Fatal("发送失败:", err)
}
// 接收服务器响应
_, message, err := c.ReadMessage()
if err != nil {
log.Fatal("读取失败:", err)
}
fmt.Printf("收到服务器 RPC 响应: %s\n", message)
}
请谨慎使用此类代码。
⚖️ 工业界生产级方案替代
如果你不想在底层自己手写框架并处理复杂的连接复用(Multiplexing),可以使用以下成熟生态: [1]
区块链生态的选择 (Web3 / Ethereum):直接引用 go-ethereum/rpc。以太坊官方高度优化了基于 WebSocket 的 JSON-RPC 服务器与客户端代码,支持发布/订阅 (Pub/Sub) 模式。
企业级高并发双向 RPC:推荐 Chainlink 开源的 smartcontractkit/wsrpc。该库在 WebSockets 之上构建了一个带有 mTLS 强安全认证的全双工 RPC 传输层,性能和安全性极高。
轻量级自定义服务生成:如果更倾向于使用契约(Protobuf),可以尝试 toba/wsrpc,它可以通过 Protobuf 文件直接生成基于 WebSocket 运行的 Go 服务代码。 [1, 2, 3, 4, 5]