4、websocket

WebSocket基础概念

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

WebSocket在2011年被IETF标准化为RFC 6455,并由W3C定义了对应的JavaScript API。

WebSocket vs HTTP

传统的HTTP协议有以下局限性:

相比之下,WebSocket提供以下优势:

WebSocket工作原理

1、握手阶段:客户端发起HTTP请求,请求升级到WebSocket协议

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

2、服务器响应:如果服务器支持WebSocket,则返回升级确认

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

3、建立连接:握手成功后,HTTP连接升级为WebSocket连接,此后的通信遵循WebSocket协议

4、数据传输:使用帧(frames)进行数据传输,帧可以是文本帧或二进制帧

5、关闭连接:任何一方都可以发送关闭帧来关闭连接

Go语言中的WebSocket实现

Go 语言有两个主要的 WebSocket 库:

我们将主要关注 gorilla/websocket 库,因为它更加强大和易用。

安装

go get github.com/gorilla/websocket

基本概念

gorilla/websocket 中使用 Conn 来表示一个 WebSocket 连接,它主要有如下作用:

消息被分为以下几种:

WebSocket 服务端

package main

import (
	"github.com/gorilla/websocket"
	"io"
	"log"
	"net/http"
)

// 配置 WebSocket 升级器
var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	// 允许所有CORS请求,生产环境应当配置更严格的检查
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
	// 将 HTTP 连接升级为 WebSocket连接
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
	}
	defer conn.Close() // 关闭链接
	log.Println("WebSocket 客户端已连接", conn.RemoteAddr())

	// 无限循环,持续读取客户端消息
	for {
		// 读取消息
		messageType, message, err := conn.ReadMessage()
		if err != nil && err != io.EOF {
			log.Println("服务端读取消息错误:", err)
			break
		}
		log.Printf("收到客户端 %v 消息: %s", conn.RemoteAddr(), string(message))
		// 简单地将消息回送给客户端
		err = conn.WriteMessage(messageType, message)
		if err != nil {
			log.Println("服务端写入消息错误:", err)
			break
		}
	}
}

func main() {
	http.HandleFunc("/ws", handleWebSocket)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatalln(err)
	}
}

WebSocket 客户端

package main

import (
	"fmt"
	"github.com/gorilla/websocket"
	"log"
	"net/url"
	"sync"
)

func main() {
	url := url.URL{
		Scheme: "ws",
		Host:   "localhost:8080",
		Path:   "/ws",
	}
	// 创建一个 WebSocket 客户端链接
	conn, _, err := websocket.DefaultDialer.Dial(url.String(), nil)
	if err != nil {
		log.Println("无法连接 WebSocket 服务端:", url.String())
	}
	log.Println("连接服务器成功")
	wg := sync.WaitGroup{}
	// 启动一个 goroutine 接收消息
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			messageType, message, err := conn.ReadMessage()
			if err != nil {
				log.Println("客户端读取消息错误:", err)
				break
			}
			log.Println("收到服务端消息:", string(message), "type:", messageType)
		}
	}()
	// 另一个 goroutine 监听用户输入发送消息
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			var message string
			fmt.Scanln(&message)
			err := conn.WriteMessage(websocket.TextMessage, []byte(message))
			if err != nil {
				log.Println("客户端发送消息错误:", err)
				break
			}
		}
	}()
	wg.Wait()
}