从零开始搭建游戏服务器 第二节 Actor模型与应用

news2024/10/7 16:20:23

目录

  • 复习
  • 本节内容
  • 正文
    • 什么是Actor模型
    • 如何应用
    • 创建Actor基类
    • 创建RootActor
    • 创建AkkaContext
    • 创建ConnectActorManager和ConnectActor
    • 生成actor并发送消息给它
  • 课后作业
  • 结尾

复习

上一节我们使用gradle构建了一个多模块系统。
并且在登录服启动了Netty服务,监听config文件中配置的端口。
在client模块中使用Netty创建一个客户端连接到登录服,并且发送并接收协议。

本节内容

本节我们学习一下Actor模型。并将其应用在我们的登录服中。
我们将会为每一个连接上来的客户端生成一个actor专门用于处理该连接发送上来的请求数据。

正文

什么是Actor模型

Actor模型是一种强大的并发计算模型。
在Actor模型中,一个Actor是一个最基本的计算单元,它可以看作是一个个独立的实体,它们之间毫无关联,但是可以通过消息来通信。
一个Actor收到其他Actor的信息后,可以根据需要作出各种响应。每个Actor的数据相互隔离,使用消息传递的方式来进行并发操作,避免了多线程编程中常见的同步和共享内存问题,从而提高了程序的可靠性和可伸缩性。
使用Actor模型最好的一点在于,为Actor逻辑进行编码时,无需过多考虑多线程并发问题,因为它是通过消息驱动的且其内部数据只能由其本身进行修改,而每个Actor的消息处理是串行的,所以每个Actor内部的数据不会被并发修改。
出bug的情况较少,对新人友好,意味着可以多招新人程序员来开发以节约项目成本。

如何应用

要在java中使用Actor模型,可以使用akka的actor库进行开发。
Kilim是一个比akka更轻量化的actor库,但是社区规模相对较小。
本项目选择使用akka的actor库。

        implementation group: 'com.typesafe.akka', name: 'akka-actor-typed_3', version: '2.8.5'

创建Actor基类

因为我们项目中将会有多种不同功能的actor出现,我们先定一个actor基类用于规范所有actor。
创建BaseMsg和BaseActor两个类

/**
* 消息基类 所有Actor消息的基类
*/
public class BaseMsg implements Serializable {
}

/**
* Actor基类
*/
@Slf4j
public abstract class BaseActor extends AbstractBehavior<BaseMsg> {
    public BaseActor(ActorContext<BaseMsg> context) {
        super(context);
    }

    @Override
    public Receive<BaseMsg> createReceive() {
        ReceiveBuilder<BaseMsg> builder = newReceiveBuilder();
        builder.onMessage(BaseMsg.class, this::onBaseMsg);
        return builder.build();
    }

    private Behavior<BaseMsg> onBaseMsg(BaseMsg msg) {
        log.info("receive base msg. {}", msg.toString());
        return this;
    }
}

BaseActor继承与AbstractBehavior,它接收所有BaseMsg类型的消息,当收到BaseMsg消息时打印receive base msg.

创建RootActor

RootActor是一个用于初始化ActorSystem和创建其他Actor的守护Actor

public class RootActor extends BaseActor{
    public RootActor(ActorContext<BaseMsg> context) {
        super(context);
    }
    public static Behavior<BaseMsg> create() {
        return Behaviors.setup(RootActor::new);
    }
}

创建AkkaContext

AkkaContext用于储存ActorSystem上下文,并提供创建Actor的方法。

final public class AkkaContext {

    private static ActorSystem<BaseMsg> ACTOR_SYSTEM;

    public static void initActorSystem() {
        ACTOR_SYSTEM = ActorSystem.create(RootActor.create(), "ActorSystem");
    }

    public static ActorRef<BaseMsg> createActor(Behavior<BaseMsg> behavior, String name) {
        return ACTOR_SYSTEM.systemActorOf(behavior, name, Props.empty());
    }
}

服务器启动时调用initActorSystem初始化akka上下文。需要生成Actor则调用createActor。

创建ConnectActorManager和ConnectActor

