《Go 简易速速上手小册》第8章:网络编程(2024 最新版)(上)+https://developer.aliyun.com/article/1487003
8.3 WebSocket 与 RPC - Go 语言的深海通信线
在Go语言的并发海洋中,WebSocket和RPC (Remote Procedure Call) 是两种深海通信线,允许我们跨越深渊,进行实时和跨服务的通信。
8.3.1 基础知识讲解
WebSocket
WebSocket提供了一个全双工通信渠道,允许客户端和服务器之间建立持久连接并实时交换数据。这对于需要实时功能的应用来说非常有用,比如在线聊天室、实时数据仪表板等。
在Go中,gorilla/websocket
是一个流行的库,用于在HTTP服务器上添加WebSocket支持。
RPC
RPC允许客户端执行远程服务器上的函数就像是执行本地函数一样,隐藏了网络请求的复杂性。Go标准库中的net/rpc
包提供了构建RPC系统的基础。
8.3.2 重点案例:实时聊天应用
在这个扩展案例中,我们将构建一个简单的实时聊天应用,使用WebSocket实现客户端和服务器之间的实时通信。这个应用将允许用户通过Web浏览器连接到聊天服务器,发送消息并实时接收来自其他用户的消息。
服务端实现
我们使用gorilla/websocket
库来处理WebSocket连接。首先,需要安装这个库:
go get -u github.com/gorilla/websocket
然后,我们实现聊天服务器的核心逻辑:
// chatserver/main.go package main import ( "github.com/gorilla/websocket" "net/http" "log" ) var clients = make(map[*websocket.Conn]bool) // 连接到服务器的客户端 var broadcast = make(chan []byte) // 广播通道 var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } func main() { http.HandleFunc("/ws", handleConnections) go handleMessages() log.Println("Chat server started on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } func handleConnections(w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Fatal(err) } defer ws.Close() clients[ws] = true for { _, msg, err := ws.ReadMessage() if err != nil { log.Printf("Error: %v", err) delete(clients, ws) break } broadcast <- msg } } func handleMessages() { for { msg := <-broadcast for client := range clients { err := client.WriteMessage(websocket.TextMessage, msg) if err != nil { log.Printf("Error: %v", err) client.Close() delete(clients, client) } } } }
在这个实现中,handleConnections
函数处理新的WebSocket连接,读取来自客户端的消息,并将它们放入broadcast
通道。handleMessages
函数监听broadcast
通道,将接收到的消息发送给所有连接的客户端。
客户端实现
客户端可以是一个简单的HTML页面,使用JavaScript与WebSocket服务器进行通信:
<!-- chatclient/index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Go Chat App</title> <script> document.addEventListener("DOMContentLoaded", function() { var ws = new WebSocket("ws://localhost:8080/ws"); var messages = document.getElementById("messages"); ws.onmessage = function(event) { var message = document.createElement("p"); message.textContent = event.data; messages.appendChild(message); }; document.getElementById("sendBtn").onclick = function() { var message = document.getElementById("messageInput").value; ws.send(message); document.getElementById("messageInput").value = ""; }; }); </script> </head> <body> <div id="messages"></div> <input id="messageInput" type="text"> <button id="sendBtn">Send</button> </body> </html>
这个HTML页面包含一个消息列表、一个文本输入框和一个发送按钮。当用户点击发送按钮时,当前的消息会通过WebSocket发送到服务器,并且清空输入框。当服务器通过WebSocket发送消息时,它们会被添加到页面的消息列表中。
运行示例
- 启动服务端:在
chatserver
目录下运行go run main.go
,启动聊天服务器。 - 打开客户端:在浏览器中打开
chatclient/index.html
文件,连接到聊天服务器。
通过这个案例,我们展示了如何使用Go和WebSocket构建一个简单的实时聊天应用。这个应用能够让多个用户通过Web界面实时交换消息,体验到实时通信的魅力。随着你继续探索WebSocket和Go在网络编程方面的更多可能性,你将能够构建更加复杂和强大的实时Web应用。
8.3.3 拓展案例 1:股票行情实时更新服务
在这个案例中,我们将构建一个基于WebSocket的股票行情实时更新服务。该服务允许客户端通过WebSocket订阅特定的股票代码,并在股票价格发生变动时接收实时更新。
服务端实现
为了简化演示,我们假设股票价格的变动是随机模拟的。在实际应用中,你可能需要连接到真实的股市数据API来获取实时行情。
首先,安装gorilla/websocket
库:
go get -u github.com/gorilla/websocket
接着,实现WebSocket服务端逻辑:
// stockservice/main.go package main import ( "encoding/json" "github.com/gorilla/websocket" "log" "math/rand" "net/http" "time" ) var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } type StockUpdate struct { Symbol string `json:"symbol"` Price float64 `json:"price"` } func handleConnections(w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Fatal(err) } defer ws.Close() // 模拟股票代码列表 stocks := []string{"AAPL", "GOOGL", "MSFT"} for { // 等待客户端消息并读取订阅的股票代码 _, msg, err := ws.ReadMessage() if err != nil { log.Printf("error: %v", err) break } symbol := string(msg) // 如果订阅的股票代码在模拟的列表中,开始发送更新 for _, s := range stocks { if s == symbol { for { // 随机生成股票价格并发送 price := rand.Float64() * 1000 update := StockUpdate{Symbol: symbol, Price: price} updateJSON, err := json.Marshal(update) if err != nil { log.Printf("error: %v", err) break } if err := ws.WriteMessage(websocket.TextMessage, updateJSON); err != nil { log.Printf("error: %v", err) break } // 每隔一秒发送一次更新 time.Sleep(1 * time.Second) } break } } } } func main() { rand.Seed(time.Now().UnixNano()) http.HandleFunc("/ws", handleConnections) log.Println("Stock service started on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }
客户端示例
客户端可以是一个简单的HTML页面,通过JavaScript与WebSocket服务器建立连接,并发送订阅请求:
<!-- stockclient/index.html --> <!DOCTYPE html> <html> <head> <title>Real-time Stock Updates</title> <script> document.addEventListener("DOMContentLoaded", () => { const ws = new WebSocket("ws://localhost:8080/ws"); ws.onopen = () => { console.log("Connected to the server"); // 订阅AAPL股票的更新 ws.send("AAPL"); }; ws.onmessage = (event) => { const stockUpdate = JSON.parse(event.data); console.log(`Stock update for ${stockUpdate.symbol}: $${stockUpdate.price.toFixed(2)}`); }; ws.onerror = (error) => { console.log("WebSocket error: " + error.message); }; ws.onclose = () => { console.log("Disconnected from the server"); }; }); </script> </head> <body> <h1>Real-time Stock Updates</h1> <p>Open the console to view the stock updates.</p> </body> </html>
在这个HTML页面中,当页面加载完成后,客户端通过WebSocket连接到服务器并发送一个订阅请求(在此例中为"AAPL"股票)。然后,它将监听服务器发送的更新,并在控制台中显示更新的股票价格。
运行示例
- 启动服务端:在
stockservice
目
录下运行go run main.go
,启动股票行情更新服务。
- 打开客户端:在Web浏览器中打开
stockclient/index.html
文件,并查看浏览器控制台以接收实时的股票价格更新。
通过这个拓展案例,我们演示了如何使用WebSocket在Go中实现一个实时的股票行情更新服务。客户端可以订阅感兴趣的股票代码,并接收关于这些股票的实时价格更新,展现了WebSocket在构建实时通信应用中的强大能力。继续探索WebSocket和Go的其他网络编程特性,为你的应用带来更加丰富的实时交互体验。
8.3.4 拓展案例 2:远程系统监控
在这个案例中,我们将构建一个基于RPC的远程系统监控工具,允许管理员从中央服务器调用远程机器上的函数,以获取系统状态信息,如CPU使用率、内存使用等。这个工具将使用Go的net/rpc
包来实现RPC通信。
功能描述
- 远程获取系统状态:允许管理员远程调用函数,获取目标机器的系统状态信息。
- 支持多种状态查询:支持查询CPU使用率、内存使用情况等不同类型的系统状态。
- 简单的认证机制:实现一个简单的认证机制,确保只有授权的用户可以查询系统状态。
服务端实现
首先,我们需要定义提供的远程调用方法和服务:
// monitorserver/main.go package main import ( "errors" "net" "net/rpc" "runtime" ) type Args struct { AuthToken string } type SystemStats struct { CPU string Memory string } type Monitor int func (t *Monitor) GetSystemStats(args *Args, reply *SystemStats) error { if args.AuthToken != "secret" { return errors.New("unauthorized") } // 模拟获取系统状态 reply.CPU = "2.4 GHz" reply.Memory = "8 GB" return nil } func main() { monitor := new(Monitor) rpc.Register(monitor) rpc.HandleHTTP() l, err := net.Listen("tcp", ":1234") if err != nil { panic(err) } for { conn, err := l.Accept() if err != nil { continue } go rpc.ServeConn(conn) } }
在这个实现中,我们定义了一个Monitor
类型,它有一个方法GetSystemStats
,用于远程获取系统状态。我们使用了一个简单的认证机制,通过检查传递的AuthToken
来授权访问。
客户端实现
客户端将通过RPC调用服务端的GetSystemStats
方法来获取系统状态:
// monitorclient/main.go package main import ( "fmt" "log" "net/rpc" ) type Args struct { AuthToken string } type SystemStats struct { CPU string Memory string } func main() { client, err := rpc.DialHTTP("tcp", "localhost:1234") if err != nil { log.Fatal("Dialing:", err) } args := &Args{"secret"} var reply SystemStats err = client.Call("Monitor.GetSystemStats", args, &reply) if err != nil { log.Fatal("Monitor error:", err) } fmt.Printf("CPU: %s\nMemory: %s\n", reply.CPU, reply.Memory) }
在这个客户端实现中,我们创建了一个RPC客户端,连接到服务端,并调用Monitor.GetSystemStats
方法,传递认证令牌并获取系统状态信息。
运行示例
- 启动服务端:在
monitorserver
目录下运行go run main.go
启动系统监控服务。 - 运行客户端:在
monitorclient
目录下运行go run main.go
来从服务端获取系统状态信息。
通过这个拓展案例,我们演示了如何使用Go的net/rpc
包实现一个基于RPC的远程系统监控工具。这个工具允许管理员远程获取系统状态信息,展现了RPC在构建分布式系统和服务通信中的实用性。继续探索Go的RPC和其他网络编程特性,为你的分布式应用提供强大的后端支持。