前言
下面我们将一步步构建一个基于Go语言的WebSocket服务器,它将与一个响应式的Web前端页面协同工作,以实现数据的实时更新和高效通信。本项目不仅涵盖了WebSocket的基础概念,还深入探讨了服务器的架构设计和前端的动态数据处理。
服务端代码
创建一个文件夹 websocket_server,然后初始化
go mod init example/websocket_server
创建 main.go 并添加代码
package main
import (
"fmt"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
var (
upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true // 允许所有来源的WebSocket连接
},
}
clients = make([]*websocket.Conn, 0) // 存储所有客户端的切片
mutex sync.Mutex // 定义互斥锁
)
// broadcast 发送消息给所有连接的客户端,但不包括发送消息的客户端
func broadcast(msg []byte, exclude *websocket.Conn) {
mutex.Lock()
defer mutex.Unlock()
for _, conn := range clients {
// if conn != exclude { // 不包括发送消息的客户端
err := conn.WriteMessage(websocket.TextMessage, msg)
if err != nil {
// 处理错误,例如关闭连接或记录错误
fmt.Println("Error writing to client:", err)
}
// }
}
}
func handleWebsocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
return
}
defer conn.Close()
// 将新的客户端添加到客户端列表中
mutex.Lock()
clients = append(clients, conn)
mutex.Unlock()
for {
_, message, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
fmt.Printf("Error during read: %v", err)
}
break
}
fmt.Printf("Received message from %s: %s\n", conn.RemoteAddr(), string(message))
// 广播消息到所有客户端,除了发送消息的客户端
broadcast(message, conn)
}
// 从客户端列表中移除当前客户端
mutex.Lock()
for i, c := range clients {
if c == conn {
clients = append(clients[:i], clients[i+1:]...)
break
}
}
mutex.Unlock()
}
func main() {
http.HandleFunc("/ws", handleWebsocket)
fmt.Println("Starting server on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Println("ListenAndServe:", err)
}
}
然后执行以下命令获取依赖
go get .
执行
go run .
客户端代码
创建一个 html 文件,websocket.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket Client</title>
</head>
<body>
<h1>WebSocket Client</h1>
<p>Send a message to the server:</p>
<input type="text" id="messageInput" placeholder="Type your message here">
<button id="sendButton">Send</button>
<p>Last message from server: <span id="lastMessage">None</span></p>
<script>
var socket = new WebSocket('ws://localhost:8080/ws'); // Replace with your server address
// Attach event handlers to the WebSocket object
socket.addEventListener('open', function(event) {
console.log('Connected to WebSocket server.');
});
socket.addEventListener('message', function(event) {
console.log('Message from server:', event.data);
document.getElementById('lastMessage').textContent = event.data;
});
socket.addEventListener('error', function(event) {
console.error('WebSocket error:', event);
});
socket.addEventListener('close', function(event) {
console.log('WebSocket connection closed:', event);
});
// Send message when the 'Send' button is clicked
document.getElementById('sendButton').addEventListener('click', function() {
var message = document.getElementById('messageInput').value;
socket.send(message);
});
</script>
</body>
</html>
可以同时打开两个页面,A 和 B,在 A 输入框中输入数据,B 会看到同步更新的数据
当然也可以使用 go 实现一个客户端,往 socket 发送数据,代码如下
package main
import (
"fmt"
"log"
"github.com/gorilla/websocket"
)
func main() {
url := "ws://localhost:8080/ws"
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Fatal("Dial failed:", err)
}
defer conn.Close()
// 发送消息到服务器
message := []byte("Hello, Server!")
err = conn.WriteMessage(websocket.TextMessage, message)
if err != nil {
log.Println("Write error:", err)
return
}
// 接收服务器的响应
_, p, err := conn.ReadMessage()
if err != nil {
log.Println("Read error:", err)
return
}
fmt.Printf("Received message: %s\n", p)
}
使用 go run . 执行命令后,会发送“Hello,server!”到 socket,页面也会同步更新
打开 F12 也可以看到具体的数据传输过程
完整代码可以带 github 上获取