Awesome
ChatBox
Simple Chat System Using Akka Actors (Akka Remoting)
ChatBox Actor
Manages the Client Actors and Client Actors register with the ChatBox Actor. The IP of the ChatBox actor is available to Client Actors. ChatBox actor watches each Client actor that registers itself with it and removes them from the list of users once Client actors are unreachable.
Conf for ChatBoxActor
application.conf
akka {
loglevel = "INFO"
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 2222
}
log-sent-messages = on
log-received-messages = on
}
}
ChatBox.scala
package actors
import akka.actor.{Terminated, ActorRef, ActorLogging, Actor}
/**
* Created by android on 21/3/15.
*/
object ChatBox {
case class Register(name: String)
case class Message(from: String, to: String, body: String)
}
class ChatBox extends Actor with ActorLogging {
var clients = Map.empty[String, ActorRef]
import ChatBox._
import Client._
override def receive = {
case Register(name) => {
if (! (clients contains name) ) {
clients += (name -> sender)
context watch sender
log.info("Clients")
log.info(clients.mkString("\n"))
}
}
case Message(from, to, body) =>
log.info(s"from: $from says: $body to: $to")
if(clients contains to) {
clients(to) ! ReceiveMessage(from, body)
}else {
log.info("message dropped")
}
case Terminated(actor) => {
clients = clients.filter(_._2 != actor)
}
case _ => log.info("unknown message")
}
}
Client Actor
Each Client Actor represents the User and registers with the ChatBox Actor initially. Registration
happens by first looking up the actor using context.actorSelection("akka.tcp://ChatSystem@someip:port/user/ChatBox")
and then the Register message is sent by the client to the ChatBox actor thus registering with the chatbox actor.
Conf for ClientActor
client.conf
akka {
loglevel = "INFO"
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 0
}
log-sent-messages = on
log-received-messages = on
}
}
Client.scala
package actors
import akka.actor.{ActorLogging, ActorSelection, Actor}
/**
* Created by android on 21/3/15.
*/
object Client {
case class SendMessage(to: String, message: String)
case class ReceiveMessage(from: String, message: String)
}
class Client(name: String, ip: String) extends Actor with ActorLogging {
import ChatBox._
import Client._
var chatBox: Option[ActorSelection] = None
override def preStart(): Unit = {
chatBox = Some(context.actorSelection("akka.tcp://ChatSystem@$ip:2222/" +
"user/ChatBox")) // node that chat box actor lookup is done using ChatBoxActor Running machine IP.
//localhost if both ChatBoxActor and Client Actor are running on same machine.
chatBox.map(actor => actor ! Register(name))
chatBox.getOrElse({
println("ChatBox unreachable, shutting down :(")
context.stop(self)
})
}
override def receive = {
case SendMessage(to, message) => chatBox.map(actor => actor ! Message(name, to, message))
case ReceiveMessage(from, message) =>
println(s"$from says: $message")
case _ => log.info("unknown message")
}
}
Main
Start ChatBox first on a Machine and use the IP of the ChatBox to lookup ChatBox from Client
StartChatBox
package main
import actors.ChatBox
import akka.actor.{Props, ActorSystem}
import com.typesafe.config.ConfigFactory
/**
* Created by android on 21/3/15.
*/
object StartChatBox {
def main(args: Array[String]): Unit = {
val config = ConfigFactory.load()
val chatSystem = ActorSystem("ChatSystem", config)
val chatBox = chatSystem.actorOf(Props[ChatBox], name = "ChatBox")
}
}
StartClient
package main
import actors.Client
import akka.actor.{Props, ActorSystem}
import com.typesafe.config.ConfigFactory
/**
* Created by android on 21/3/15.
*/
object StartClient {
def main(args: Array[String]): Unit = {
if (args.isEmpty) {
println("Please provide IP of ChatBox Actor as the commandline argument")
System.exit(0)
}
val config = ConfigFactory.load("client")
val clientSystem = ActorSystem("ClientSystem", config)
println("Enter your Nick Name:")
var name = Console.readLine.trim
while (name == "") {
println("Enter your Nick Name:")
name = Console.readLine.trim
}
val client = clientSystem.actorOf(Props(new Client(name)), "Client")
println("Type message end with -> after -> type name of the person to send " +
"and hit enter to send messages")
while (true) {
val line = Console.readLine.trim
if (line != "" && line.contains("->") && line.split("->").size == 2) {
val texts = line.split("->")
client ! Client.SendMessage(texts(1).trim, texts(0).trim)
}
}
}
}
Usage
Start ChatBox Actor
sbt "runMain main.StartChatBox" //note the IP of the machine
now Start Client Actor
sbt "runMain main.StartClient 127.0.0.1" // IP of the machine running chatbox actor