【Pitaya游戏服务器实战】注册登录 --> 1.1 前后端服务器间通信

第一章,我们先一起完成一个简单的登录流程。虽说简单,但也涉及到了多个服务器和数据库,所以借此功能,我们刚好了解如何通过 pitaya 建立前后端服务器、进行服务器间通信。

本节代码:xyq10612/PitayaGame at chapter1.1-前后端服务期间通信 (github.com)

注册登录服务器框架

先画一个简单的服务器框架图(只包括与注册登录有关的框架,其他服务器例如匹配、战斗、聊天等以后再加入,前期不要复杂度拉太高):

  1. Client 通过 socket 连接到 proxyServer(在 proxyServer 创建 Acceptor 监听);
  2. proxyServer 作为代理服务器,隔绝客户端与逻辑服务,更加安全,也方便做逻辑层的负载均衡。目前开发中为了方便测试,我们只开放一个 proxyServer,让客户端直连上来,之后可以多开代理服,使用 ngnix 之类的服务来做外层的负载均衡;
  3. 服务器内部使用 pitaya 框架提供的 RPC 功能来实现进程见通信;
  4. proxyServer 和 lobbyServer 都需要与 redis 建立链接,用于实现登录功能,保证登录的原子性(这个在我们实现登录逻辑的时候再细说);
  5. 这套实战项目采用 mongdb 作为落地服务器,所有 lobbyServer 直连 db,其他服务器不直接修改玩家存盘数据,均交由 lobbyServer 来处理。

实现前后端服务器通信

1. 建立工程

先建立工程,我这里命名是 "PitayaGame",起名困难户...... 同时把我们的两个服务器文件夹、go.mod go.work啥的都创建好:

2. 创建 proxyServer 服务器

参考 pitaya 的其他 demo 写法,创建默认的 Builder,添加 Acceptor 等:

go 复制代码
// main.go
var app pitaya.Pitaya

func main() {
	serverType := "proxy"

	port := flag.Int("port", 40000, "the port to listen")
	flag.Parse()

	logrus.SetLevel(logrus.DebugLevel)

	config := config.NewDefaultBuilderConfig()
	builder := pitaya.NewDefaultBuilder(true, serverType, pitaya.Cluster, map[string]string{}, *config)
	builder.AddAcceptor(newAcceptor(*port))

	app = builder.Build()

	defer app.Shutdown()

	app.Start()
}

