您的位置:

全能开发工程师之ginwebsocket详解

对于Web开发,Gin是一种非常出色的go语言框架,因为它具有出色的速度和抽象性。GinWebSocket是Gin框架的WebSocket包,允许在Gin中创建WebSocket端点。WebSocket是一种网络协议,它允许在客户端和服务器之间进行双向通信,并且非常适合需要实时通信的应用程序。

一、快速入门

在gin中的WebSocket处理过程中,要引入github.com/gin-gonic/gin、github.com/gin-gonic/gin/binding、github.com/gorilla/websocket这三个包。通过对这些包的导入,我们可以轻松地在网站上实现WebSocket。

   import (
	   "net/http"
	   "github.com/gin-gonic/gin"
	   "github.com/gin-gonic/gin/binding"
	   "github.com/gorilla/websocket"
   )

   //此处使用默认Engine
   var upGrader = websocket.Upgrader{
	   ReadBufferSize:  1024,
	   WriteBufferSize: 1024,
   }

   func Websocket(c *gin.Context) {
	   conn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
	   if err != nil {
		   fmt.Println(err)
		   return
	   }
   }

上述代码主要用于处理来自客户端的请求,首先使用 gin.Context 来获得 http.ResponseWriter 和 *http.Request,这些内容将用于WebSocket握手。

接下来,我们使用 gorilla/websocket 中的Upgrader类型来创建WebSocket连接并从其中获取来自客户端的消息和断开连接请求。

二、创建Websocket客户端

在Gin中,我们可以通过在浏览器中使用Javascript中的WebSocket对象来创建WebSocket客户端。

	function connect() {
		var socket = new WebSocket('ws://127.0.0.1:8080/ws');
		socket.onmessage = function (msg) {
			console.log('Received message from server: ' + msg.data);
		};
		socket.onerror = function (error) {
			console.error('WebSocket error: ' + error);
		};
		socket.onclose = function () {
			console.log('WebSocket connection closed');
		};

		socket.onopen = function (event) {
			// Send an initial message
			socket.send('I am the client and I'm listening!');
		};
	}
	connect();

在上面的示例代码中,我们可以看到如何使用Javascript来连接到WebSocket服务器。浏览器使用WebSocket对象并传递要连接的服务器的地址,然后通过调用onopen、onmessage、onerror和onclose函数进行初始设置。

三、WebSocket中间件

GinWebSocket允许您使用WebSocket中间件针对使用WebSocket的请求提供通用处理程序。在使用WebSocket中间件时,将在WebSocket处理程序中自动注入WebsocketContext。

	func main() {
		r := gin.Default()
		r.GET("/", func(c *gin.Context) {
			c.JSON(200, gin.H{
				"message": "Hello world!",
			})
		})

		r.Use(WebsocketMiddleware())
		r.GET("/websocket", WebsocketHandler)

		r.Run(":8080")
	}

	func WebsocketMiddleware() gin.HandlerFunc {
		return func(c *gin.Context) {
			conn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
			if err != nil {
				http.NotFound(c.Writer, c.Request)
				return
			}
			wc := &WebsocketContext{conn}
			c.Set("ws", wc)
			err = c.Next()
			if err != nil {
				wc.Close()
			}
		}
	}

在上面的示例代码中,我们使用WebsocketMiddleware在请求进入WebSocket处理程序之前对其进行处理。然后我们使用Set方法设置一个WebSocket上下文,以便在WebsocketHandler中访问该上下文。

四、广播

在许多场景中,您可能希望将消息广播给多个WebSocket客户端。使用 ginWebSocket,我们可以轻松地实现这一点。

	var clients = make(map[*websocket.Conn]bool) // connected clients
	var broadcast = make(chan Message)           // broadcast channel

	type Message struct {
		Message string `json:"message"`
	}

	func main() {
		r := gin.Default()
		r.Use(WebsocketMiddleware())
		r.GET("/ws", WebsocketHandler)

		go handleMessages()

		r.Run(":8080")
	}

	func handleMessages() {
		for {
			// Grab the next message from the broadcast channel
			msg := <-broadcast

			// Send it out to every client that is currently connected
			for client := range clients {
				err := client.WriteJSON(msg)
				if err != nil {
					log.Printf("error: %v", err)
					client.Close()
					delete(clients, client)
				}
			}
		}
	}

	func WebsocketHandler(c *gin.Context) {
		conn, _ := upGrader.Upgrade(c.Writer, c.Request, nil)

		clients[conn] = true

		for {
			var msg Message
			err := conn.ReadJSON(&msg)
			if err != nil {
				log.Printf("error: %v", err)
				delete(clients, conn)
				break
			}
			broadcast <- msg
		}
	}

在上面的示例代码中,我们使用一个全局 map 变量 clients 来存储客户端连接,并且通过一个广播通道 broadcast 进行消息广播。

在WebsocketHandler中,我们使用 ReadJSON 方法从连接中读取消息,并将该消息发送到广播通道中;在handleMessages中,我们使用 range 语句循环所有连接并将消息发回给它们所有人。

五、多线程中协同工作

对于更大的应用程序,我们可能希望使用多个goroutine来处理WebSocket链接。当多个goroutine同时处理WebSocket请求时,我们需要确保它们之间通信的正确性。在这些情况下,可以使用goroutine-safe通道来确保正常的协同工作。

	var clients = make(map[*websocket.Conn]bool) // connected clients
	var broadcast = make(chan Message)           // broadcast channel

	type Message struct {
		Message string `json:"message"`
	}

	func main() {
		r := gin.Default()
		r.Use(WebsocketMiddleware())
		r.GET("/ws", WebsocketHandler)

		go handleMessages()

		r.Run(":8080")
	}

	func handleMessages() {
		for {
			// Grab the next message from the broadcast channel
			msg := <-broadcast

			// Send it out to every client that is currently connected
			for client := range clients {
				err := client.WriteJSON(msg)
				if err != nil {
					log.Printf("error: %v", err)
					client.Close()
					delete(clients, client)
				}
			}
		}
	}

	func WebsocketHandler(c *gin.Context) {
		conn, _ := upGrader.Upgrade(c.Writer, c.Request, nil)

		clients[conn] = true

		go func() {
			for {
				var msg Message
				err := conn.ReadJSON(&msg)
				if err != nil {
					log.Printf("error: %v", err)
					broadcast <- Message{Message: "A client has disconnected."}
					delete(clients, conn)
					return
				}
				msg.Message = fmt.Sprintf("Message received from %v: %v", conn.RemoteAddr(), msg.Message)
				broadcast <- msg
			}
		}()
	}

在上面的示例代码中,我们使用一个goroutine来处理conn.ReadJSON(),并在goroutine-safe通道中发送读取的消息。这是为了确保多个线程同时处理WebSocket请求时可以进行正确的协调和通信。

总结

通过这篇文章,您应该已经能够了解如何在 Gin 中使用 ginWebSocket。您还可以使用 ginWebSocket 提供的一些其他功能,例如在应用程序中使用更高效的二进制消息,以便更快地发送和接收大量数据。同时,在代码设计过程中一定要注意线程安全问题,保证多个goroutine之间的正确协作。