Akka网络编程案例

Akka作为spark通信的基础,了解Akka的网络编程有助于了解spark是如何通信的。
Akka基础请点击链接

需求介绍

咨询问题
咨询问题
回复消息
回复消息
客户端1Customer
服务端Server
客户端2Customer
  • 服务端监听9999端口
  • 客户端通过键盘输入向服务端发送咨询问题给服务端
  • 服务端回答客户的问题并显示出来

功能实现

添加Maven依赖

 <!-- 定义一下常量 -->
    <properties>
        <encoding>UTF-8</encoding>
        <scala.version>2.11.8</scala.version>
        <scala.compat.version>2.11</scala.compat.version>
        <akka.version>2.4.17</akka.version>
    </properties>

    <dependencies>
        <!-- 添加scala的依赖 -->
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>

        <!-- 添加akka的actor依赖 -->
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-actor_${scala.compat.version}</artifactId>
            <version>${akka.version}</version>
        </dependency>

        <!-- 多进程之间的Actor通信 -->
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-remote_${scala.compat.version}</artifactId>
            <version>${akka.version}</version>
        </dependency>
    </dependencies>

客户端代码

import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import akka.common.{ClientMessage, ServerMessage}
import com.typesafe.config.ConfigFactory
import scala.io.StdIn

class CustomerActor(serverHost: String, serverPort: Int) extends Actor {
  //定义一个ServerRef
  var serverActorRef: ActorSelection = _

  //在Actor中有一个方法PreStart方法,他会在actor运行前执行
  //在akka的开发中,通常将初始化的工作,放在preStart方法
  override def preStart(): Unit = {
    println("preStart() 执行")
    //获取到serverActorRef用来通信,Server_Actor_Ref 这个名称要和server注册的时候的server 的ActorRef一样,要不然没办法通信
    serverActorRef = context.actorSelection(s"akka.tcp://Server@${serverHost}:${serverPort}/user/Server_Actor_Ref")
    println("serverActorRef=" + serverActorRef)
  }

  override def receive: Receive = {
    case "start" => println("start,客户端运行,可以咨询问题")
    case mes: String => {
      //发给客服
      serverActorRef ! ClientMessage(mes) //使用ClientMessage case class apply
    }
    //如果接收到服务器的回复
    case ServerMessage(mes) => {
      println(s"收到客服(Server): $mes")
    }

  }
}

//主程序-入口
object CustomerActor extends App {
  val (clientHost, clientPort, serverHost, serverPort) = ("127.0.0.1", 9990, "127.0.0.1", 9999)
  val config = ConfigFactory.parseString(
    s"""
       |akka.actor.provider="akka.remote.RemoteActorRefProvider"
       |akka.remote.netty.tcp.hostname=$clientHost
       |akka.remote.netty.tcp.port=$clientPort
        """.stripMargin)

  //创建ActorSystem
  val customerActorSystem = ActorSystem("client", config)

  //创建CustomerActor的实例和引用
  val customerActorRef: ActorRef = customerActorSystem.actorOf(Props(new CustomerActor(serverHost, serverPort)), "CustomerActor")

  //启动customerRef/也可以理解启动Actor
  customerActorRef ! "start"

  //客户端可以发送消息给服务器
  while (true) {
    println("请输入要咨询的问题")
    val mes = StdIn.readLine()
    customerActorRef ! mes
  }
}
  • 说明
  1. 在Actor中有一个方法PreStart方法,他会在actor运行前执行,一般用PreStart做初始化的动作。例如代码中初始化现获取到serverActorRef,以方便和客户端和服务端进行通信。获取到serverActorRef用来通信时,Server_Actor_Ref 这个名称要和server注册的时候的server 的ActorRef一样,要不然找不到Server的ActorRef。
  2. 客户端和服务端之间相互发送数据的方式是通过样例类来传递。例如:serverActorRef ! ClientMessage(mes) ,ClientMessage和ServerMessage就是一个样例类。

服务端代码

import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import akka.common.{ClientMessage, ServerMessage}
import com.typesafe.config.ConfigFactory

class Server extends Actor{
  override def receive:Receive = {
    case "start" => println("客服开始工作了....")
      //如果接收到ClientMessage
    case ClientMessage(mes) => {
      //使用match --case 匹配(模糊)
      mes match {
         //sender()方法能够获取到发送消息的CustomerActor的ActorRef然后才可以发送消息。
        case "你是谁?" => sender() ! ServerMessage("你猜我是谁呀,猜对了我就告诉你哦")
        case "你是小猪" => sender() ! ServerMessage("恭喜你猜对了,你真聪明")
        case "你是小狗" => sender() ! ServerMessage("我是猪")
        case _ => sender() ! ServerMessage("你说的啥子~")
      }
    }
  }
}
//主程序-入口
object Server extends App {
  val host = "127.0.0.1" //服务端ip地址
  val port = 9999
  //创建config对象,指定协议类型,监听的ip和端口
  val config = ConfigFactory.parseString(
    s"""
       |akka.actor.provider="akka.remote.RemoteActorRefProvider"
       |akka.remote.netty.tcp.hostname=$host
       |akka.remote.netty.tcp.port=$port
        """.stripMargin)

  //创建ActorSystem
  //url (统一资源定位)
  val serverActorSystem = ActorSystem("Server",config)
  //Server 的actor和返回actorRef
  val ServerRef: ActorRef = serverActorSystem.actorOf(Props[Server],"Server_Actor_Ref")

  //启动
  ServerRef ! "start"
}
  • 说明
  1. 客户端获取到的ActorRef的名称要和服务端的ActorRef一致,要不然找不到服务端。例如服务端的ServerRef的名称为Server_Actor_Ref,那么客户端通过context.actorSelection()方法选择的时候名称要对应上去。
  2. sender()方法能够获取到发送消息的Actor的ActorRef,也即获取到客户端的ActorRef,然后才可以回复客户端的消息。
  3. ClientMessage客户端发给服务器协议(序列化的对象)和ServerMessage服务端发给客户端的协议(样例类对象)都是样例类。

样例类

//使用样例类来构建协议
//客户端发给服务器协议(序列化的对象)
case class ClientMessage(mes: String)

//服务端发给客户端的协议(样例类对象)
case class ServerMessage(mes: String)

执行结果

在这里插入图片描述
上面的例子介绍了Akka网络编程的案例,看懂的话就很容易理解Spark的Master和Worker之间是如何通讯的了。想要了解Spark Master和Workert通讯机制,请点击链接: Akka模拟Spark Master和Workert通讯机制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值