关于akka官方quickstart示例程序(scala)的记录

news2025/6/7 23:55:56

参考资料

  • 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示例

代码的整体示意图如下

hello-world2.png

  1. HelloWorldMain创建ActorSystem,作为一个actorref指向HelloWorldMain actor。使用此引用向HelloWorldMain actor发送SayHello消息
  2. HelloWorldMain actor初始化Helloworld actor和HelloWorldBot,以及在收到SayHello消息后向HelloWorld actor发送Greet消息(其中带有HelloWorldBot的actorref)
  3. HelloWorld actor收到消息后向HelloWorldBot发送Greeted消息
  4. 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是自身的actorref
    Behaviors.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 + 1
      context.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") // 初始化HelloWorld
      Behaviors.receiveMessage { message => // 收到消息后创建HelloWorldBot
        val 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示例

代码的整体示意图如下

chat-room.png

  1. Main启动后,初始化chatRoom和Gabbler客户端。向ChatRoom发送GetSession消息(带有client actorref)
  2. chatRoom创建session actor,用来隔离会话
  3. chatRoom向client发送SessionGranted消息(带有session actorref)
  4. client(Gabbler)收到SessionGranted后向session actor发送PostMessage消息
  5. session 收到SessionGranted后向room发送PublishSessionMessage
  6. room返回NotifyClient给session
  7. 然后按照NotifyClient(带有MessagePosted)中的client actorref将MessagePosted转发给特定的client
  8. client收到MessagePosted之后完成并推出

整体的思路

  1. ChatRoom Actor:作为中央枢纽,负责管理所有的会话(Sessions)。每个连接到聊天室的客户端都会通过 GetSession 消息与 ChatRoom 交互,并获得一个专属的会话 Actor。
  2. Session Actor:每个客户端都有一个对应的 Session Actor,用于处理该客户端的消息收发、保持客户端状态等。
  3. client(Gabbler)Actor:模拟客户端行为,可以发送消息给 ChatRoom 或者其他客户端。
  4. 客户端间通信:通过 ChatRoom 转发消息来实现客户端间的通信。当一个客户端发送消息时,它实际上是将消息发送给了 ChatRoom,然后由 ChatRoom 将消息广播给所有其他在线的客户端。

代码示例

定义消息,这里实际上等同于定义actor之间的通信协议

  • RoomCommand,用来获取session
  • SessionEvent,用来管理session和发送message
  • SessionCommand,用来发送message和通知client
object ChatRoom {
  sealed trait RoomCommand
  final case class GetSession(screenName: String, replyTo: ActorRef[SessionEvent]) extends RoomCommand

  sealed trait SessionEvent
  final case class SessionGranted(handle: ActorRef[PostMessage]) extends SessionEvent
  final case class SessionDenied(reason: String) extends SessionEvent
  final case class MessagePosted(screenName: String, message: String) extends SessionEvent

  sealed trait SessionCommand
  final case class PostMessage(message: String) extends SessionCommand
  private final case class NotifyClient(message: MessagePosted) extends SessionCommand
}

ChatRoom actor部分

object ChatRoom {
    
  // PublishSessionMessage消息将包含的ChatRoom消息传播到所有连接的客户端
  private final case class PublishSessionMessage(screenName: String, message: String) extends RoomCommand

  def 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 client
        case 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中的所有client
          Behaviors.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发布消息通知client
      case NotifyClient(message) =>
        client ! message
        Behaviors.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.stopped
        case SessionGranted(handle) =>
          handle ! PostMessage("Hello World!")
          Behaviors.same
        case MessagePosted(screenName, message) =>
          context.log.info("message has been posted by '{}': {}", screenName, message)
          Behaviors.stopped
      }
    }

actorsystem入口

  • 这里使用了Behaviors.setup。Behaviors.setupBehaviors.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,并且发送消息收到响应后推出

在这里插入图片描述

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

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

相关文章

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 路由导航</…

@Builder的用法

Builder 是 Lombok 提供的一个注解&#xff0c;用于简化 Java 中构建对象的方式&#xff08;Builder 模式&#xff09;。它可以让你以更加简洁、链式的方式来创建对象&#xff0c;尤其适用于构造参数较多或部分可选的类。

vue实现点击按钮input保持聚焦状态

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

[蓝桥杯]取球博弈

取球博弈 题目描述 两个人玩取球的游戏。 一共有 NN 个球&#xff0c;每人轮流取球&#xff0c;每次可取集合 n1,n2,n3n1​,n2​,n3​中的任何一个数目。 如果无法继续取球&#xff0c;则游戏结束。 此时&#xff0c;持有奇数个球的一方获胜。 如果两人都是奇数&#xff…