在登录服中,我们会为每一个连接分配一个Actor,称之为ConnectActor,用于接收各个连接发送过来的消息,并串行地处理消息逻辑。
为了管理ConnectActor,我们需要一个ConnectActorManager用于存储每一个连接与connectActor的映射关系。

首先我们需要为每一个连接分配一个唯一id,称之为connectId。
修改LoginNettyHandler中负责处理建立连接逻辑的接口channelActive

	// 登录服ctx自定义属性
    private static final AttributeKey<HashMap<String, Object>> loginContextAttr = AttributeKey.valueOf("login");
	/**
     * 建立连接
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
        String ip = address.getAddress().getHostAddress();

        HashMap<String, Object> context = this.getContextAttrMap(ctx);
        if (context == null) {
            context = new HashMap<>();
            ctx.channel().attr(loginContextAttr).set(context);
        }
        // 先只用uuid的64位来做connectId,只要不超过64位理论上不会重复
        long connectId = UUID.randomUUID().getLeastSignificantBits();
        context.put("connectId", connectId);
        context.put("ip", ip);

        if (ctx.channel().isActive()) {
            log.info("创建连接—成功:ip = {},connectId = {}", ip, connectId);
        }
    }
    /**
     * 从ctx中获取自定义属性参数
     */
    private HashMap<String, Object> getContextAttrMap(ChannelHandlerContext ctx) {
        return ctx.channel().attr(loginContextAttr).get();
    }

我们为每一个连接设置了一个存放自定义属性的HashMap,然后使用UUID生成了一个connectId存入其中。

然后我们创建一个ConnectActor类,和RootActor类类似,继承BaseActor类,并实现create方法。

public class ConnectActor extends BaseActor {

    private long connectId;

    private ChannelHandlerContext ctx;

    public ConnectActor(ActorContext<BaseMsg> context, long connectId, ChannelHandlerContext ctx) {
        super(context);
        this.connectId = connectId;
        this.ctx = ctx;
    }

    /**
     * 生成一个ConnectActor的行为
     */
    public static Behavior<BaseMsg> create(long connectId, ChannelHandlerContext ctx) {
        return Behaviors.setup(context -> new ConnectActor(context, connectId, ctx));
    }
}

创建ConnectActorManager类,用于管理所有的ConnectActor。

@Component
public class ConnectActorManager {

    private final Map<Long, ActorRef<BaseMsg>> connectActorMap = new ConcurrentHashMap<>();

    public ActorRef<BaseMsg> getConnectActor(long connectId) {
        return connectActorMap.get(connectId);
    }

    public static ConnectActorManager getInstance() {
        return SpringUtils.getBean(ConnectActorManager.class);
    }
    /**
     * 创建一个ConnectActor
     */
    public ActorRef<BaseMsg> createConnectActor(long connectId, ChannelHandlerContext ctx) {
        ActorRef<BaseMsg> actor = AkkaContext.createActor(ConnectActor.create(connectId, ctx), String.valueOf(connectId));
        connectActorMap.put(connectId, actor);
        return actor;
    }
}

在ConnectActorManager类中,我们定义了一个ConcurrentHashMap,用于存放所有的ConnectActor,因为Netty接收数据是多线程的,所以可能有多个线程同时对其进行读写操作,所以使用ConcurrentHashMap保证线程安全。

生成actor并发送消息给它

我们修改LoginNettyHandler类,处理读取数据的接口channelRead0.

    @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 = SpringUtils.getBean(ConnectActorManager.class);
        ActorRef<BaseMsg> connectActor = actorManager.getConnectActor(connectId);
        if (connectActor == null) {
            connectActor = actorManager.createConnectActor(connectId, ctx);
        }
        log.info(new String(msg));
        BaseMsg baseMsg = new BaseMsg();
        connectActor.tell(baseMsg);
        ctx.channel().writeAndFlush(msg);
    }

当接收到数据时,我们从ctx中获得建立连接时生成的connectId,再用其去ConnectActorManager中获取对应的connectActor,没有则创建一个新的。
然后我们创建一个消息BaseMsg,将其发送给connectActor进行处理。
最后我们将数据原封不动地返回回去。

运行一下试试。
运行结果
可以看到我们的服务器收到了test消息,并且给ConnectActor发送了一个BaseMsg消息。

