avatar

mdo

Hello

  • 首页
  • 知识库
  • 归档
  • 标签
  • 关于
主页 Golang 中,结合使用 RPC(远程过程调用)与 WebSocket
文章

Golang 中,结合使用 RPC(远程过程调用)与 WebSocket

发表于 2026-06-19 更新于 2026-06- 19
作者 mdo
16~21 分钟 阅读

在 Golang 中,结合使用 RPC(远程过程调用)与 WebSocket 是一种极佳的实时全双工通信方案。它不仅克服了传统 gRPC 对浏览器支持不佳的痛点,还能利用 WebSocket 的长连接特性消除频繁建立 HTTP 连接的开销。 [1, 2, 3]

常见的实现方案主要基于 JSON-RPC 2.0 协议,并配合经典的 gorilla/websocket(或现代的 nhooyr.io/websocket)库。 [1, 2, 3, 4, 5]


🛠️ 核心架构与原理

  1. 握手阶段:客户端发起标准 HTTP 请求,通过 Upgrade 头将连接升级为 WebSocket。

  2. 传输阶段:双端复用此 TCP 长连接,将带有 id、method 和 params 的 JSON-RPC 载荷封装在 WebSocket 文本/二进制帧中发送。 [1, 2, 3, 4, 5]


💻 完整代码实现

以下是一个基于 JSON-RPC 标准的高效生产级示例:

1. 服务端实现 (server.go) [1, 2]

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]

技术
许可协议:  CC BY 4.0
分享

相关文章

7月 26, 2026

完美地解决 TP3 老系统数据的平滑读取

为了彻底、完美地解决 TP3 老系统数据的平滑读取,必须解决两个核心痛点: TP3 的盲猜反序列化机制(标量存原样,数组/对象存原生 serialize,同时兼容 json)。 TP3 与 TP6 的缓存前缀(Prefix)不一致。 以下是为你量身定制的终极落地解决方案,通

7月 26, 2026

thinkphp3 redis序列化和反序列化

在老系统重构迁移至 ThinkPHP 6 (TP6) 的过程中,ThinkPHP 3 (TP3) 的 Redis 序列化历史遗留问题是最核心的连环坑之一。 TP3 的 S('key', $value) 缓存方法和底层 Redis 驱动,在存储非字符串(如数组、对象)时,有一套独特的序列化行为。如果不

7月 24, 2026

Table 空间极易发生哈希冲突并溢出

既然你没有在业务中主动调用 Room,只使用了全局广播,那么这个问题就非常明确了:即使你不用房间功能,think-swoole 只要开启了 WebSocket,底层就会强行初始化并注入一个名为 Room 的驱动组件。 [1] 框架底层默认使用 table 类型来作为 Room 的存储介质。当进行全局

下一篇

自己编写一个自定义的 MCP 服务器

上一篇

开发一个博客文章发布 App

最近更新

  • 完美地解决 TP3 老系统数据的平滑读取
  • thinkphp3 redis序列化和反序列化
  • Table 空间极易发生哈希冲突并溢出
  • 将监控程序直接跑在云端
  • AI 驱动型 Facebook 群组关键词监控 Chrome 浏览器插件

热门标签

API CodeGeex Gitkraken Management Manticore Premiere Sublime Swoole ThinkPHP ThinkPHP5

目录

©2026 mdo. 保留部分权利。