[Java 基础]数组

什么是数组&#xff1f;想象一下&#xff0c;你需要存储 5 个学生的考试成绩。你可以声明 5 个不同的 int 变量&#xff0c;但这会显得很笨拙。数组提供了一种更简洁、更有组织的方式来存储和管理这些数据。 数组可以看作是相同类型元素的集合&#xff0c;这些元素在内存中是连…

‘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 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 原来是安装的全局路径被我改了 npm list -g --depth 0 把上述…

Android Test2 获取系统android id

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

webpack打包学习

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

基于Java(Jsp+servelet+Javabean)+MySQL实现图书管理系统

图书管理系统 一、需求分析 1.1 功能描述 1.1.1“读者”功能 1&#xff09;图书的查询&#xff1a;图书的查询可以通过搜索图书 id、书名、作者名、出版社来实现,显示结果中需要包括书籍信息以及是否被借阅的情况&#xff1b; 2&#xff09;图书的借阅&#xff1a;借阅图书…

服务器CPU被WMI Provider Host系统进程占用过高,导致系统偶尔卡顿的排查处理方案

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

JavaSwing之--JMenuBar

Java Swing之–JMenuBar(菜单栏) JMenuBar是 Java Swing 库中的一个组件&#xff0c;用于创建菜单栏&#xff0c;通常位于窗口的顶部。它是菜单系统的容器&#xff0c;用于组织和显示应用程序的菜单结构 菜单栏由菜单构成&#xff0c;菜单由菜单项或子菜单构成&#xff0c;也…

【物联网-S7Comm协议】

物联网-S7Comm协议 ■ 调试工具■ S7协议-简介■ S7协议和modbusTCP协议区别■ OSI 层 S7 协议■ S7协议数据结构 &#xff08;TPKTCOTPS7Comm&#xff09;■ TPKT&#xff08;第五层&#xff1a;会话层&#xff09; 总共占4个字节■ COTP&#xff08;第六层&#xff1a;表示层…

数据分析后台设计指南:实战案例解析与5大设计要点总结

引言 数据于企业而言异常重要&#xff0c;企业通过数据可以优化战略决策&#xff0c;因此企业对数据的采集正趋向智能化、数字化&#xff0c;数据分析后台就是企业智能化、数字化记录、分析数据的渠道。本文分享一个数据分析后台原型实战案例&#xff0c;通过页面拆解总结原型…

网络测试实战:金融数据传输的生死时速

阅读原文 7.4 网络测试实战--数据传输&#xff1a;当毫秒决定百万盈亏 你的交易指令为何总是慢人一步&#xff1f; 在2020年"原油宝"事件中&#xff0c;中行原油宝产品因为数据传输延迟导致客户未能及时平仓&#xff0c;最终亏损超过90亿元。这个血淋淋的案例揭示了…

数据库系统概论(十四)详细讲解SQL中空值的处理

数据库系统概论&#xff08;十四&#xff09;详细讲解SQL中空值的处理 前言一、什么是空值&#xff1f;二、空值是怎么产生的&#xff1f;1. 插入数据时主动留空2. 更新数据时设置为空3. 外连接查询时自然出现 三、如何判断空值&#xff1f;例子&#xff1a;查“漏填数据的学生…

【信创-k8s】海光/兆芯+银河麒麟V10离线部署k8s1.31.8+kubesphere4.1.3

❝ KubeSphere V4已经开源半年多&#xff0c;而且v4.1.3也已经出来了&#xff0c;修复了众多bug。介于V4优秀的LuBan架构&#xff0c;核心组件非常少&#xff0c;资源占用也显著降低&#xff0c;同时带来众多功能和便利性。我们决定与时俱进&#xff0c;使用1.30版本的Kubernet…

一台电脑联网如何共享另一台电脑?网线方式

前言 公司内网一个人只能申请一个账号和一个主机设备&#xff1b;会检测MAC地址&#xff1b;如果有两台设备&#xff0c;另一台就没有网&#xff1b;因为是联想老电脑&#xff0c;共享热点用不了&#xff0c;但是有一根网线&#xff0c;现在解决网线方式共享网络&#xff1b; …

MacroDroid安卓版:自动化操作,让生活更智能

在智能手机的日常使用中&#xff0c;我们常常会遇到一些重复性的任务&#xff0c;如定时开启或关闭Wi-Fi、自动回复消息、根据位置调整音量等。这些任务虽然简单&#xff0c;但频繁操作会让人感到繁琐。MacroDroid安卓版正是为了解决这些问题而设计的&#xff0c;它是一款功能强…

力提示(force prompting)的新方法

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…