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

news2024/12/24 8:52:07

目录

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

上一节问题答案公布

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

  1. 修改BaseActor.java
	@Override
   public 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方法

   @Override
   protected 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
   /**
    * 收到协议数据
    */
   @Override
   protected 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);
   }
   /**
    * 连接断开
    */
   @Override
   public 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

        // protobuf
        implementation 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 { // 客户端上行包,返回S2CPlayerRegister
    string 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.

	@Override
    protected 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://www.coloradmin.cn/o/1523553.html

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

相关文章

掌握C#: 从基础到精通 - 中级实战练习集

文章目录 异常处理尝试-捕获结构 文件 I/O 练习追加而不覆盖处理目录 LINQ 查询练习筛选集合中的对象排序复杂对象 类与对象练习继承与多态性 你是否已经掌握了C#的基础知识&#xff0c;正在寻找更多挑战来提升你的能力&#xff1f;那么&#xff0c;这篇文章就是为你准备的。我…

实锤!北大学者证实富钾盐代替食盐可安全降低高血压风险,发文顶刊JACC

编者 “要注意饮食&#xff0c;减少食盐摄入”这是高血压患者就诊时&#xff0c;医生说的最多的一句话。虽然低盐可以预防高血压&#xff0c;但国人食盐摄入量还是高于世界卫生组织&#xff08;成人每天摄入盐不超过5克&#xff09;的建议。 好在“天无绝人之路”&#xff0c;一…

人脸检测的5种实现方法

众所周知&#xff0c;人脸识别是计算机视觉应用的一个重大领域&#xff0c;在学习人脸识别之前&#xff0c;我们先来简单学习下人脸检测的几种用法。 常见的人脸检测方法大致有5种&#xff0c;Haar、Hog、CNN、SSD、MTCNN&#xff1a; 相关构造检测器的文件&#xff1a;opencv…

Java实现简单的通讯录

每日一言 泪眼问花花不语&#xff0c;乱红飞过秋千去。 —欧阳修- 简单的通讯录实现&#xff0c;跟写Java实现图书管理系统差不多&#xff0c;用到的知识也差不多&#xff0c;就当个小练习&#xff0c;练习一下写Java程序的手感。 Java实现图书管理系统 关于通讯录的代码都写…

P8681 [蓝桥杯 2019 省 AB] 完全二叉树的权值:做题笔记

目录 思路 代码 注意点 题目链接&#xff1a; P8681 [蓝桥杯 2019 省 AB] 完全二叉树的权值 (可跳) 这道题刚看到的时候想着主要就是算出每层2的次方个节点的权值和。 我的思路经过了很多次缝缝补补。创建一个sum数组&#xff0c;下标表示深度&#xff0c;每个元素代表…

Unity游戏项目接广告

Unity游戏项目中接入GoogleAdMob 先看效果图 接入测试横幅广告,代码如下: using System.Collections; using System.Collections.Generic; using UnityEngine; using GoogleMobileAds.Api; using System;public class GoogleAdMobManager : MonoBehaviour {private static …

C语言初学12:强制类型转换

一、强制数据类型转换举例 1.1 double赋值给int #include<stdio.h> int main() {double sum 18, count 5;int mean;mean sum / count;printf("Value of mean : %d\n", mean);} 执行结果&#xff1a; double赋值给int&#xff0c;小数部分会删除&#xff…

存内领域前沿,基于忆阻器的存内计算----浅析忆阻存内计算

目录 一.概念浅析 1.存内计算 2.忆阻器 3.基于忆阻器的存内计算 二.忆阻器的分类 1.磁效应忆阻器 2 .相变效应忆阻器 3 .阻变效应忆阻器 三.基于忆阻器的存内计算原理 1. 利用二值忆阻器的布尔计算 3.1R-R 逻辑运算 3.2V-R 逻辑运算 3.3V-V 逻辑运算 2. 利用模拟…

旋转中心 机械手抓料方式

一、为什么要计算旋转中心&#xff1f; 机器视觉——旋转中心的标定_旋转标定-CSDN博客 在机械手抓料的时候传送带上过来的料可能是各个角度的&#xff0c;不同的位置&#xff0c;这样如果我们没有做好机械手标定的话很难抓取&#xff0c;因此我们要做旋转中和和机械手TCP标定…

Oracle19c静默部署

Oracle19c静默部署文档 下载地址 https://www.oracle.com/database/technologies/oracle-database-software-downloads.html#db_free 一、系统基础配置 1、创建用户和用户组 # 创建oinstall和dba用户组 groupadd oinstall groupadd dba# 创建Oracle用户 useradd -g oinstall…

redis学习-Hash类型相关命令及特殊情况分析

目录 1. hset KEY key1 value1 key2 value2 ... 2. hget KEY key 3. hgetall KEY 4. hmget KEY key1 key2 ... 5. hkeys KEY 6. hvals KEY 7. hdel KEY key1 key2 ... 8. hlen KEY 9. hexists KEY key 10. hincrby KEY key num 11. hsetnx KEY key value Hash的内部…

HDFSDATANODE数据传输详解

本文主要阐述datanode中一个socket连接接收字节流的构成&#xff0c;帮助datanode的接收与处理数据。注意hadoop版本为3.1.1。 写在前面 Datanode本质上也是TCPServer&#xff0c;一般的TCPServer接到客户端请求以后会分配一个线程处理&#xff0c;对于Datanode而言&#xff…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:SideBarContainer)

提供侧边栏可以显示和隐藏的侧边栏容器&#xff0c;通过子组件定义侧边栏和内容区&#xff0c;第一个子组件表示侧边栏&#xff0c;第二个子组件表示内容区。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:TextTimer)

通过文本显示计时信息并控制其计时器状态的组件。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 TextTimer(options?: TextTimerOptions) 参数&#xff1a; 参数名参数类型…

数据仓库数据分层详解

数据仓库中的数据分层是一种重要的数据组织方式&#xff0c;其目的是为了在管理数据时能够对数据有一个更加清晰的掌控。以下是数据仓库中的数据分层详解&#xff1a; 原始数据层&#xff08;Raw Data Layer&#xff09;&#xff1a;这是数仓中最底层的层级&#xff0c;用于存…

【老旧小区用电安全谁能管?】安科瑞智慧用电安全管理系统解决方案

行业背景 电气火灾指由电气故障引发的火灾。每年以30%的比例高居各类火灾原因之首。以50%到80%的比例高居重特大火灾之首。已成为业界重点关注的对象并为此进行着孜孜不倦的努力。 国务院安委会也于2017年5月至2020年4月年开展了为期3年的电气火灾综合治理工作。在各界努力的…

主干网络篇 | YOLOv8更换主干网络之ShuffleNetV2

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。ShuffleNetV2是一种轻量级的神经网络架构&#xff0c;用于图像分类和目标检测任务。它是ShuffleNet的改进版本&#xff0c;旨在提高模型的性能和效率。ShuffleNetV2相比于之前的版本&#xff0c;在保持模型轻量化的同时&am…

LeetCode刷题小记 八、【回溯算法】

1.回溯算法 文章目录 1.回溯算法写在前面1.1回溯算法基本知识1.2组合问题1.3组合问题的剪枝操作1.4组合总和III1.5电话号码的字母组合1.6组合总和1.7组合总和II1.8分割回文串1.9复原IP地址1.10子集问题1.11子集II1.12非递减子序列1.13全排列1.14全排列II1.15N皇后1.16解数独 写…

WanAndroid(鸿蒙版)开发的第三篇

前言 DevEco Studio版本&#xff1a;4.0.0.600 WanAndroid的API链接&#xff1a;玩Android 开放API-玩Android - wanandroid.com 其他篇文章参考&#xff1a; 1、WanAndroid(鸿蒙版)开发的第一篇 2、WanAndroid(鸿蒙版)开发的第二篇 3、WanAndroid(鸿蒙版)开发的第三篇 …

三级等保技术建议书

1信息系统详细设计方案 1.1安全建设需求分析 1.1.1网络结构安全 1.1.2边界安全风险与需求分析 1.1.3运维风险需求分析 1.1.4关键服务器管理风险分析 1.1.5关键服务器用户操作管理风险分析 1.1.6数据库敏感数据运维风险分析 1.1.7“人机”运维操作行为风险综合分析 1.2…