This commit is contained in:
孟帅
2022-11-24 23:37:34 +08:00
parent 4ffe54b6ac
commit 29bda0dcdd
1487 changed files with 97869 additions and 96539 deletions

View File

@@ -0,0 +1,204 @@
// Package websocket
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package websocket
import (
"context"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/guid"
"github.com/gorilla/websocket"
"hotgo/internal/consts"
"hotgo/internal/library/contexts"
"hotgo/internal/library/location"
"hotgo/internal/model"
"runtime/debug"
)
const (
// 用户连接超时时间
heartbeatExpirationTime = 5 * 60
)
// 用户登录
type login struct {
UserId int64
Client *Client
}
// GetKey 读取客户端数据
func (l *login) GetKey() (key string) {
key = GetUserKey(l.UserId)
return
}
// Client 客户端连接
type Client struct {
Addr string // 客户端地址
ID string // 连接唯一标识
Socket *websocket.Conn // 用户连接
Send chan *WResponse // 待发送的数据
SendClose bool // 发送是否关闭
FirstTime uint64 // 首次连接时间
HeartbeatTime uint64 // 用户上次心跳时间
Tags garray.StrArray // 标签
User *model.Identity // 用户信息
context context.Context // Custom context for internal usage purpose.
IP string // 客户端IP
UserAgent string // 用户代理
}
// NewClient 初始化
func NewClient(r *ghttp.Request, socket *websocket.Conn, firstTime uint64) (client *Client) {
client = &Client{
Addr: socket.RemoteAddr().String(),
ID: guid.S(),
Socket: socket,
Send: make(chan *WResponse, 100),
SendClose: false,
FirstTime: firstTime,
HeartbeatTime: firstTime,
User: contexts.Get(r.Context()).User,
IP: location.GetClientIp(r),
UserAgent: r.UserAgent(),
}
return
}
// 读取客户端数据
func (c *Client) read() {
defer func() {
if r := recover(); r != nil {
g.Log().Warningf(ctxManager, "client read err: %+v, stack:%+v, user:%+v", r, string(debug.Stack()), c.User)
}
}()
defer func() {
c.close()
}()
for {
_, message, err := c.Socket.ReadMessage()
if err != nil {
return
}
// 处理消息
handlerMsg(c, message)
}
}
// 向客户端写数据
func (c *Client) write() {
defer func() {
if r := recover(); r != nil {
g.Log().Warningf(ctxManager, "client write err: %+v, stack:%+v, user:%+v", r, string(debug.Stack()), c.User)
}
}()
defer func() {
clientManager.Unregister <- c
c.Socket.Close()
}()
for {
select {
case message, ok := <-c.Send:
if !ok {
// 发送数据错误 关闭连接
g.Log().Warningf(ctxManager, "client write message, user:%+v", c.User)
return
}
c.Socket.WriteJSON(message)
}
}
}
// SendMsg 发送数据
func (c *Client) SendMsg(msg *WResponse) {
if c == nil || c.SendClose {
return
}
defer func() {
if r := recover(); r != nil {
g.Log().Infof(ctxManager, "SendMsg err:%+v, stack:%+v", r, string(debug.Stack()))
}
}()
c.Send <- msg
}
// Context is alias for function GetCtx.
func (c *Client) Context() context.Context {
if c.context == nil {
c.context = gctx.New()
}
return c.context
}
// Heartbeat 心跳更新
func (c *Client) Heartbeat(currentTime uint64) {
c.HeartbeatTime = currentTime
return
}
// IsHeartbeatTimeout 心跳是否超时
func (c *Client) IsHeartbeatTimeout(currentTime uint64) (timeout bool) {
if c.HeartbeatTime+heartbeatExpirationTime <= currentTime {
timeout = true
}
return
}
// 关闭客户端
func (c *Client) close() {
if c.SendClose {
return
}
c.SendClose = true
if _, ok := <-c.Send; !ok {
g.Log().Warningf(ctxManager, "close of closed channel, client.id:%v", c.ID)
} else {
// 关闭 chan
close(c.Send)
}
}
// Close 关闭指定客户端连接
func Close(client *Client) {
client.close()
}
// SendSuccess 发送成功消息
func SendSuccess(client *Client, event string, data ...interface{}) {
d := interface{}(nil)
if len(data) > 0 {
d = data[0]
}
client.SendMsg(&WResponse{
Event: event,
Data: d,
Code: consts.CodeOK,
Timestamp: gtime.Now().Unix(),
})
before(client)
}
// SendError 发送错误消息
func SendError(client *Client, event string, err error) {
client.SendMsg(&WResponse{
Event: event,
Code: consts.CodeNil,
ErrorMsg: err.Error(),
Timestamp: gtime.Now().Unix(),
})
before(client)
}
// before
func before(client *Client) {
client.Heartbeat(uint64(gtime.Now().Unix()))
}