func newAcceptor(port int) acceptor.Acceptor {
	tcp := acceptor.NewTCPAcceptor(fmt.Sprintf(":%d", port))
	return tcp

serverType 作为路由的一部分是不会修改的,在代码里可以写死,端口需要可配置,所以开放到命令行参数里。 go run 把服务器跑起来,没有问题~(注意 etcd 和 nats-server 记得开启,开启方法参考:0.准备工作

3. 定义 proto

有些通信协议是多个服务器都需要使用的,为了方便复用,我们干脆把所有的 proto 都放在一个公共包里:

define 目录用来存放 pb 定义,proto 目录用来存放生成的 go 文件。

我们先来为 注册 功能定义相应的 pb,顺便抽取出一些可复用的部分,比如 errorCode 等。

err.proto

protobuf 复制代码
syntax = "proto3";

package proto;
option go_package="../proto";

enum ErrCode {
    OK = 0;
    ERR = -1;

    // 1000 - 1999 通用错误
    UpParam = 1000; // 上行参数错误

    // 2000 - 2999 用户错误
    AccountRegister_NameInvalid = 2000; // 用户名不合法
    AccountRegister_PwdInvalid = 2001; // 密码不合法
    AccountRegister_NameExist = 2002; // 用户名已存在
}

common.proto

protobuf 复制代码
syntax = "proto3";

package proto;
option go_package = "../proto";

import "err.proto";

message CommonResponse {
  ErrCode err = 1;
}

message ErrorMessage {
  ErrCode err = 1;
  string msg = 2;
}

account.proto

protobuf 复制代码
syntax = "proto3";

package proto;
option go_package = "../proto";

import "err.proto";

message RegisterRequest {
  string account = 1;
  string password = 2;
}

PS:后续如果再有 pb 相关的代码段,就不再给出前面几行 package、option 之类的设置,不然代码太长了。

顺便在 define 目录下写一个生成脚本:

generate.bat

bat 复制代码
protoc --go_out=. ^
    common.proto ^
    err.proto ^
    account.proto

pause

exit

PS: 关于如何生成 protobuf go 代码,网上也很多参考文章,我就不写了,懒......

跑完 bat 脚本,在 proto 目录下生成 go 代码。

4. proxy 账号服务 AccountService

在 proxyServer 里建立 service 文件夹,在这个示例中, service 作为服务提供方,用来处理消息,还有一种命名是 handler ,这里沿用 pitaya demo 中的用法 ------ service

  1. 实现一个简单的服务,没有实际逻辑,仅仅测试一下通信
go 复制代码
// proxyServer/service/accountService.go
// AccountService 账号服务
type AccountService struct {
	component.Base
	app pitaya.Pitaya
}

func NewAccountService(app pitaya.Pitaya) *AccountService {
	return &AccountService{app: app}
}

func (s *AccountService) Register(ctx context.Context, req *proto.RegisterRequest) (*proto.CommonResponse, error) {
	logrus.Debugf("register request: %v", req)

	rsp := &proto.CommonResponse{Err: proto.ErrCode_OK}

	return rsp, nil
  1. 在 main 中注册服务
go 复制代码
// main.go
func main() {
  //...
  defer app.Shutdown()

  initServices()
  
  app.Start()
}

func initServices() {
	account := service.NewAccountService(app)
	app.Register(account,
		component.WithName("account"),
		component.WithNameFunc(strings.ToLower))
}

5. 测试 proxy 服务器的消息处理

开启服务器,使用 pitaya-cli 测试一下账号服务的消息处理:

sh 复制代码
>pitaya-cli
Pitaya REPL Client
>>> connect 127.0.0.1:40000
Using json client
connected!
>>> request proxy.account.register {"account":"test1", "password":"ttt"}
>>> sv->{}

客户端发送请求,服务端正常打印了日志:

sh 复制代码
level=debug msg="SID=4, Data={\"account\":\"test1\",\"password\":\"ttt\"}" requestId=ATMuGsStlRAk9pZQ26bumE route=proxy.account.register source=pitaya userId=

OK,初步调通。

6. 创建 lobbyServer 服务器

lobby 服务器作为后端服务器,是不需要对外的,不需要添加 Acceptor 监听。

go 复制代码
// main.go
func main() {
	serverType := "lobby"

	logrus.SetLevel(logrus.DebugLevel)

	config := config.NewDefaultBuilderConfig()
	builder := pitaya.NewDefaultBuilder(false, serverType, pitaya.Cluster, map[string]string{}, *config)

	app = builder.Build()

	defer app.Shutdown()

	initServices()

	app.Start()
}

func initServices() {
	account := service.NewAccountService(app)
	app.Register(account,
		component.WithName("account"),
		component.WithNameFunc(strings.ToLower))
	app.RegisterRemote(account,
		component.WithName("account"),
		component.WithNameFunc(strings.ToLower))
}

7. lobbyServer 账号服务 AccountService

先测试前后端服务器的通信,没有具体逻辑,打印个log:

go 复制代码
type AccountService struct {
	component.Base
	app pitaya.Pitaya
}

func NewAccountService(app pitaya.Pitaya) *AccountService {
	return &AccountService{
		app: app,
	}
}

func (s *AccountService) Register(ctx context.Context, req *proto.RegisterRequest) (*proto.CommonResponse, error) {
	logrus.Debugf("register request: %v", req)

	logrus.Debugf("do something in lobby")

	return &proto.CommonResponse{Err: proto.ErrCode_OK}, nil
}

8. 在 proxyServer 中远程调用 lobbyServer

第 4 步里,我们是直接把注册请求返回的,现在我们修改一下代码,让 proxyServer 的 Register 远程调用 lobbyServer 的 Register。 远程调用可以使用 RPCRPCTo,后者需要指定 ServerId,这里使用 RPC,底层会调用默认的路由方法 defaultRoute 随机选择一个该类型服务器。

go 复制代码
// proxyServer/service/accountService.go
func (s *AccountService) Register(ctx context.Context, req *proto.RegisterRequest) (*proto.CommonResponse, error) {
	logrus.Debugf("register request: %v", req)

	logrus.Debugf("do something in proxy")

	rsp := &proto.CommonResponse{}

	err := s.app.RPC(ctx, "lobby.account.register", rsp, req)
	if err != nil {
		return nil, err
	}

	return rsp, nil
}

9. 测试前后端通信

记得开启 etcd 和 nats-server,框架 RPC 默认就是通过 nats 实现的,对底层实现感兴趣的可以看看这篇文章:cluster grpc demo

开启 1 个 proxyServer 和 2 个 lobbyServer,使用 2 个 pitaya-cli 测试:

sh 复制代码
pitaya-cli
Pitaya REPL Client
>>> connect 127.0.0.1:40000
Using json client
connected!
>>> request proxy.account.register {"account":"test1", "password":"ttt"}
>>> sv->{}

在 2 个 client 端都发出以上请求,根据默认的路由策略,这些请求会随机分配到 lobby1 或者 lobby2,lobby 侧输出了日志:

sh 复制代码
level=debug msg="register request: account:\"test1\"  password:\"ttt\""
level=debug msg="do something in lobby"

你也可以多开几个 client,看看这些请求最终都分配到哪个 lobby 上。

小结

本节主要是对注册登录所涉及到的服务器框架有了一个大致的了解,并搭建了代理服务器 proxyServer 和大厅服务器 lobbyServer,并且测试了前后端服务器之间的通信。 下一节,我们一起来封装 mongodb 操作,完善注册逻辑,实现玩家数据入库。

相关推荐
知恒18 小时前
Go语言接口与多态
go
知恒19 小时前
Go语言变量与数据类型
go
知恒19 小时前
Go包管理与模块化
go
HokKeung19 小时前
飞书 lark-cli 如何存储 tenant_access_token 和 user_access_token
人工智能·go
止语Lab1 天前
sync.Pool 的真正分界线不是对象大小——一次 benchmark 翻车记录
go
HokKeung1 天前
Go 里的 IO 应该怎么管理
go
喵个咪1 天前
Go-Wind HTTP 服务器从入门到精通
后端·http·go
喵个咪1 天前
Go-Wind gRPC 服务器从入门到精通
后端·go·grpc
SmalBox1 天前
【节点】[Zigzag节点]原理解析与实际应用
unity3d·游戏开发·图形学
知恒1 天前
Go环境搭建与入门
go