关于akka官方quickstart示例程序(scala)的记录
参考资料
- https://doc.akka.io/libraries/akka-core/current/typed/actors.html#first-example
关于scala语法的注意事项
extends App
是个语法糖,等同于直接在伴生对象中编写main 方法- 对象是通过apply方法创建的,也可以通过对象的名称单独创建(此时实际上会调用apply方法)
- case class 样例类用于定义不可变类,可以用于模式匹配
- trait类似接口但是可以包括抽象方法,具体方法,子类。带有sealed表示只能在定义它的同一个文件中被继承,常用于更加安全的模式匹配,例如如果消息类型为sealed trait,则actor可以安全接受多种消息。
helloworld示例
代码的整体示意图如下
- HelloWorldMain创建ActorSystem,作为一个actorref指向HelloWorldMain actor。使用此引用向HelloWorldMain actor发送SayHello消息
- HelloWorldMain actor初始化Helloworld actor和HelloWorldBot,以及在收到SayHello消息后向HelloWorld actor发送Greet消息(其中带有HelloWorldBot的actorref)
- HelloWorld actor收到消息后向HelloWorldBot发送Greeted消息
- HelloWorldBot actor收到消息后greetingCounter计数增加,并向HelloWorld actor返回Greet消息。当greetingCounter超过max时暂停行为。
代码示例
//#imports
import akka.actor.typed.ActorRef
import akka.actor.typed.ActorSystem
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors//#hello-world-actor
object HelloWorld {// 使用样例类定义消息类型final case class Greet(whom: String, replyTo: ActorRef[Greeted])final case class Greeted(whom: String, from: ActorRef[Greet])// Behaviors.receive函数接收一个函数作为参数,{}是为了容纳多行lambda表达式def apply(): Behavior[Greet] = Behaviors.receive { (context, message) =>context.log.info("Hello {}!", message.whom)message.replyTo ! Greeted(message.whom, context.self) // 向message.replyTo发送消息Greeted,其中context.self是自身的actorrefBehaviors.same // 设置后续的消息处理逻辑不变}
}//#hello-world-bot
object HelloWorldBot {def apply(max: Int): Behavior[HelloWorld.Greeted] = {bot(0, max)}private def bot(greetingCounter: Int, max: Int): Behavior[HelloWorld.Greeted] =Behaviors.receive { (context, message) =>val n = greetingCounter + 1context.log.info("Greeting {} for {}", n, message.whom)if (n == max) {Behaviors.stopped // 到达max次数后停止行为,避免无限循环} else {message.from ! HelloWorld.Greet(message.whom, context.self)bot(n, max)}}
}//#hello-world-main
object HelloWorldMain {final case class SayHello(name: String)def apply(): Behavior[SayHello] =Behaviors.setup { context =>val greeter = context.spawn(HelloWorld(), "greeter") // 初始化HelloWorldBehaviors.receiveMessage { message => // 收到消息后创建HelloWorldBotval replyTo = context.spawn(HelloWorldBot(max = 3), message.name)greeter ! HelloWorld.Greet(message.name, replyTo)Behaviors.same}}def main(args: Array[String]): Unit = {val system: ActorSystem[HelloWorldMain.SayHello] =ActorSystem(HelloWorldMain(), "hello")system ! HelloWorldMain.SayHello("World")Thread.sleep(3000)system.terminate()}
}
chatroom示例
代码的整体示意图如下
- Main启动后,初始化chatRoom和Gabbler客户端。向ChatRoom发送GetSession消息(带有client actorref)
- chatRoom创建session actor,用来隔离会话
- chatRoom向client发送SessionGranted消息(带有session actorref)
- client(Gabbler)收到SessionGranted后向session actor发送PostMessage消息
- session 收到SessionGranted后向room发送PublishSessionMessage
- room返回NotifyClient给session
- 然后按照NotifyClient(带有MessagePosted)中的client actorref将MessagePosted转发给特定的client
- client收到MessagePosted之后完成并推出
整体的思路
- ChatRoom Actor:作为中央枢纽,负责管理所有的会话(Sessions)。每个连接到聊天室的客户端都会通过
GetSession
消息与 ChatRoom 交互,并获得一个专属的会话 Actor。 - Session Actor:每个客户端都有一个对应的 Session Actor,用于处理该客户端的消息收发、保持客户端状态等。
- client(Gabbler)Actor:模拟客户端行为,可以发送消息给 ChatRoom 或者其他客户端。
- 客户端间通信:通过 ChatRoom 转发消息来实现客户端间的通信。当一个客户端发送消息时,它实际上是将消息发送给了 ChatRoom,然后由 ChatRoom 将消息广播给所有其他在线的客户端。
代码示例
定义消息,这里实际上等同于定义actor之间的通信协议
- RoomCommand,用来获取session
- SessionEvent,用来管理session和发送message
- SessionCommand,用来发送message和通知client
object ChatRoom {sealed trait RoomCommandfinal case class GetSession(screenName: String, replyTo: ActorRef[SessionEvent]) extends RoomCommandsealed trait SessionEventfinal case class SessionGranted(handle: ActorRef[PostMessage]) extends SessionEventfinal case class SessionDenied(reason: String) extends SessionEventfinal case class MessagePosted(screenName: String, message: String) extends SessionEventsealed trait SessionCommandfinal case class PostMessage(message: String) extends SessionCommandprivate final case class NotifyClient(message: MessagePosted) extends SessionCommand
}
ChatRoom actor部分
object ChatRoom {// PublishSessionMessage消息将包含的ChatRoom消息传播到所有连接的客户端private final case class PublishSessionMessage(screenName: String, message: String) extends RoomCommanddef apply(): Behavior[RoomCommand] =chatRoom(List.empty)private def chatRoom(sessions: List[ActorRef[SessionCommand]]): Behavior[RoomCommand] =Behaviors.receive { (context, message) =>message match {// 如果收到GetSession,create a child actor for further interaction with the clientcase GetSession(screenName, client) =>val ses = context.spawn(session(context.self, screenName, client),name = URLEncoder.encode(screenName, StandardCharsets.UTF_8.name))client ! SessionGranted(ses)chatRoom(ses :: sessions) // ::用于将ses添加到sessions头。由于Akka 的行为是不可变的(每次更改状态都必须返回一个新的 behavior),所以通常通过 递归函数 + 参数携带状态 的方式来模拟“状态变化”// 如果接收到 PublishSessionMessage 就向所有的session发送notification,每个session都带有client内容。等于是申请chatroom允许发送case PublishSessionMessage(screenName, message) =>val notification = NotifyClient(MessagePosted(screenName, message))sessions.foreach(_ ! notification) // 将消息转发给session中的所有clientBehaviors.same}}// 用于创建session actor,接受SessionCommand消息private def session(room: ActorRef[PublishSessionMessage],screenName: String,client: ActorRef[SessionEvent]): Behavior[SessionCommand] =Behaviors.receiveMessage {// 向room中的所有其他用户发送消息case PostMessage(message) =>room ! PublishSessionMessage(screenName, message)Behaviors.same// room发布消息通知clientcase NotifyClient(message) =>client ! messageBehaviors.same}
}
客户端部分
object Gabbler {import ChatRoom._def apply(): Behavior[SessionEvent] =Behaviors.setup { context =>Behaviors.receiveMessage {case SessionDenied(reason) =>context.log.info("cannot start chat room session: {}", reason)Behaviors.stoppedcase SessionGranted(handle) =>handle ! PostMessage("Hello World!")Behaviors.samecase MessagePosted(screenName, message) =>context.log.info("message has been posted by '{}': {}", screenName, message)Behaviors.stopped}}
actorsystem入口
- 这里使用了Behaviors.setup。
Behaviors.setup
和Behaviors.receiveMessage
都是用于定义 Actor 行为的工厂方法,区别是Behaviors.setup
允许你在初始化阶段访问ActorContext
,而Behaviors.receiveMessage
不直接提供对上下文的访问,专注于消息处理逻辑 Main
Actor,对应于传统 Java 应用程序中的main
方法
object Main {def apply(): Behavior[NotUsed] =Behaviors.setup { context =>val chatRoom = context.spawn(ChatRoom(), "chatroom")val gabblerRef = context.spawn(Gabbler(), "gabbler")context.watch(gabblerRef) //监控gabbler actor,如果gabbler 终止了,当前 Actor 将收到一个 Terminated(gabblerRef) 信号chatRoom ! ChatRoom.GetSession("ol’ Gabbler", gabblerRef)// 处理 Terminated 信号Behaviors.receiveSignal {case (_, Terminated(_)) =>Behaviors.stopped}}def main(args: Array[String]): Unit = {ActorSystem(Main(), "ChatRoomDemo")}
}
运行结果如下,按照预期逻辑,目前只有一个client,并且发送消息收到响应后推出
相关文章:

关于akka官方quickstart示例程序(scala)的记录
参考资料 https://doc.akka.io/libraries/akka-core/current/typed/actors.html#first-example 关于scala语法的注意事项 extends App是个语法糖,等同于直接在伴生对象中编写main 方法对象是通过apply方法创建的,也可以通过对象的名称单独创建&#x…...

2025年渗透测试面试题总结-腾讯[实习]玄武实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]玄武实验室-安全工程师 1. 自我介绍 2. CSRF原理 3. Web安全入门时间 4. 学习Web安全的原因 …...

网站首页菜单两种布局vue+elementui顶部和左侧栏导航
顶部菜单实现 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Vue.js Element UI 路由导航</…...
AWS之迁移与传输服务
目录 一、迁移管理与规划类 二、应用程序迁移类 1. MGN的迁移范围(能替代的场景) ✅ 自动包含以下数据: 🔹 适用场景举例: 2. 仍需DMS或DataSync的场景(不可替代) ❌ DMS仍必要的情况: ❌ DataSync仍必要的情况: 3. 技术原理对比 MGN的数据迁移机制: DMS…...

@Builder的用法
Builder 是 Lombok 提供的一个注解,用于简化 Java 中构建对象的方式(Builder 模式)。它可以让你以更加简洁、链式的方式来创建对象,尤其适用于构造参数较多或部分可选的类。...
Unity3D 逻辑代码性能优化策略
前言 在Unity3D中优化逻辑代码性能是提升游戏流畅度的关键。以下是系统性的优化策略和示例: 对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交流一下开发经验呀! 1. 避免高频操作中的开销 缓存组件引用 private …...
【Python Cookbook】文件与 IO(二)
文件与 IO(二) 6.字符串的 I/O 操作7.读写压缩文件8.固定大小记录的文件迭代(⭐⭐) 6.字符串的 I/O 操作 你想使用操作类文件对象的程序来操作文本或二进制字符串。 使用 io.StringIO() 和 io.BytesIO() 类来创建类文件对象操作…...

vue实现点击按钮input保持聚焦状态
主要功能: 点击"停顿"按钮切换对话框显示状态输入框聚焦时保持状态点击对话框外的区域自动关闭 以下是代码版本: <template><div class"input-container"><el-inputv-model"input"style"width: 2…...

[蓝桥杯]取球博弈
取球博弈 题目描述 两个人玩取球的游戏。 一共有 NN 个球,每人轮流取球,每次可取集合 n1,n2,n3n1,n2,n3中的任何一个数目。 如果无法继续取球,则游戏结束。 此时,持有奇数个球的一方获胜。 如果两人都是奇数ÿ…...
Spring Security入门:创建第一个安全REST端点项目
项目初始化与基础配置 创建基础Spring Boot项目 我们首先创建一个名为ssia-ch2-ex1的空项目(该名称与配套源码中的示例项目保持一致)。项目需要添加以下两个核心依赖: org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-secur…...

[Java 基础]数组
什么是数组?想象一下,你需要存储 5 个学生的考试成绩。你可以声明 5 个不同的 int 变量,但这会显得很笨拙。数组提供了一种更简洁、更有组织的方式来存储和管理这些数据。 数组可以看作是相同类型元素的集合,这些元素在内存中是连…...
fastadmin fildList 动态下拉框默认选中
html页面 <td><select class"form-control dtselect" data-rule"required" data-dtselected"<%row.type%>" name"<%name%>[<%index%>][type]">{foreach nametypeList idvo}<option value"{$vo…...
java学习笔记——数组和二维数组
一、一维数组 1. 定义数组 语法: // 动态初始化(指定长度) 数据类型[] 数组名 = new 数据类型[长度]; // 示例: int[] arr1 = new int[5]; // 默认值:0// 静态初始化(直接赋值) 数据类型[] 数组名 = {元素1, 元素2, ...}; // 示例: String[]…...

‘pnpm‘ 不是内部或外部命令,也不是可运行的程序
npm install -g pnpm changed 1 package in 4s 1 package is looking for funding run npm fund for details C:\Users\gang>pnpm pnpm 不是内部或外部命令,也不是可运行的程序 或批处理文件。 原来是安装的全局路径被我改了 npm list -g --depth 0 把上述…...

Android Test2 获取系统android id
Android Test2 获取系统 android id 这篇文章针对一个常用的功能做一个测试。 在项目中,时常会遇到的一个需求就是:一台设备的唯一标识值。然后,在网络请求中将这个识别值传送到后端服务器,用作后端数据查询的条件。Android 设备…...

webpack打包学习
vue开发 现在项目里安装vue: npm install vue vue的文件后缀是.vue webpack不认识vue的话就接着安插件 npm install vue-loader -D 这是.vue文件: <template> <div><h2 class"title">{{title}}</h2><p cla…...

基于Java(Jsp+servelet+Javabean)+MySQL实现图书管理系统
图书管理系统 一、需求分析 1.1 功能描述 1.1.1“读者”功能 1)图书的查询:图书的查询可以通过搜索图书 id、书名、作者名、出版社来实现,显示结果中需要包括书籍信息以及是否被借阅的情况; 2)图书的借阅:借阅图书…...

服务器CPU被WMI Provider Host系统进程占用过高,导致系统偶尔卡顿的排查处理方案
问题现状 最近一个项目遇到一个非常奇葩的问题:正式服务器被一个WMI Provider Host的系统进程占用大量的CPU资源,导致我们的系统偶尔卡顿 任务管理器-详细信息中CPU时间,这个进程也是占用最多的 接口时不时慢很多 但单独访问我们的接口又正…...

JavaSwing之--JMenuBar
Java Swing之–JMenuBar(菜单栏) JMenuBar是 Java Swing 库中的一个组件,用于创建菜单栏,通常位于窗口的顶部。它是菜单系统的容器,用于组织和显示应用程序的菜单结构 菜单栏由菜单构成,菜单由菜单项或子菜单构成,也…...
vue3+elementplus表格表头加图标及文字提示
表头加自定义内容有很多种方法,包括使用el-icon,插槽,CSS 伪元素添加图标还有font-awesome等等。 一、方法一:使用render-header属性 <el-table :data"tableData"><el-table-column prop"name" la…...

【物联网-S7Comm协议】
物联网-S7Comm协议 ■ 调试工具■ S7协议-简介■ S7协议和modbusTCP协议区别■ OSI 层 S7 协议■ S7协议数据结构 (TPKTCOTPS7Comm)■ TPKT(第五层:会话层) 总共占4个字节■ COTP(第六层:表示层…...
NLP中的input_ids是什么?
在自然语言处理(NLP)中,input_ids 是什么 在自然语言处理(NLP)中,input_ids 是将文本转换为模型可处理的数字表示后的结果,是模型输入的核心参数之一。 一、基本概念 文本数字化 原始文本(如 “Hello world!”)无法直接被模型处理,需要通过分词器(Tokenizer) 将其…...
LeetCode Hot100刷题——划分字母区间
763.划分字母区间 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 "ababcc" 能够被分为 ["abab", "cc"],但类似 ["aba", "bcc"…...
c++ 基于OpenSSL的EVP接口进行SHA3-512和SM3哈希计算
通过OpenSSL的EVP接口进行 SHA3-512 和 SM3 哈希计算 #include <iostream> #include <openssl/evp.h> #include <cstring>using namespace std;void PrintHex(const std::string &hexStr) {for (unsigned char c : hexStr){printf("%02x", c)…...
Vue3实现拖拽改变元素大小
代码实现 整体页面结构通过一个 dragResize-wrapper 包含左右两个区域,左侧区域包含一个可拖拽的边界。以下是关键代码 HTML 部分 <template><div class"dragResize-wrapper"><div class"dragResize-left"><div class&…...
Spring IoC 详解:原理、实现与实战
Spring IoC 详解:原理、实现与实战 前言 Spring IoC(Inversion of Control,控制反转)是Spring框架的核心基础。它通过解耦对象的创建与依赖关系管理,极大提升了系统的可维护性和扩展性。本文将系统梳理Spring IoC的原…...
深入Java NIO:构建高性能网络应用
引言 在上一篇文章中,我们介绍了Java网络编程的基础模型:阻塞式I/O和线程池模型。这些模型在处理高并发场景时存在明显的局限性。本文将深入探讨Java NIO(New I/O)技术,这是一种能够显著提升网络应用性能的非阻塞I/O模…...

数据分析后台设计指南:实战案例解析与5大设计要点总结
引言 数据于企业而言异常重要,企业通过数据可以优化战略决策,因此企业对数据的采集正趋向智能化、数字化,数据分析后台就是企业智能化、数字化记录、分析数据的渠道。本文分享一个数据分析后台原型实战案例,通过页面拆解总结原型…...
深度学习之模型压缩三驾马车:基于ResNet18的模型剪枝实战(1)
一、背景:为什么需要模型剪枝? 随着深度学习的发展,模型参数量和计算量呈指数级增长。以ResNet18为例,其在ImageNet上的参数量约为1100万,虽然在服务器端运行流畅,但在移动端或嵌入式设备上部署时…...
SSH/RDP无法远程连接?腾讯云CVM及通用服务器连接失败原因与超全排查指南
更多服务器知识,尽在hostol.com 嘿,各位服务器的“船长”和“管理员”们!咱们在浩瀚的数字海洋中驾驭着自己的服务器“战舰”,最怕遇到什么情况?除了数据丢失,恐怕就是突然发现自己被锁在“驾驶舱”门外—…...