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

效果展示

相关推荐
大梦百万秋4 分钟前
Spring Boot实战:构建一个简单的RESTful API
spring boot·后端·restful
斌斌_____33 分钟前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
木心41 分钟前
Git基本操作快速入门(30min)
git·github
路在脚下@41 分钟前
Spring如何处理循环依赖
java·后端·spring
一个不秃头的 程序员1 小时前
代码加入SFTP JAVA ---(小白篇3)
java·python·github
逸_1 小时前
dify工作流+github actions实现翻译并创建PR
gpt·github·dify
海绵波波1072 小时前
flask后端开发(1):第一个Flask项目
后端·python·flask
小奏技术2 小时前
RocketMQ结合源码告诉你消息量大为啥不需要手动压缩消息
后端·消息队列
小k_不小2 小时前
C++面试八股文:指针与引用的区别
c++·面试
布兰妮甜3 小时前
使用GitHub Pages部署静态网站:简易指南
github·pages