安装

go get -u github.com/gin-gonic/gin
go get github.com/gorilla/websocket

单聊

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"sync"

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

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

// 用户结构体
type User struct {
	ID   string
	Conn *websocket.Conn
}

// 用户管理器
type UserManager struct {
	sync.Mutex
	Users map[string]*User
}

var userManager = UserManager{
	Users: make(map[string]*User),
}

// 用户登录
func login(c *gin.Context) {
	userID := c.Query("userID")

	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "升级连接失败"})
		return
	}

	user := &User{
		ID:   userID,
		Conn: conn,
	}

	userManager.Lock()
	userManager.Users[userID] = user
	userManager.Unlock()

	// 处理WebSocket通信
	go handleWebSocket(user)
}

// 接受发送过来json数据
type MyData struct {
	Touser string `json:"touser"`
	Msg    string `json:"msg"`
}

// 处理WebSocket通信
func handleWebSocket(user *User) {
	defer user.Conn.Close()

	for {
		messageType, p, err := user.Conn.ReadMessage()
		// fmt.Println(messageType)
		if err != nil {
			// 用户下线
			userManager.Lock()
			delete(userManager.Users, user.ID)
			userManager.Unlock()
			break
		}

		//解析json数据
		var data MyData
		if err := json.Unmarshal(p, &data); err != nil {
			fmt.Printf("解析json错误: %v", err)
		}

		// 广播消息
		userManager.Lock()
		for _, u := range userManager.Users {
			if u.ID == data.Touser {
				err = u.Conn.WriteMessage(messageType, p)
				if err != nil {
					// 处理错误
				}
			}
		}
		userManager.Unlock()
	}
}

func main() {
	r := gin.Default()
	r.GET("/login", login)
	r.Run(":8080")
}

测试

多开几个窗口改变userID,touser要发送给的用户id,msg消息内容 

群聊 

package main

import (
	"net/http"
	"sync"

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

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

// 用户结构体
type User struct {
	ID     string
	Conn   *websocket.Conn
	RoomID string
}

// 用户管理器
type UserManager struct {
	sync.Mutex
	Users map[string]*User
}

var userManager = UserManager{
	Users: make(map[string]*User),
}

// 用户登录
func login(c *gin.Context) {
	userID := c.Query("userID")
	roomID := c.Query("roomID")

	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "升级连接失败"})
		return
	}

	user := &User{
		ID:     userID,
		Conn:   conn,
		RoomID: roomID,
	}

	userManager.Lock()
	userManager.Users[userID] = user
	userManager.Unlock()

	// 处理WebSocket通信
	go handleWebSocket(user)
}

// 处理WebSocket通信
func handleWebSocket(user *User) {
	defer user.Conn.Close()

	for {
		messageType, p, err := user.Conn.ReadMessage()
		if err != nil {
			// 用户下线
			userManager.Lock()
			delete(userManager.Users, user.ID)
			userManager.Unlock()
			break
		}

		// 广播消息
		userManager.Lock()
		for _, u := range userManager.Users {
			if u.RoomID == user.RoomID {
				err = u.Conn.WriteMessage(messageType, p)
				if err != nil {
					// 处理错误
				}
			}
		}
		userManager.Unlock()
	}
}

func main() {
	r := gin.Default()
	r.GET("/login", login)
	r.Run(":8080")
}

测试

多开几个窗口改变userID,roomID是房间号,一个房间的可以聊天

心跳机制 

package main

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

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

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

// 用户结构体
type User struct {
	ID       string
	Conn     *websocket.Conn
	RoomID   string
	LastPong time.Time // 用于记录最后一个Pong时间
}

// 用户管理器
type UserManager struct {
	sync.Mutex
	Users map[string]*User
}

var userManager = UserManager{
	Users: make(map[string]*User),
}

// 用户登录
func login(c *gin.Context) {
	userID := c.Query("userID")
	roomID := c.Query("roomID")

	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "升级连接失败"})
		return
	}

	user := &User{
		ID:     userID,
		Conn:   conn,
		RoomID: roomID,
	}

	userManager.Lock()
	userManager.Users[userID] = user
	userManager.Unlock()

	// 开始心跳
	go startHeartbeat(user)

	// 处理WebSocket通信
	go handleWebSocket(user)
}

// 处理WebSocket通信
func handleWebSocket(user *User) {
	defer user.Conn.Close()

	for {
		messageType, p, err := user.Conn.ReadMessage()
		if err != nil {
			// 用户下线
			userManager.Lock()
			delete(userManager.Users, user.ID)
			userManager.Unlock()
			break
		}

		// 重置最后的Pong时间
		if string(p) == "ping" {
			user.LastPong = time.Now()
		}

		// 广播消息
		userManager.Lock()
		for _, u := range userManager.Users {
			if u.RoomID == user.RoomID {
				err = u.Conn.WriteMessage(messageType, p)
				if err != nil {
					// 处理错误
				}
			}
		}
		userManager.Unlock()
	}
}

// 心跳机制
func startHeartbeat(user *User) {
	// 设置心跳周期为20秒
	PingTimer := time.NewTicker(20 * time.Second)
	defer PingTimer.Stop()

	for range PingTimer.C {
		// 检查最后的Pong时间,如果超时则认为连接异常(连续3次心跳超时,则认为连接异常)
		if time.Since(user.LastPong) > 65*time.Second {
			// 用户离线,删除连接
			userManager.Lock()
			delete(userManager.Users, user.ID)
			userManager.Unlock()
			user.Conn.Close()
			return
		}
		// 发送Ping
		err := user.Conn.WriteMessage(websocket.TextMessage, []byte("ping"))
		if err != nil {
			// 处理错误

		}
	}
}

func main() {
	r := gin.Default()
	r.GET("/login", login)
	r.Run(":8080")
}

gitee代码示例

alsark/go-websocket示例https://gitee.com/alsark/go-websocket-example.git参考:Golang Gin框架实现WebSocket长连接实战指南 - 云原生实践

更多推荐