一个websocket框架是怎么诞生的

文中示例的代码已开源

事情是这样的

最近一直在换代码,想把原来用PHP写的代码搬运到Go来运行,其他的CURD、支付、Redis、MQ、ES都没有遇到什么大的问题,但是切到Socket服务的时候,找了一圈都没有找到有类似Workerman-GatewayWorker这样多快好省的Socket框架。

既然如此,那就自己撸一个出来吧。

设计环节

撸代码前肯定要先构思好架构。

用过Gatewayworker的同学应该都知道里面三个比较重要的架构概念ClientGroupUsers,用来区分管理所有连接到服务的客户端。

一个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,大家自取~

效果展示

相关推荐
修仙的人5 分钟前
【开发环境】 VSCode 快速搭建 Python 项目开发环境
前端·后端·python
FinalLi7 分钟前
SpringBoot3.5.0项目使用ALLATORI JAVA混淆器
后端
bobz9651 小时前
用于服务器测试的 MCP 开发工具
后端
SimonKing1 小时前
流式数据服务端怎么传给前端,前端怎么接收?
java·后端·程序员
Laplaces Demon1 小时前
Spring 源码学习(十)—— DispatcherServlet
java·后端·学习·spring
BigYe程普1 小时前
出海技术栈集成教程(一):域名解析与配置
前端·后端·全栈
这里有鱼汤1 小时前
如何用‘资金视角’理解短线交易?这篇讲透了!
后端
扶风呀1 小时前
负载均衡详解
运维·后端·微服务·面试·负载均衡
uhakadotcom1 小时前
在nodejs之中, userUuid !== '' 和 userUuid != ''是一样的吗?
前端·javascript·面试
我想说一句2 小时前
JavaScript之深浅拷贝
前端·javascript·面试