课后作业

现在我们可以给ConnectActor发送消息了,是不是可以将BaseMsg细分成更多不一样的消息,用于处理ctx的不同类型的数据。
我预计为BaseMsg创建不同的子类,用来通知ConnectActor来做出不同的行为。
ClientUpMsg:客户端上行数据消息;
ConnectClosedMsg:客户端断开消息;

但是我不会在这里将代码展示出来,我希望读者们可以自己去实现这两个消息,只需要打印出不同的日志就行。

结尾

本节我们讲了Actor模型并且在实战中使用了它。
Actor模型是一个非常好用的并发计算模型,有了它可以使开发者不用过多关心并发问题和数据共享问题,只需要专注于业务逻辑的开发即可。
希望本节的内容能对你有所帮助,有任何问题可以评论或私信。
感兴趣的同学可以关注该专栏,我会持续更新更多关于游戏服务器开发的内容。

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

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

相关文章

字符串的模式匹配算法

一、朴素模式匹配算法 二、KMP算法 三、KMP求Next数组 四、KMP求NextVal数组

Nacos与Eureka的使用与区别

Nacos与Eureka的使用与区别 单体架构&#xff1a;优点缺点 分布式架构需要考虑的问题&#xff1a;微服务企业需求 认识SpringCloud服务的拆分与远程调用微服务调用方式 Eureka提供者和消费者架构搭建Eureka服务注册服务发现 Ribbon负载均衡饥饿加载总结 Nacos注册中心Nacos安装…

Task-balanced distillation for object detection用于

Task-balanced distillation for object detection用于目标检测的任务平衡蒸馏 摘要 主流的目标检测器通常由分类和回归两个子任务组成&#xff0c;由两个并行头部实现。这种经典的设计范式不可避免的导致分类得分和定位质量&#xff08;IOU&#xff09;之间的空间分布不一致…

鸿蒙开发实现弹幕功能

鸿蒙开发实现弹幕功能如下&#xff1a; 弹幕轮播组件&#xff1a;BannerScroll import type { IDanMuInfoList, IDanMuInfoItem } from ../model/DanMuData //定义组件 Component export default struct BannerScroll {//Watch 用来监视状态数据的变化&#xff0c;包括&#…

软考79-上午题-【面向对象技术3-设计模式】-结构型设计模式02

一、组合模式 1-1、意图 将对象组合成树型结构&#xff0c;以表示"部分-整体"的层次结构。Composite使得用户对单个对象和组 合对象的使用具有一致性。 示例&#xff1a;对象&#xff1a;文件、文件夹 1-2、结构 Component 为组合中的对象声明接口&#xff1b;在适…

CKA认证之Etcd备份与恢复

题目介绍&#xff1a; 资料参考&#xff1a; https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/configure-upgrade-etcd 解题&#xff1a; 1、备份 #参考模板列出 etcdctl 可用的各种选项。 #例如&#xff0c;你可以通过指定端点、证书和密钥来制作快照&#xff0…

Android 开发 地图 polygon 显示信息

问题 Android 开发 地图 polygon 显示信息 详细问题 笔者进行Android项目开发&#xff0c;接入高德地图绘制区域后&#xff0c;需要在指定区域&#xff08;位置&#xff09;内显示文本信息&#xff0c;如何实现 实现效果 解决方案 代码 import com.amap.api.maps.model.T…

Day47-http和www基础2

Day47-http和www基础2 1.Linux下实践观察HTTP协议通信过程1.1 DNS解析过程1.2 tcp三次握手1.3 发起HTTP请求报文阶段1.4 请求报文阶段1.5 响应报文部分1.6 分析URL&#xff0c;DNS解析1.7 HTTPS连接建立的过程1.8 请求报文阶段1.8.1 referer1.8.2 如何解决防盗链&#xff1f; 2…

【机器学习】分类模型的评价方法

&#x1f33b;个人主页&#xff1a;相洋同学 &#x1f947;学习在于行动、总结和坚持&#xff0c;共勉&#xff01; #学习笔记# 目录 一、混淆矩阵&#xff08;Confusion Matrix&#xff09; 二、评估指标&#xff08;Evaluation metrics&#xff09; 1.正确率(accuracy) …