View File

@@ -0,0 +1,331 @@
// Package websocket
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package websocket
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcron"
"github.com/gogf/gf/v2/os/gtime"
"runtime/debug"
"sync"
)
// ClientManager 客户端管理
type ClientManager struct {
Clients map[*Client]bool // 全部的连接
ClientsLock sync.RWMutex // 读写锁
Users map[string][]*Client // 登录的用户
UserLock sync.RWMutex // 读写锁
Register chan *Client // 连接连接处理
Login chan *login // 用户登录处理
Unregister chan *Client // 断开连接处理程序
Broadcast chan *WResponse // 广播 向全部成员发送数据
ClientBroadcast chan *ClientWResponse // 广播 向某个客户端发送数据
TagBroadcast chan *TagWResponse // 广播 向某个标签成员发送数据
UserBroadcast chan *UserWResponse // 广播 向某个用户的所有链接发送数据
closeSignal chan struct{} // 关闭信号
}
func NewClientManager() (clientManager *ClientManager) {
clientManager = &ClientManager{
Clients: make(map[*Client]bool),
Users: make(map[string][]*Client),
Register: make(chan *Client, 1000),
Unregister: make(chan *Client, 1000),
Broadcast: make(chan *WResponse, 1000),
TagBroadcast: make(chan *TagWResponse, 1000),
UserBroadcast: make(chan *UserWResponse, 1000),
closeSignal: make(chan struct{}, 1),
}
return
}
func Manager() *ClientManager {
return clientManager
}
// GetUserKey 获取用户key
func GetUserKey(userId int64) (key string) {
key = fmt.Sprintf("%s_%d", "ws", userId)
return
}
// InClient 客户端是否存在
func (manager *ClientManager) InClient(client *Client) (ok bool) {
manager.ClientsLock.RLock()
defer manager.ClientsLock.RUnlock()
_, ok = manager.Clients[client]
return
}
// GetClients 获取所有客户端
func (manager *ClientManager) GetClients() (clients map[*Client]bool) {
clients = make(map[*Client]bool)
manager.ClientsRange(func(client *Client, value bool) (result bool) {
clients[client] = value
return true
})
return
}
// ClientsRange 遍历
func (manager *ClientManager) ClientsRange(f func(client *Client, value bool) (result bool)) {
manager.ClientsLock.RLock()
defer manager.ClientsLock.RUnlock()
for key, value := range manager.Clients {
result := f(key, value)
if result == false {
return
}
}
return
}
// GetClientsLen 获取客户端总数
func (manager *ClientManager) GetClientsLen() (clientsLen int) {
clientsLen = len(manager.Clients)
return
}
// AddClients 添加客户端
func (manager *ClientManager) AddClients(client *Client) {
manager.ClientsLock.Lock()
defer manager.ClientsLock.Unlock()
manager.Clients[client] = true
}
// DelClients 删除客户端
func (manager *ClientManager) DelClients(client *Client) {
manager.ClientsLock.Lock()
defer manager.ClientsLock.Unlock()
if _, ok := manager.Clients[client]; ok {
delete(manager.Clients, client)
}
}
// GetClient 通过socket ID获取客户端的连接
func (manager *ClientManager) GetClient(ID string) (client *Client) {
for c, _ := range manager.Clients {
if c.ID == ID {
return c
}
}
return
}
// GetUserClient 获取用户的连接
func (manager *ClientManager) GetUserClient(userId int64) (clients []*Client) {
manager.UserLock.RLock()
defer manager.UserLock.RUnlock()
userKey := GetUserKey(userId)
if value, ok := manager.Users[userKey]; ok {
clients = value
}
return
}
// AddUsers 添加用户
func (manager *ClientManager) AddUsers(key string, client *Client) {
manager.UserLock.Lock()
defer manager.UserLock.Unlock()
manager.Users[key] = append(manager.Users[key], client)
}
// DelUsers 删除用户
func (manager *ClientManager) DelUsers(client *Client) (result bool) {
manager.UserLock.Lock()
defer manager.UserLock.Unlock()
key := GetUserKey(client.User.Id)
if clients, ok := manager.Users[key]; ok {
for _, value := range clients {
// 判断是否为相同的用户
if value.Addr != client.Addr {
return
}
delete(manager.Users, key)
result = true
}
}
return
}
// GetUsersLen 已登录用户数
func (manager *ClientManager) GetUsersLen() (userLen int) {
userLen = len(manager.Users)
return
}
// EventRegister 用户建立连接事件
func (manager *ClientManager) EventRegister(client *Client) {
manager.AddClients(client)
// 用户登录
manager.EventLogin(&login{
UserId: client.User.Id,
Client: client,
})
////发送当前客户端标识
//SendSuccess(client, "connected", g.Map{"id": client.ID, "userInfo": client.User})
}
// EventLogin 用户登录事件
func (manager *ClientManager) EventLogin(login *login) {
client := login.Client
if manager.InClient(client) {
userKey := login.GetKey()
manager.AddUsers(userKey, login.Client)
}
}
// EventUnregister 用户断开连接事件
func (manager *ClientManager) EventUnregister(client *Client) {
manager.DelClients(client)
// 删除用户连接
deleteResult := manager.DelUsers(client)
if deleteResult == false {
// 不是当前连接的客户端
return
}
client.close()
}
// ClearTimeoutConnections 定时清理超时连接
func (manager *ClientManager) clearTimeoutConnections() {
currentTime := uint64(gtime.Now().Unix())
clients := clientManager.GetClients()
for client := range clients {
if client.IsHeartbeatTimeout(currentTime) {
//fmt.Println("心跳时间超时 关闭连接", client.Addr, client.UserId, client.LoginTime, client.HeartbeatTime)
client.Socket.Close()
}
}
}
// WebsocketPing 心跳处理
func (manager *ClientManager) ping() {
defer func() {
if r := recover(); r != nil {
g.Log().Warningf(ctxManager, "websocket gcron ping recover:%+v, stack:%+v", r, string(debug.Stack()))
return
}
}()
////定时任务,发送心跳包
//gcron.Add(ctx, "0 */1 * * * *", func(ctx context.Context) {
// res := &WResponse{
// Event: "ping",
// Timestamp: gtime.Now().Unix(),
// }
// SendToAll(res)
//})
// 定时任务,清理超时连接
gcron.Add(ctxManager, "*/30 * * * * *", func(ctx context.Context) {
manager.clearTimeoutConnections()
})
}
// 管道处理程序
func (manager *ClientManager) start() {
defer func() {
if r := recover(); r != nil {
g.Log().Warningf(ctxManager, "websocket start recover:%+v, stack:%+v", r, string(debug.Stack()))
return
}
}()
for {
select {
case conn := <-manager.Register:
// 建立连接事件
manager.EventRegister(conn)
case login := <-manager.Login:
// 用户登录
manager.EventLogin(login)
case conn := <-manager.Unregister:
// 断开连接事件
manager.EventUnregister(conn)
case message := <-manager.Broadcast:
// 全部客户端广播事件
clients := manager.GetClients()
for conn := range clients {
conn.SendMsg(message)
}
case message := <-manager.TagBroadcast:
// 标签广播事件
clients := manager.GetClients()
for conn := range clients {
if conn.Tags.Contains(message.Tag) {
if message.WResponse.Timestamp == 0 {
message.WResponse.Timestamp = gtime.Now().Timestamp()
}
conn.SendMsg(message.WResponse)
}
}
case message := <-manager.UserBroadcast:
// 用户广播事件
clients := manager.GetClients()
for conn := range clients {
if conn.User.Id == message.UserID {
if message.WResponse.Timestamp == 0 {
message.WResponse.Timestamp = gtime.Now().Timestamp()
}
conn.SendMsg(message.WResponse)
}
}
case message := <-manager.ClientBroadcast:
// 单个客户端广播事件
clients := manager.GetClients()
for conn := range clients {
if conn.ID == message.ID {
if message.WResponse.Timestamp == 0 {
message.WResponse.Timestamp = gtime.Now().Timestamp()
}
conn.SendMsg(message.WResponse)
}
}
case <-manager.closeSignal:
g.Log().Infof(ctxManager, "websocket closeSignal quit..")
return
}
}
}
// SendToAll 发送全部客户端
func SendToAll(response *WResponse) {
clientManager.Broadcast <- response
}
// SendToClientID 发送单个客户端
func SendToClientID(id string, response *WResponse) {
clientRes := &ClientWResponse{
ID: id,
WResponse: response,
}
clientManager.ClientBroadcast <- clientRes
}
// SendToUser 发送单个用户
func SendToUser(userID int64, response *WResponse) {
userRes := &UserWResponse{
UserID: userID,
WResponse: response,
}
clientManager.UserBroadcast <- userRes
}
// SendToTag 发送某个标签
func SendToTag(tag string, response *WResponse) {
tagRes := &TagWResponse{
Tag: tag,
WResponse: response,
}
clientManager.TagBroadcast <- tagRes
}

