WebSocket 提供全双工通信能力,适合实时应用。Gin 可以配合 gorilla/websocket 实现 WebSocket 功能。
package main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func main() {
r := gin.Default()
r.GET("/ws", func(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Printf("WebSocket upgrade error: %v", err)
return
}
defer conn.Close()
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Printf("Read error: %v", err)
break
}
log.Printf("Received: %s", message)
err = conn.WriteMessage(messageType, message)
if err != nil {
log.Printf("Write error: %v", err)
break
}
}
})
r.Run(":8080")
}
type Client struct {
Conn *websocket.Conn
Send chan []byte
}
type ChatRoom struct {
Clients map[*Client]bool
Register chan *Client
Unregister chan *Client
Broadcast chan []byte
}
func NewChatRoom() *ChatRoom {
return &ChatRoom{
Clients: make(map[*Client]bool),
Register: make(chan *Client),
Unregister: make(chan *Client),
Broadcast: make(chan []byte),
}
}
func (r *ChatRoom) Run() {
for {
select {
case client := <-r.Register:
r.Clients[client] = true
case client := <-r.Unregister:
if _, ok := r.Clients[client]; ok {
delete(r.Clients, client)
close(client.Send)
}
case message := <-r.Broadcast:
for client := range r.Clients {
select {
case client.Send <- message:
default:
close(client.Send)
delete(r.Clients, client)
}
}
}
}
}
func (c *Client) ReadPump(room *ChatRoom) {
defer func() {
room.Unregister <- c
c.Conn.Close()
}()
for {
_, message, err := c.Conn.ReadMessage()
if err != nil {
break
}
room.Broadcast <- message
}
}
func (c *Client) WritePump() {
defer c.Conn.Close()
for message := range c.Send {
if err := c.Conn.WriteMessage(websocket.TextMessage, message); err != nil {
break
}
}
}
var chatRoom = NewChatRoom()
func main() {
go chatRoom.Run()
r := gin.Default()
r.GET("/ws/chat", func(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
client := &Client{
Conn: conn,
Send: make(chan []byte, 256),
}
chatRoom.Register <- client
go client.WritePump()
client.ReadPump(chatRoom)
})
r.Run(":8080")
}
const (
writeWait = 10 * time.Second
pongWait = 60 * time.Second
pingPeriod = (pongWait * 9) / 10
maxMessageSize = 512
)
func (c *Client) ReadPumpWithPing(room *ChatRoom) {
defer func() {
room.Unregister <- c
c.Conn.Close()
}()
c.Conn.SetReadLimit(maxMessageSize)
c.Conn.SetReadDeadline(time.Now().Add(pongWait))
c.Conn.SetPongHandler(func(string) error {
c.Conn.SetReadDeadline(time.Now().Add(pongWait))
return nil
})
for {
_, message, err := c.Conn.ReadMessage()
if err != nil {
break
}
room.Broadcast <- message
}
}
func (c *Client) WritePumpWithPing() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.Conn.Close()
}()
for {
select {
case message, ok := <-c.Send:
c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
if !ok {
c.Conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
c.Conn.WriteMessage(websocket.TextMessage, message)
case <-ticker.C:
c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}
}
const socket = new WebSocket('ws://localhost:8080/ws');
socket.onopen = function(e) {
console.log('Connected');
socket.send('Hello Server');
};
socket.onmessage = function(event) {
console.log('Received:', event.data);
};
socket.onclose = function(event) {
console.log('Disconnected');
};
socket.onerror = function(error) {
console.log('Error:', error);
};
WebSocket 适合实时通信场景。使用 gorilla/websocket 可以轻松在 Gin 中实现 WebSocket 功能。注意处理连接关闭、心跳检测和并发安全。