HCIP—OSPF课后练习一

本实验模拟了一个企业网络场景&#xff0c;R1、R2、R3为公司总部网络的路由器&#xff0c;R4、R5分别为企业分支机构1和分支机构2的路由器&#xff0c;并且都采用双上行方式与企业总部相连。整个网络都运行OSPF协议&#xff0c;R1、R2、R3之间的链路位于区域0&#xff0c;R4与R…

数据分析-Pandas的直接用Matplotlib绘图

数据分析-Pandas的直接用Matplotlib绘图 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表…

Swift 面试题及答案整理,最新面试题

Swift 中如何实现单例模式&#xff1f; 在Swift中&#xff0c;单例模式的实现通常采用静态属性和私有初始化方法来确保一个类仅有一个实例。具体做法是&#xff1a;定义一个静态属性来存储这个单例实例&#xff0c;然后将类的初始化方法设为私有&#xff0c;以阻止外部通过构造…

[java基础揉碎]多态参数

多态参数 方法定义的形参类型为父类类型&#xff0c;实参类型允许为子类类型 例子: 定义一个员工类, 有名字和工资两个属性, 有年工资的方法 定义一个普通员工继承了员工类 , 重写了年工资的方法 定义一个经理类, 也继承了员工类, 同时经理多以了一个奖金的属性, 重写的年…

Ubuntu 如何安装 Beyond Compare?

Ubuntu20.04安装Beyond Compare 4.3.7 一、官网下载方式一&#xff1a;方法二&#xff1a;使用 .deb 包安装 二、安装相关依赖和bcompare三、破解常见错误解决方法 ) 文件比较工具Beyond Compare是一套由Scooter Software推出的文件比较工具。主要用途是对比两个文件夹或者文件…

JUnit 面试题及答案整理,最新面试题

JUnit中的断言&#xff08;Assert&#xff09;有哪些类型&#xff1f; JUnit提供了多种断言类型来帮助测试代码的正确性。常见的断言类型包括&#xff1a; 1、assertEquals&#xff1a; 用于检查两个值是否相等。如果不相等&#xff0c;测试失败。 2、assertTrue和assertFal…

监控系统prometheus+grafana+发送告警信息

1、基础环境准备两台或更多的主机 2、关闭selinux vi /etc/selinux/config&#xff0c;修改SELINUX的值为disabled 3、关闭防火墙 systemctl disable firewalld systemctl stop firewalld 4、prometheus官网下载 https://prometheus.io/download/ 5、grafana官网下载 https…

HTML语法基础

1.HTML是什么 HTML是超文本标记语言&#xff0c;标准通用标记语言下的一个应用。 “超文本”就是指页面内可以包含图片、链接&#xff0c;甚至音乐、程序等非文字元素。 超文本标记语言的结构包括“头”部分&#xff08;英语&#xff1a;Head&#xff09;、和“主体”部分&…

如何在“Microsoft Visual Studio”中使用OpenCV编译应用程序

返回目录&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 前一篇&#xff1a;OpenCV4.9.0在windows系统下的安装 后一篇&#xff1a; 警告&#xff1a; 本教程可以包含过时的信息。 我在这里描述的所有内容都将适用于 OpenCV 的C\C接口。我首先假…

在centos8中部署Tomcat和Jenkins

参考链接1&#xff1a;tomcat安装和部署jenkins_jenkins和tomcat-CSDN博客 参考链接2&#xff1a;--配置开机启动tomcat文件 x​​​​​​超详细&#xff1a;Centos8安装Tomcat并配置开机自动启动_centos设置tomcat开机自启-CSDN博客文章浏览阅读4.4k次&#xff0c;点赞4次&…

爱奇艺 CTR 场景下的 GPU 推理性能优化

01 背景介绍 GPU 目前大量应用在了爱奇艺深度学习平台上。GPU 拥有成百上千个处理核心&#xff0c;能够并行的执行大量指令&#xff0c;非常适合用来做深度学习相关的计算。在 CV&#xff08;计算机视觉&#xff09;&#xff0c;NLP&#xff08;自然语言处理&#xff09;的模型…