View File

@@ -0,0 +1,56 @@
// Package websocket
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package websocket
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gorilla/websocket"
"net/http"
)
var (
ctxManager context.Context // 主上下文
clientManager = NewClientManager() // 客户端管理
routers = make(map[string]EventHandler) // 消息路由
upGrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
)
// Start 启动
func Start(c context.Context) {
ctxManager = c
g.Log().Info(ctxManager, "启动WebSocket")
go clientManager.start()
go clientManager.ping()
}
// Stop 关闭
func Stop() {
clientManager.closeSignal <- struct{}{}
}
// WsPage ws入口
func WsPage(r *ghttp.Request) {
conn, err := upGrader.Upgrade(r.Response.ResponseWriter, r.Request, nil)
if err != nil {
return
}
currentTime := uint64(gtime.Now().Unix())
client := NewClient(r, conn, currentTime)
go client.read()
go client.write()
// 用户连接事件
clientManager.Register <- client
}

View File

@@ -0,0 +1,44 @@
// Package websocket
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package websocket
import "github.com/gogf/gf/v2/frame/g"
// WRequest 输入对象
type WRequest struct {
Event string `json:"event"` // 事件名称
Data g.Map `json:"data"` // 数据
}
// WResponse 输出对象
type WResponse struct {
Event string `json:"event"` // 事件名称
Data interface{} `json:"data,omitempty"` // 数据
Code int64 `json:"code"` // 状态码
ErrorMsg string `json:"errorMsg,omitempty"` // 错误消息
Timestamp int64 `json:"timestamp"` // 服务器时间
}
type TagWResponse struct {
Tag string
WResponse *WResponse
}
type UserWResponse struct {
UserID int64
WResponse *WResponse
}
type ClientWResponse struct {
ID string
WResponse *WResponse
}
// EventHandler 消息处理器
type EventHandler func(client *Client, req *WRequest)
type EventHandlers map[string]EventHandler

View File

@@ -0,0 +1,53 @@
// Package websocket
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package websocket
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"runtime/debug"
)
// handlerMsg 处理消息
func handlerMsg(client *Client, message []byte) {
defer func() {
if r := recover(); r != nil {
g.Log().Warningf(ctxManager, "handlerMsg recover, err:%+v, stack:%+v", r, string(debug.Stack()))
}
}()
request := &WRequest{}
err := gconv.Struct(message, request)
if err != nil {
g.Log().Warningf(ctxManager, "handlerMsg 数据解析失败,err:%+v, message:%+v", err, string(message))
return
}
if request.Event == "" {
g.Log().Warning(ctxManager, "handlerMsg request.Event is null")
return
}
//g.Log().Infof(ctxManager, "websocket handlerMsg:%+v", request)
fun, ok := routers[request.Event]
if !ok {
g.Log().Warningf(ctxManager, "handlerMsg function id %v: not registered", request.Event)
return
}
fun(client, request)
}
// RegisterMsg 注册消息
func RegisterMsg(handlers EventHandlers) {
for id, f := range handlers {
if _, ok := routers[id]; ok {
g.Log().Fatalf(ctxManager, "RegisterMsg function id %v: already registered", id)
return
}
routers[id] = f
}
}