go websocket实现单聊群聊心跳机制
·
安装
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长连接实战指南 - 云原生实践
更多推荐



所有评论(0)