从零开始搭建游戏服务器 第三节 Protobuf的引入并使用

目录

  • 上一节问题答案公布
  • 本节内容
  • Protobuf介绍
  • 正文
    • 在build.gradle引入protobuf
    • 编写proto并生成
    • 使用生成的proto来进行数据传输
  • 总结

上一节问题答案公布

上一节我们创建了ConnectActor,并且使用ConnectActorManager和connectId将其管理起来。
并且我们在收到客户端上行数据时,对指定的ConnectActor发送了一条BaseMsg消息。
上一节笔者留下来的作业答案在此公布,应该不困难,步骤如下:

  1. 修改BaseActor.java
	@Overridepublic Receive<BaseMsg> createReceive() {ReceiveBuilder<BaseMsg> builder = newReceiveBuilder();onCreateReceive(builder);builder.onMessage(BaseMsg.class, this::onBaseMsg);return builder.build();}protected void onCreateReceive(ReceiveBuilder<BaseMsg> builder){}
添加了一个onCreateReceive方法用于各个Actor自己注册消息回调方法。
  1. 创建ClientUpMsg和ConnectClosedMsg
/**
* 客户端上行数据
*/
public class ClientUpMsg extends BaseMsg {private final byte[] data;public ClientUpMsg(byte[] data) {this.data = data;}public byte[] getData() {return data;}
}/**
1. 连接断开信息
*/
public class ConnectClosedMsg extends BaseMsg {
}
  1. 修改ConnectActor重写onCreateReceive方法
@Overrideprotected void onCreateReceive(ReceiveBuilder<BaseMsg> builder) {builder.onMessage(ClientUpMsg.class, this::onClientUpMsg);builder.onMessage(ConnectClosedMsg.class, this::onConnectClosedMsg);}/*** 客户端上行数据*/private Behavior<BaseMsg> onClientUpMsg(ClientUpMsg msg) {log.info("receive client up msg. {}", new String(msg.getData()));return this;}/*** 连接关闭* 移除connectActor*/private Behavior<BaseMsg> onConnectClosedMsg(ConnectClosedMsg msg) {log.info("receive connect closed msg.");ConnectActorManager.getInstance().removeConnectActor(connectId);return this;}
  1. 修改LoginNettyHandler使其在不同的情况下发送不同的消息给ConnectActor
   /*** 收到协议数据*/@Overrideprotected void channelRead0(ChannelHandlerContext ctx, byte[] msg) throws Exception {HashMap<String, Object> context = this.getContextAttrMap(ctx);long connectId = (long)context.get("connectId");ConnectActorManager actorManager = ConnectActorManager.getInstance();ActorRef<BaseMsg> connectActor = actorManager.getConnectActor(connectId);if (connectActor == null) {connectActor = actorManager.createConnectActor(connectId, ctx);}ClientUpMsg clientUpMsg = new ClientUpMsg(msg);connectActor.tell(clientUpMsg);}/*** 连接断开*/@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {HashMap<String, Object> contextAttrMap = this.getContextAttrMap(ctx);long connectId = (long) contextAttrMap.get("connectId");ActorRef<BaseMsg> actorRef = ConnectActorManager.getInstance().getConnectActor(connectId);if (actorRef != null) {actorRef.tell(new ConnectClosedMsg());} else {log.info("onClose时 connectActor不存在,直接跳过了。 connectId={}", connectId);}log.info("连接断开, connectId={}", connectId);}

测试一下:
启动LoginServer和Client,等待连接完成后在Client端控制台分别输入test和stop。
测试结果

本节内容

本节我们将引入protobuf, 并使用protobuf生成对应的java类, 然后在Client中将protobuf消息发送到LoginServer.

Protobuf介绍

Protobuf是Google公司开发的一种灵活,高效,自动化地序列化结构数据的方法,类似于XML、JSON、YAML等。
但是它比上述格式更小、更快、更灵活。
我们可以编写.proto文件定义数据的结构,然后用其提供的工具生成对应语言的代码。

正文

在build.gradle引入protobuf

        // protobufimplementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.25.3'

