使用Gin框架实现WebSocket服务器

150 min read
package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

func main() {
	r := gin.Default()

	r.GET("/ws", func(c *gin.Context) {
		// 升级HTTP请求为WebSocket协议
		conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
		if err != nil {
			log.Println("upgrade:", err)
			return
		}
		defer conn.Close()

		// 循环读取客户端发送的消息
		for {
			messageType, message, err := conn.ReadMessage()
			if err != nil {
				log.Println("read:", err)
				return
			}
			log.Printf("recv: %s", message)

			// 将消息发送回客户端
			err = conn.WriteMessage(messageType, message)
			if err != nil {
				log.Println("write:", err)
				return
			}
		}
	})

	// 启动HTTP服务
	if err := r.Run(":8080"); err != nil {
		log.Fatal("failed to start server:", err)
	}
}

// 使用默认的Upgrader
var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

在这个示例中,使用Gin框架创建了一个HTTP路由/ws,并在路由处理函数中将HTTP请求升级为WebSocket协议。在处理函数中,循环读取客户端发送的消息,并将其发送回客户端。请注意,示例中使用了默认的websocket.Upgrader,但您也可以根据需要进行配置。

启动服务器后,您可以使用WebSocket客户端连接到ws://localhost:8080/ws地址,并向服务器发送消息。服务器将接收到消息,并将其发送回客户端。

Gin中实现WebSocket连接的认证

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

// 客户端
type Client struct {
	conn *websocket.Conn
}

// WebSocket连接池
type Pool struct {
	clients map[*Client]bool
}

// 客户端读取数据
func (c *Client) read() {
	for {
		_, message, err := c.conn.ReadMessage()
		if err != nil {
			log.Println("read:", err)
			delete(pool.clients, c)
			return
		}
		log.Printf("recv: %s", message)
	}
}

// 客户端写入数据
func (c *Client) write() {
	for {
		message := time.Now().String()
		err := c.conn.WriteMessage(websocket.TextMessage, []byte(message))
		if err != nil {
			log.Println("write:", err)
			delete(pool.clients, c)
			return
		}
		time.Sleep(1 * time.Second)
	}
}

// WebSocket连接池广播消息
func (p *Pool) broadcast() {
	for {
		message := time.Now().String()
		for client := range p.clients {
			err := client.conn.WriteMessage(websocket.TextMessage, []byte(message))
			if err != nil {
				log.Println("write:", err)
				delete(p.clients, client)
			}
		}
		time.Sleep(1 * time.Second)
	}
}

// 中间件,实现WebSocket连接的认证
func Authenticate() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 根据需要实现自己的认证逻辑
		username := c.Query("username")
		password := c.Query("password")
		if username == "admin" && password == "123456" {
			c.Next()
		} else {
			c.AbortWithStatus(http.StatusUnauthorized)
		}
	}
}

func main() {
	r := gin.Default()

	// 添加WebSocket连接认证中间件
	r.Use(Authenticate())

	pool := &Pool{
		clients: make(map[*Client]bool),
	}

	upgrader := websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}

	r.GET("/ws", func(c *gin.Context) {
		conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
		if err != nil {
			log.Println("upgrade:", err)
			return
		}
		client := &Client{conn: conn}
		pool.clients[client] = true
		go client.read()
		go client.write()
	})

	// 启动WebSocket连接池广播
	go pool.broadcast()

	// 启动HTTP服务
	if err := r.Run(":8080"); err != nil {
		log.Fatal("failed to start server:", err)
	}
}

在Gin中使用协程处理WebSocket连接

package main

import (
	"fmt"
	"log"
	"net/http"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

// 客户端
type Client struct {
	conn *websocket.Conn
}

// WebSocket连接池
type Pool struct {
	clients map[*Client]bool
	mutex   sync.Mutex
}

// 客户端读取数据
func (c *Client) read(pool *Pool) {
	defer func() {
		pool.mutex.Lock()
		delete(pool.clients, c)
		pool.mutex.Unlock()
		c.conn.Close()
	}()

	for {
		_, message, err := c.conn.ReadMessage()
		if err != nil {
			log.Println("read:", err)
			break
		}
		log.Printf("recv: %s", message)
	}
}

// 客户端写入数据
func (c *Client) write() {
	for {
		message := time.Now().String()
		err := c.conn.WriteMessage(websocket.TextMessage, []byte(message))
		if err != nil {
			log.Println("write:", err)
			break
		}
		time.Sleep(1 * time.Second)
	}
}

// WebSocket连接池广播消息
func (p *Pool) broadcast() {
	for {
		message := time.Now().String()

		p.mutex.Lock()
		for client := range p.clients {
			err := client.conn.WriteMessage(websocket.TextMessage, []byte(message))
			if err != nil {
				log.Println("write:", err)
				delete(p.clients, client)
			}
		}
		p.mutex.Unlock()

		time.Sleep(1 * time.Second)
	}
}

func main() {
	r := gin.Default()

	pool := &Pool{
		clients: make(map[*Client]bool),
	}

	upgrader := websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}

	r.GET("/ws", func(c *gin.Context) {
		conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
		if err != nil {
			log.Println("upgrade:", err)
			return
		}
		client := &Client{conn: conn}
		pool.mutex.Lock()
		pool.clients[client] = true
		pool.mutex.Unlock()
		go client.read(pool)
		go client.write()
	})

	// 启动WebSocket连接池广播
	go pool.broadcast()

	// 启动HTTP服务
	if err := r.Run(":8080"); err != nil {
		log.Fatal("failed to start server:", err)
	}
}

使用了sync.Mutex来实现线程安全的WebSocket连接池。在每个客户端读取数据时,使用了defer语句释放了连接资源,并使用了sync.Mutex来保证并发安全。在广播消息时,也使用了sync.Mutex来保证并发安全。

在Go中,sync.Mutex是一种互斥锁,用于保护共享资源,防止并发访问时出现数据竞争问题。在并发程序中,当多个协程同时访问某个共享资源时,可能会出现竞争条件,导致程序行为出现不可预测的结果。使用sync.Mutex可以保证在同一时间只有一个协程可以访问共享资源,从而避免竞争条件的出现。

在上述示例中,pool.mutex.Lock()用于获取互斥锁,防止多个协程同时访问Pool结构体中的clients字段,保证对该字段的并发访问是安全的。在Pool结构体中,我们使用sync.Mutex来实现对clients字段的并发访问控制。当一个协程获取了该锁后,其他协程必须等待该协程释放锁之后才能继续获取锁并访问共享资源。这样就可以确保在同一时间只有一个协程可以访问clients字段,从而避免出现竞争条件。

当访问共享资源的代码执行完毕时,需要使用pool.mutex.Unlock()释放互斥锁,以便其他协程可以获取锁并访问共享资源。在使用sync.Mutex时,需要特别注意,如果未能正确地释放互斥锁,可能会导致程序出现死锁问题。因此,通常建议使用defer语句在访问共享资源的代码块前面加上pool.mutex.Lock(),在代码块结束时自动释放互斥锁,以保证互斥锁正确释放。