事情是这样的
最近一直在换代码,想把原来用PHP写的代码搬运到Go来运行,其他的CURD、支付、Redis、MQ、ES都没有遇到什么大的问题,但是切到Socket服务的时候,找了一圈都没有找到有类似Workerman-GatewayWorker这样多快好省的Socket框架。
既然如此,那就自己撸一个出来吧。
设计环节
撸代码前肯定要先构思好架构。
用过Gatewayworker的同学应该都知道里面三个比较重要的架构概念
Client
、Group
、Users
,用来区分管理所有连接到服务的客户端。
一个Users可以绑定多个Client连接对象,但一个Client只能绑定一个Users
一个Users可以绑定多个Group群组
WebSocket服务的只是作为一个独立的组件存在于业务系统中,它的存在不应该侵入系统。
业务系统应该怎么和WebSocket框架进行通讯来推送消息呢?
既然用Go了,那肯定是优先考虑用gRPC啦
要不要考虑做集群呢?
短期内暂时用不到,所以目前的结构中就没有涉及
大致的构思好了,那就开干吧!!!
代码环节
(1):启动Socket服务器
main.go
go
/*
* @Author: psq
* @Date: 2023-04-24 15:14:11
* @LastEditors: psq
* @LastEditTime: 2023-08-02 14:08:29
*/
package main
import (
"fmt"
"gateway-websocket/command"
"gateway-websocket/config"
gRPC "gateway-websocket/services/grpc"
WebSocket "gateway-websocket/services/websocket"
"os"
"github.com/gin-gonic/gin"
)
func gatewayWebsocketService() {
gin.SetMode(gin.ReleaseMode)
go gRPC.RegService()
engine := gin.Default()
engine.GET("/", WebSocket.WsServer)
engine.Run(fmt.Sprintf(":%d", config.GatewayConfig["GatewayServicePort"]))
}
func main() {
args := os.Args[1:]
if len(args) > 0 {
switch args[0] {
case "start":
command.StartService()
return
case "stop":
command.StopService()
return
case "status":
command.DumpServieStatus()
return
case "daemon":
gatewayWebsocketService()
return
default:
goto TIPS
}
} else {
goto TIPS
}
TIPS:
usage := `
gateway-websocket is an efficient WebSocket server.
Usage:
gateway-websocket <command>
The commands are:
start starts the gateway-websocket process
stop stops the gateway-websocket process
status displays the runtime status and client connection information of gateway-websocket
`
fmt.Println(usage)
}
wsserver.go
go
/*
* @Author: psq
* @Date: 2022-05-08 14:18:08
* @LastEditors: psq
* @LastEditTime: 2023-07-13 10:08:48
*/
package websocket
import (
"gateway-websocket/config"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-module/carbon"
"github.com/google/uuid"
"github.com/gorilla/websocket"
)
var (
upGrader = websocket.Upgrader{
// 设置消息接收缓冲区大小(byte),如果这个值设置得太小,可能会导致服务端在读取客户端发送的大型消息时遇到问题
ReadBufferSize: config.GatewayConfig["ReadBufferSize"].(int),
// 设置消息发送缓冲区大小(byte),如果这个值设置得太小,可能会导致服务端在发送大型消息时遇到问题
WriteBufferSize: config.GatewayConfig["WriteBufferSize"].(int),
// 消息包启用压缩
EnableCompression: config.GatewayConfig["MessageCompression"].(bool),
// ws握手超时时间
HandshakeTimeout: time.Duration(config.GatewayConfig["WebsocketHandshakeTimeout"].(int)) * time.Second,
// ws握手过程中允许跨域
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// 设置心跳检测间隔时长(秒)
HeartbeatTime = config.GatewayConfig["HeartbeatTimeout"].(int)
)
func WsServer(c *gin.Context) {
// 将 HTTP 连接升级为 WebSocket 连接
conn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
defer conn.Close()
// 客户端唯一身份标识
clientID := uuid.New().String()
// 记录客户端连接信息
GatewayClients[clientID] = &WebSocketClientBase{
ID: clientID,
Conn: conn,
LastHeartbeat: carbon.Now().Timestamp(),
}
// 发送客户端唯一标识 ID
if err = conn.WriteMessage(config.GatewayConfig["MessageFormat"].(int), []byte(clientID)); err != nil {
return
}
go clientHeartbeatCheck(clientID)
for {
// 读取客户端发送过来的消息
messageType, message, err := conn.ReadMessage()
// 当收到err时则标识客户端连接出现异常,如断线
if err != nil {
if GatewayClients[clientID].BindUid != "" {
clientUnBindUid(clientID, GatewayClients[clientID].BindUid)
}
if len(GatewayClients[clientID].JoinGroup) > 0 {
clientLeaveGroup(clientID)
}
delete(GatewayClients, clientID)
break
}
// 当客户端发送的是心跳时返回pong字符包以此刷新心跳
if messageType == config.GatewayConfig["MessageFormat"].(int) && string(message) == "ping" {
err = conn.WriteMessage(config.GatewayConfig["MessageFormat"].(int), []byte("pong"))
// 向客户端发送消息如果遇到异常当即断开连接
if err != nil {
if GatewayClients[clientID].BindUid != "" {
clientUnBindUid(clientID, GatewayClients[clientID].BindUid)
}
if len(GatewayClients[clientID].JoinGroup) > 0 {
clientLeaveGroup(clientID)
}
delete(GatewayClients, clientID)
break
}
// 刷新最后最后心跳时间
GatewayClients[clientID].LastHeartbeat = carbon.Now().Timestamp()
}
}
}
(2):启动gRPC服务
less
/*
* @Author: psq
* @Date: 2023-04-28 20:30:51
* @LastEditors: psq
* @LastEditTime: 2023-07-13 10:35:09
*/
package services
import (
"context"
"fmt"
"gateway-websocket/config"
"net"
Controllers "gateway-websocket/services/grpc/controllers"
BroadcastMessagePB "gateway-websocket/services/grpc/proto/broadcastmessage"
ClientBindUidPB "gateway-websocket/services/grpc/proto/clientbinduid"
ClientIsOnlinePB "gateway-websocket/services/grpc/proto/clientisonline"
ClonseClientPB "gateway-websocket/services/grpc/proto/clonseclient"
CountOnlineClientPB "gateway-websocket/services/grpc/proto/countonlineclient"
GetAllOnlineClientPB "gateway-websocket/services/grpc/proto/getallonlineclient"
GetClientByUidPB "gateway-websocket/services/grpc/proto/getclientbyuid"
SendMessageToClientPB "gateway-websocket/services/grpc/proto/sendmessagetoclient"
CountOnlineUidPB "gateway-websocket/services/grpc/proto/countonlineuid"
GetAllOnlineUidPB "gateway-websocket/services/grpc/proto/getallonlineuid"
GetUidByClientPB "gateway-websocket/services/grpc/proto/getuidbyclient"
SendMessageToUidPB "gateway-websocket/services/grpc/proto/sendmessagetouid"
UidIsOnlinePB "gateway-websocket/services/grpc/proto/uidisonline"
UnBindUidPB "gateway-websocket/services/grpc/proto/unbinduid"
CountGroupPB "gateway-websocket/services/grpc/proto/countgroup"
CountOnlineGroupPB "gateway-websocket/services/grpc/proto/countonlinegroup"
GetGroupOnlineClientPB "gateway-websocket/services/grpc/proto/getgrouponlineclient"
JoinGroupPB "gateway-websocket/services/grpc/proto/joingroup"
LeaveGroupPB "gateway-websocket/services/grpc/proto/leavegroup"
SendMessageToGroupPB "gateway-websocket/services/grpc/proto/sendmessagetogroup"
UnGroupPB "gateway-websocket/services/grpc/proto/ungroup"
"google.golang.org/grpc"
GRPC "google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
)
/**
* @description: gRPC拦截器
* @param {context.Context} ctx
* @param {interface{}} req
* @param {*grpc.UnaryServerInfo} info
* @param {grpc.UnaryHandler} handler
* @return {*}
*/
func identityCheck(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
p, _ := peer.FromContext(ctx)
// 拆分地址信息,获取 IP 地址部分
host, _, _ := net.SplitHostPort(p.Addr.String())
pass := false
ipaddr := config.GatewayConfig["ClientIP"].([]string)
if len(ipaddr) >= 1 {
for _, v := range ipaddr {
if host == v {
pass = true
break
}
}
} else {
pass = true
}
if !pass {
return nil, status.Errorf(codes.InvalidArgument, "invalid request")
}
return handler(ctx, req)
}
func RegService() {
listenPort, _ := net.Listen("tcp", fmt.Sprintf(":%d", config.GatewayConfig["GRPCServicePort"]))
gRPC := GRPC.NewServer(GRPC.UnaryInterceptor(identityCheck))
reflection.Register(gRPC)
// client相关接口
CountOnlineClientPB.RegisterCountOnlineClientServer(gRPC, &Controllers.ClientControllers{})
ClientIsOnlinePB.RegisterClientIsOnlineServer(gRPC, &Controllers.ClientControllers{})
GetAllOnlineClientPB.RegisterGetAllOnlineClientServer(gRPC, &Controllers.ClientControllers{})
ClonseClientPB.RegisterClonseClientServer(gRPC, &Controllers.ClientControllers{})
SendMessageToClientPB.RegisterSendMessageToClientServer(gRPC, &Controllers.ClientControllers{})
BroadcastMessagePB.RegisterBroadcastMessageServer(gRPC, &Controllers.ClientControllers{})
// users相关接口
GetClientByUidPB.RegisterGetClientByUidServer(gRPC, &Controllers.UserControllers{})
ClientBindUidPB.RegisterClientBindUidServer(gRPC, &Controllers.UserControllers{})
CountOnlineUidPB.RegisterCountOnlineUidServer(gRPC, &Controllers.UserControllers{})
GetAllOnlineUidPB.RegisterGetAllOnlineUidServer(gRPC, &Controllers.UserControllers{})
UnBindUidPB.RegisterUnBindUidServer(gRPC, &Controllers.UserControllers{})
UidIsOnlinePB.RegisterUidIsOnlineServer(gRPC, &Controllers.UserControllers{})
SendMessageToUidPB.RegisterSendMessageToUidServer(gRPC, &Controllers.UserControllers{})
GetUidByClientPB.RegisterGetUidByClientServer(gRPC, &Controllers.UserControllers{})
// group相关接口
CountGroupPB.RegisterCountGroupServer(gRPC, &Controllers.GroupControllers{})
CountOnlineGroupPB.RegisterCountOnlineGroupServer(gRPC, &Controllers.GroupControllers{})
GetGroupOnlineClientPB.RegisterGetGroupOnlineClientServer(gRPC, &Controllers.GroupControllers{})
JoinGroupPB.RegisterJoinGroupServer(gRPC, &Controllers.GroupControllers{})
LeaveGroupPB.RegisterLeaveGroupServer(gRPC, &Controllers.GroupControllers{})
SendMessageToGroupPB.RegisterSendMessageToGroupServer(gRPC, &Controllers.GroupControllers{})
UnGroupPB.RegisterUnGroupServer(gRPC, &Controllers.GroupControllers{})
// 启动服务
gRPC.Serve(listenPort)
}
这里只展示了部分代码,其余已开源带Github,大家自取~