前言

下面我们将一步步构建一个基于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 上获取

https://github.com/zhengjianhong001/go_websocket