创建几个目录用于保存protobuf文件 common模块下添加org.protobuf包, 与commmon区分开的原因是减少spring扫描的文件加快启动速度.
在根目录下创建protobuf目录用于保存proto源文件, 然后生成的java代码放到common下的org.protobuf包
添加protobuf目录

编写proto并生成

在protobuf目录新建PlayerMsg.proto和ProtoEnumMsg.proto
分别用于存放玩家相关协议结构和协议号定义

syntax = "proto3";option java_outer_classname = "PlayerMsg";
option java_package = "org.protobuf";// 玩家注册
message C2SPlayerRegister { // 客户端上行包,返回S2CPlayerRegisterstring accountName = 1; // 账号string password = 2;    // 密码
}
message S2CPlayerRegister {bool success = 1;   // 是否成功
}
syntax = "proto3";option java_outer_classname = "ProtoEnumMsg";
option java_package = "org.protobuf";// 所有协议号
message CMD {enum ID {DEFAULT = 0;// 玩家注册PLAYER_REGISTER = 10101;}
}

IDEA安装genprotobuf插件
插件
插件配置
修改一下插件的配置,使其默认生成java类
插件配置java
选中我们刚创建的两个Msg,右键生成protobuf类
生成protobuf
然后将生成的文件移动到common模块下的org.protobuf包
移动目录
至此完成proto文件的编写和生成.

使用生成的proto来进行数据传输

修改clientMain下的handleBackGroundCmd, 当我们输入register时就发送一个C2SPlayerRegister消息到LoginServer.

	@Overrideprotected void handleBackGroundCmd(String cmd) {if (cmd.equals("test")) {channel.writeAndFlush("test".getBytes());} else if (cmd.equals("register")) {PlayerMsg.C2SPlayerRegister.Builder builder = PlayerMsg.C2SPlayerRegister.newBuilder();builder.setAccountName("clintAccount");builder.setPassword("123456");Pack pack = new Pack(ProtoEnumMsg.CMD.ID.PLAYER_REGISTER_VALUE, builder.build().toByteArray());byte[] data = PackCodec.encode(pack);channel.writeAndFlush(data);}}

当我们输入register时,创建一个PlayerMsg.C2SPlayerRegister.Builder, 往里面的字段赋值, 然后用Pack将其和上面定义的协议号打包, 最后整个协议包编码成byte[]后通过channel通道发送到LoginServer.

接下来修改LoginServer进行协议的接收与解码. 由于我们之前已经将Channel接收到的数据通过ClientUpMsg发送到了ConnectActor,所以我们只需要修改ConnectActor里的消息处理逻辑即可.

/*** 客户端上行数据*/private Behavior<BaseMsg> onClientUpMsg(ClientUpMsg msg) throws InvalidProtocolBufferException {Pack decode = PackCodec.decode(msg.getData());log.info("receive client up msg. cmdId = {}", decode.getCmdId());byte[] data = decode.getData();if (decode.getCmdId() == ProtoEnumMsg.CMD.ID.PLAYER_REGISTER_VALUE) {// 注册协议PlayerMsg.C2SPlayerRegister c2SPlayerRegister = PlayerMsg.C2SPlayerRegister.parseFrom(data);log.info("player register, accountName = {}, password = {}", c2SPlayerRegister.getAccountName(), c2SPlayerRegister.getPassword());}return this;}

在上述代码中,我们将byte[]编码成Pack,然后获得协议号, 因为每个协议号对应的协议结构是相同的,所以我们判断协议号为玩家注册后直接对其进行还原, 就能得到客户端上行的数据.

测试一下:
启动LoginServer, 启动Client
Client连接上后控制台输入register发送消息
可以看到LoginServer的控制台打印出了玩家注册日志
测试结果

总结

本节的讲东西比较简单, 主要是proto文件的编写与生成, 以及如何对protobuf打包与解包. 这些在后续我们多使用就能熟练.
留一个作业, 在PlayerMsg中添加一个PlayerLogin的登录协议, 然后client输入login发送账号密码, LoginServer接收到后进行解包并输出到控制台中.

下一节将开始使用MongoDB进行数据的持久化保存, 为什么使用MongoDB是因为最近的手游公司使用MongoDB的占比越来越多, 一些以前使用MySQL的公司也开始逐渐切换到MongoDB.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2871486.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【Twinmotion】Twinmotion导入UE5

步骤 1. 在虚幻商城中安装“Datasmith Twinmotion导入器插件” 安装“面向虚幻引擎的Twinmotion内容” 2. 打开虚幻引擎&#xff0c;在插件中搜索“twinmotion”&#xff0c;勾选如下两个插件&#xff0c;然后重启虚幻引擎 3. 打开Twinmotion&#xff0c;随便添加一个物体 导出…

腾讯云2核2G免费服务器申请流程,2024免费服务器入口

腾讯云免费服务器申请入口 https://curl.qcloud.com/FJhqoVDP 免费服务器可选轻量应用服务器和云服务器CVM&#xff0c;轻量配置可选2核2G3M、2核8G7M和4核8G12M&#xff0c;CVM云服务器可选2核2G3M和2核4G3M配置&#xff0c;腾讯云服务器网txyfwq.com分享2024年最新腾讯云免费…

4.1_5 文件存储空间管理

文章目录 4.1_5 文件存储空间管理&#xff08;一&#xff09;存储空间的划分与初始化&#xff08;二&#xff09;存储空间管理——空闲表法&#xff08;三&#xff09;存储空间管理——空闲链表法&#xff08;1&#xff09;空闲盘块链&#xff08;2&#xff09;空闲盘区链 &…

VScode----debug调试python代码添加上额外命令(args)

这里写目录标题 问题描述问题解决 更多内容可以点击这里查看个人博客&#xff1a;个人博客 问题描述 在服务器上运行python代码时&#xff0c;总会添加上额外的参数一般是用jyputer或者终端直接加上命令&#xff0c;现在我在vscode调试远程代码的时候想要加上这些命令. 问…

DFL《384底丹 430万》 wf/df-udt/448/96/96/32预训练模型

384底丹430万迭代&#xff1a;点击下载 训练素材19万张来自于以下数据集&#xff1a; 【更新】DST全角度训练图集V3.1 WF512【2.6W张 6GB 】【人脸混合_WF】FFHQ女性人脸数据&#xff0c;预训练炼丹专用【金鱼基础模型库】用于补全SRC极限角度香港中文大学CelebA预训练集-WF5…

HarmonyOS NEXT应用开发—状态栏显隐变化

介绍 本示例介绍使用Scroll组件的滚动事件 onScroll 实现状态栏显隐变化。该场景多用于各种软件的首页、我的等页面中。 效果预览图 使用说明 加载完成后显示状态栏显隐变化页面&#xff0c;上下拖动屏幕&#xff0c;顶端状态栏出现显隐变化。 实现思路 在置顶位置使用sta…

Vue-router3.0版本跳转报错

1.路由创建之后发现控制台push路由跳转报错了 2.解决方法&#xff1a; //在router文件中添加 const originalPush VueRouter.prototype.push VueRouter.prototype.push function push(location) {return originalPush.call(this, location).catch(err > err) }3.解决了

webpack5零基础入门-10babel的使用

Babel JavaScript 编译器。 主要用于将 ES6 语法编写的代码转换为向后兼容的 JavaScript 语法&#xff0c;以便能够运行在当前和旧版本的浏览器或其他环境中 1.安装相关包 npm install -D babel-loader babel/core babel/preset-env 2.进行相关配置 2.1第一种写法是在webp…

Day67:WEB攻防-Java安全JNDIRMILDAP五大不安全组件RCE执行不出网

知识点&#xff1a; 1、Java安全-RCE执行-5大类函数调用 2、Java安全-JNDI注入-RMI&LDAP&高版本 3、Java安全-不安全组件-Shiro&FastJson&JackJson&XStream&Log4j Java安全-RCE执行-5大类函数调用 Java中代码执行的类&#xff1a; GroovyRuntimeExecPr…

git如何回退版本reset和revert命令的区别

文章目录 git回退版本的方法使用reset回退使用revert回退 总结 git回退版本的方法 Git回退到某个版本有两种方法&#xff1a;reset和revert。 使用reset回退 git reset --hard <版本号>该命令将HEAD指针移动到指定的版本&#xff0c;并重置工作目录和暂存区的内容。这…

微信小程序Skyline模式自定义tab组件胶囊与原生胶囊平齐,安卓和ios均自适应

进入下面小程序可以体验效果&#xff1a; 至于原理的话&#xff0c;解释起来毕竟麻烦&#xff0c;各位可以看源码自己分析。其实很简单&#xff0c;就算计算布局。很多网上公布的布局&#xff0c;都不能正常自适应。在下这个是完美可以的 1、WXML <view class"weui…

【遍历方法】浅析Java中字符串、数组、集合的遍历

目录 前言 字符串篇 1.1 使用 for 循环和 charAt 方法 1.2 使用增强 for 循环&#xff08;forEach 循环&#xff09; 1.3 使用 Java 8 的 Stream API 最终效果 数组篇 2.1 使用普通 for 循环 2.2 使用增强型 for 循环( forEach 循环) 2.3 使用 Arrays.asList 和 forE…

Python之Web开发中级教程----配置数据库

Python之Web开发中级教程----配置数据库 在settings.py中保存了数据库的连接配置信息&#xff0c;Django默认初始配置使用sqlite数据库。 DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: os.path.join(BASE_DIR, db.sqlite3), } } 如果需要用MySQL数据…

LeetCode 0310.最小高度树:拓扑排序秒了

【LetMeFly】310.最小高度树&#xff1a;拓扑排序秒了 力扣题目链接&#xff1a;https://leetcode.cn/problems/minimum-height-trees/ 树是一个无向图&#xff0c;其中任何两个顶点只通过一条路径连接。 换句话说&#xff0c;一个任何没有简单环路的连通图都是一棵树。 给你…

nginx做静态代理方式

改配置文件 server {listen 8899;server_name localhost;location / {root html;index index.html index.htm;} } 生成页面代码 例子 GetMapping("createIndex")public Result createIndex() {//获取后台存储数据Result result productFeignClient.getB…

python之万花尺

1、使用模块 import sys, random, argparse import numpy as np import math import turtle import random from PIL import Image from datetime import datetime from math import gcd 依次使用pip下载即可 2、代码 import sys, random, argparse import numpy as np imp…

Linux环境开发工具之yum

前言 前面我们已经对基本的指令和权限进行了介绍&#xff0c;本期开始我们将介绍常用的开发工具。例如&#xff1a;软件包管理器yum。 本期内容介绍 Linux上安装软件的方式 什么是yum yum的相关操作 yum的本地配置和yum源 一、Linux上安装软件的方式 在介绍Linux上如何安装一…

Docker 安装 Skywalking以及UI界面

关于Skywalking 在现代分布式系统架构中&#xff0c;应用性能监控&#xff08;Application Performance Monitoring, APM&#xff09;扮演着至关重要的角色。本文将聚焦于一款备受瞩目的开源APM工具——Apache Skywalking&#xff0c;通过对其功能特性和工作原理的详细介绍&am…

打破数据孤岛,TDengine 与 Tapdata 实现兼容性互认证

当前&#xff0c;传统行业正面临着数字化升级的紧迫需求&#xff0c;但海量时序数据的处理以及数据孤岛问题却日益突出。越来越多的传统企业选择引入时序数据库&#xff08;Time Series Database&#xff0c;TSDB&#xff09;升级数据架构&#xff0c;同时&#xff0c;为了克服…

C++ 笛卡尔树

目录 一、性质二、构建笛卡尔树三、应用四、源码 一、性质 堆性质&#xff1a; 笛卡尔树是一种满足堆性质的树。每个节点包含两个值&#xff1a;键值&#xff08;key&#xff09;和优先级值&#xff08;priority&#xff09;。在笛卡尔树中&#xff0c;根节点的优先级值最大&am…