22.3. Aktorzy — przykład 2
W pliku Guess2.scala znajduje się inna implementacja przykładu. Poniższy listing przedstawia jego pierwszą część.
Plik Guess2.scala: import akka.actor._ object Guess2 extends App { println("Guess2: begin") val system = ActorSystem("Guess2") val server = system.actorOf(Props[GuessServer2],"GuessServer2")
val client = system.actorOf(Props[GuessClient2])
import scala.concurrent.duration._
implicit val timeout = akka.util.Timeout(5.seconds)
import system.dispatcher
import akka.pattern.ask
val responseFuture = client ? 'start
responseFuture.onSuccess { case response =>
println("Guess2: received response: "+response) client ! 'stop } println("Guess2: end")
}
Implementacja obiektu Guess2 różni się od implementacji obiektu
Guess1 przedstawionego w poprzednim podrozdziale. W wierszu
tworzymy instancję servera nadając mu nazwę „GuessServer2”. Nazwę tego
aktora wykorzystamy w kodzie klienta, aby uzyskać adres serwera. W
wierszu
tworzymy klienta. W tym przykładzie klasa klienta nie ma
parametru. W wierszu
przesyłamy do klienta komunikat 'start,
jednak tym razem wykorzystujemy do tego metodę ?. Metoda ?
przesyła do obiorcy komunikat oraz zwraca rezultat reprezentujący
odpowiedź, jaką ten odbiorca powinien zwrócić nadawcy. Metoda ?,
podobnie jak !, działa asynchronicznie. Nie blokuje działania wątku
do momentu otrzymania odpowiedzi — zamiast tego zwraca rezultat w
postaci instancji klasy Future. Ta instancja reprezentuje wartość,
która zostanie obliczona (w naszym przypadku ta wartość nadejdzie jako
odpowiedź od klienta) w przyszłości. Aby możliwe było użycie metody
?, w poprzedzających ją wierszach dokonywane są pewne
przygotowania. W wierszu
tworzona jest niejawnie przekazywana
wartość typu Timeout, określająca maksymalny czas oczekiwania na
odpowiedź. Wykorzystanie wyrażenia 5.seconds, określającego czas
oczekiwania jako 5 sekund, jest możliwe dzięki instrukcji import z
wiersza
. W wierszach
i
znajdują się kolejne instrukcje importu
potrzebne, aby możliwe było użycie metody ?. Wywołana w wierszu
metoda onSuccess otrzymuje jako argument funkcję częściową
określającą, jakie działania mają zostać wykonane, kiedy wartość
responseFuture zostanie obliczona (czy raczej otrzymana w tym
przypadku). Działania te polegają na wypisaniu na ekranie komunikatu
oraz wysłaniu klientowi komunikatu 'stop. W wierszu
na ekranie
wypisywany jest komunikat. Jak zobaczymy dalej, ten komunikat zostanie
wyświetlony w początkowym okresie działania programu.
Klient jest implementowany przez klasę GuessClient2, której definicja jest pokazana poniżej.
Plik Guess2.scala: class GuessClient2 extends Actor { println("GuessClient2: starting") private[this] var n: Int = 5 val server = context.actorSelection("../GuessServer2")
def receive = { case 'start =>
val guessSender = sender
send(n)
context.become {
case 'correct =>
println(s"GuessClient2: solution found: $n") context.unbecome()
guessSender ! n
case 'tooSmall =>
println("GuessClient2: too small") n = n + 1 send(n) case 'tooBig =>
println("GuessClient2: too big") n = n - 1 send(n) } case 'stop =>
println("GuessClient2: stopping") context.system.shutdown() } def send(msg: Int) { println(s"GuessClient2: sending $msg") server ! msg } }
Klasa klienta nie ma parametrów, a adres serwera uzyskuje za pomocą
metody actorSelection z wiersza . Metodzie tej przekazujemy łańcuch
znakowy określający ścieżkę szukanego aktora. W naszym przypadku
ścieżka zawiera nazwę aktora przypisaną mu wcześniej w wierszu
,
poprzedzoną łańcuchem „../”, który wskazuje, że jest to scieżka
względna, a szukany aktor znajduje się bezpośrednio „pod” rodzicem
bieżącego aktora. Metoda receive definiuje obsługę komunikatów
'start (wiersz
) oraz 'stop (wiersz
). Po otrzymaniu komunikatu
'start, w wierszu
zapamiętywana jest referencja do nadawcy tego
komunikatu. Następnie wysyłamy do serwera pierwszy komunikat (wiersz
). W wierszu
wywoływana jest metoda become, której przekazujemy
funkcję częściową implementującą obsługę otrzymywanych
komunikatów. Wywołanie tej metody powoduje zastąpienie bieżącej
procedury obsługi komunikatów (która jest zdefiniowane przez metodę
receive i zawiera obsługę komunikatów 'start i 'stop) nową
procedurą obsługi. Ta nowa procedura obsługuje komunikaty 'correct
(wiersz
), 'tooSmall (wiersz
) i 'tooBig (wiersz
). Procedura
obsługi komunikatu 'correct zawiera wywołanie metody unbecome
(wiersz
), która przywraca poprzednią procedurę obsługi, a więc po
jej wykonaniu aktor znowu obsługuje komunikaty 'start i 'stop,
natomiast nie obsługuje już komunikatów 'correct, 'tooSmall oraz
'tooBig. W wierszu
przesyłamy odpowiedź do nadawcy komunikatu
'start, co w rezultacie powoduje wywołanie funkcji przekazanej
metodzie onSuccess w wierszu
.
Klasa GuessServer2 jest implementacją aktora grającego rolę serwera.
Plik Guess2.scala: class GuessServer2 extends Actor { val n = scala.util.Random.nextInt(10) println("GuessServer2: starting") def receive = { case x:Int if x > n => send(sender, 'tooBig) case x:Int if x < n => send(sender, 'tooSmall) case x:Int => send(sender, 'correct) } def send(sender: ActorRef, msg: Symbol) { println(s"GuessServer2: sending $msg") sender ! msg } }
Poniżej przedstawiony jest przykładowy rezultat wykonania programu.
$ scala Guess2 Guess2: begin GuessClient2: starting GuessServer2: starting Guess2: end GuessClient2: sending 5 GuessServer2: sending 'tooBig GuessClient2: too big GuessClient2: sending 4 GuessServer2: sending 'tooBig GuessClient2: too big GuessClient2: sending 3 GuessServer2: sending 'tooBig GuessClient2: too big GuessClient2: sending 2 GuessServer2: sending 'correct GuessClient2: solution found: 2 Guess2: received response: 2 GuessClient2: stopping