Dean Wampler / Alex Payne, Programmieren mit Scala, O ... · rufezeichen,auch Bang genannt),mit der...

21
Dean Wampler / Alex Payne, Programmieren mit Scala, O´Reilly, ISBN 9783897216471 D3kjd3Di38lk323nnm

Transcript of Dean Wampler / Alex Payne, Programmieren mit Scala, O ... · rufezeichen,auch Bang genannt),mit der...

Dean Wampler / Alex Payne, Programmieren mit Scala, O´Reilly, ISBN 9783897216471

D3kjd3Di38lk323nnm

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

KAPITEL 9

Robuste, skalierbare Nebenläufigkeitmit Aktoren

Das Problem des gemeinsamen synchronisierten ZustandsMit Nebenläufigkeit umzugehen, ist nicht einfach. Ein Programm zu schreiben, das mehrereDinge gleichzeitig tut, hat traditionell immer bedeutet, sich mit Mutexen, Wettlaufsituatio-nen, Lock-Konflikten und dem ganzen übrigen Gepäck auseinanderzusetzen, das mit demMultithreading einhergeht. Ereignisbasierte Modelle für Nebenläufigkeit können dieseSchwierigkeiten abmildern, können aber größere Anwendungen leicht in ein einzigesGestrüpp von Callback-Funktionen verwandeln. Es ist daher kein Wunder, dass die meistenProgrammierer nebenläufige Programmierung fürchten oder sogar komplett zu vemeidensuchen, indem sie auf mehrere voneinander unabhängige Prozesse zurückgreifen, die ihreDaten extern teilen (beispielsweise mithilfe einer Datenbank oder eine Message Queue).

Ein großer Teil der Schwierigkeiten mit der Nebenläufigkeit lässt sich auf wenige Fragenzurückführen: Woher wissen Sie, was Ihr multithreaded Programm tut und wann es dastut? Welchen Wert enthält eine Variable, wenn zwei Threads zugleich auf sie zugreifen,oder fünf, oder 50? Wie können Sie garantieren, dass sich die zahlreichen Tentakeln IhresProgramms nicht im Wettlauf um die nächste Aktivität ineinander verheddern? Dasthreadbasierte Paradigma für die Nebenläufigkeit wirft hier mehr Fragen auf, als esbeantwortet.

Zum Glück bietet Scala einen vernünftigen und flexiblen Ansatz für nebenläufige Pro-gramme, und den wollen wir uns in diesem Kapitel ansehen.

AktorenObwohl Scala und Aktoren häufig in einem Atemzug genannt werden, stellen Aktoren1

kein Scala-spezifisches Konzept dar. Ursprünglich waren sie für die Verwendung in der

1 Die englische Bezeichnung actor für nebenläufige Objekte bedeutet zugleich »Schauspieler«. Das ist eine beliebteQuelle für Wortspiele, wie Sie auch an den Beispielen dieses Kapitels erkennen können; Anm. d. Übers.

| 211

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

Intelligenzforschung vorgesehen und wurden erstmalig im Jahr 1973 zum Einsatz ge-bracht (vgl. [Hewitt1973] und [Agha1987]). Seitdem sind in einigen Programmierspra-chen Varianten der Aktor-Idee aufgetaucht, allen voran in Erlang und Io. Als Abstraktionsind Aktoren allgemeingültig genug, um als Bibliothek (wie in Scala) oder als fundamen-taler Teil eines Anwendungssystems implementiert werden zu können.

Im Prinzip ist ein Aktor ein Objekt, das Nachrichten erhält und aufgrund dieser Nach-richten Aktionen ausführt. Die Reihenfolge, in der die Nachrichten eintreffen, ist für einenAktor unwichtig, obwohl manche Aktor-Implementierungen (wie die von Scala) dieNachrichungen der Reihe nach übertragen. Ein Aktor kann eine Nachricht intern ab-arbeiten, er kann selbst eine Nachricht an einen anderen Aktor senden oder er kann einenanderen Aktor erzeugen, der auf diese Nachricht hin bestimmte Aktivitäten ausführt.Aktoren stellen eine Abstraktion auf sehr hohem Niveau dar.

Anders als traditionelle Objektsysteme (die, wie Sie jetzt vielleicht so bei sich denken,ebenfalls viele der beschriebenen Eigenschaften aufweisen) erfordern Aktoren keinebestimmte Sequenz oder Reihenfolge bei ihren Aktionen. Diese inhärente Scheu vorSequenzialität, verbunden mit Unabhängigkeit von gemeinsamen globalen Zuständen,ermöglicht es Aktoren, ihre Arbeit parallel zu erledigen. Wie wir später noch sehenwerden, passt eine vernünftige Verwendung von immutablen Daten ideal zum Aktor-Modell und verhilft weiterhin zu einer Art der nebenläufigen Programmierung, die sicherund nachvollziehbar ist.

Genug der Theorie. Wir wollen uns jetzt Aktoren im Einsatz ansehen.

Aktoren in ScalaIm Grunde sind Aktoren in Scala nichts weiter als Objekte, die von scala.actors.Actorabgeleitet sind:

// code-examples/Concurrency/simple-actor-script.scala

import scala.actors.Actor

class Redford extends Actor {def act() {

println("Viel von dem, was Schauspielerei ausmacht, ist aufmerksam zu sein.")}

}

val robert = new Redfordrobert.start

Wie Sie sehen, muss ein so definierter Aktor sowohl instanziiert als auch gestartet werden.Das funktioniert ähnlich wie der Umgang mit Threads in Java. Der Aktor muss die abstrakteMethode act implementieren, die Unit zurückgibt. Nachdem wir diesen einfachen Aktorgestartet haben, erscheint der folgende Ratschlag für angehende Mimen auf der Konsole:

Viel von dem, was Schauspielerei ausmacht, ist aufmerksam zu sein.

212 | Kapitel 9: Robuste, skalierbare Nebenläufigkeit mit Aktoren

Dean Wampler / Alex Payne, Programmieren mit Scala, O´Reilly, ISBN 9783897216471

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

Das Package scala.actors enthält eine Factory-Methode zum Erzeugen von Aktoren, dieeinen Großteil der Vorbereitungen im obigen Beispiel vermeiden hilft. Diese Methode undweitere Hilfsmethoden können wir aus scala.actors.Actors._ importieren. Hier ist einAktor, der durch die Factory erzeugt wird:

// code-examples/Concurrency/factory-actor-script.scala

import scala.actors.Actorimport scala.actors.Actor._

val paulNewman = actor {println("Um ein Schauspieler zu sein, muss man Kind sein.")

}

Während eine Subklasse, die die Klasse Actor erweitert, die Methode act sein muss, umkonkret sein zu können, gibt es für Factory-generierte Aktoren keine solche Einschrän-kung. In diesem kürzeren Beispiel wird der Rumpf der an actor übergebenen Methode imEndeffekt an act unseres ersten Beispiels weitergeleitet. Erwartungsgemäß gibt dieserAktor ebenfalls eine Meldung aus, wenn er gestartet wird. Das ist schon einmal erhellend,aber das entscheidende Teil im Aktor-Puzzle haben Sie noch nicht gesehen: den Nach-richtenaustausch.

Nachrichten an Aktoren sendenEin Aktor kann jedes beliebige Objekt als Nachricht empfangen, von Textstrings übernumerische Typen bis hin zu allem, was Sie in Ihren Programmen ersonnen haben. Ausdiesem Grund gehen Aktoren und Pattern-Matching Hand in Hand. Ein Aktor sollte nurdann tätig werden, wenn er eine Nachricht von einem bekannten Typ erhält; daher ist dasPattern-Matching über den Typ und/oder den Inhalt der Nachricht eine gute, defensiveProgrammiertechnik und erhöht außerdem die Lesbarkeit des Aktor-Codes:

// code-examples/Concurrency/pattern-match-actor-script.scala

import scala.actors.Actorimport scala.actors.Actor._

val fussyActor = actor {loop {

receive {case s: String => println("String erhalten: " + s)case i: Int => println("Int erhalten: " + i.toString)case _ => println("Keine Ahnung, was ich da erhalten habe.")

}}

}

fussyActor ! "Hallo"fussyActor ! 23fussyActor ! 3.33

Aktoren in Scala | 213

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

Dieses Beispiel gibt Folgendes aus, wenn Sie es laufen lassen:

String erhalten: HalloInt erhalten: 23Keine Ahnung, was ich da erhalten habe.

Der Rumpf der Methode fussyActor ist eine receive-Methode, die in einem loop verpacktist. Die Methode loop ist im Wesentlichen eine nette Abkürzung für while(true); sie führtalles in ihrem Block immer wieder aus. receive blockiert, bis eine Nachricht eines Typseintrifft, der durch einen seiner internen Pattern-Matching-Fälle abgedeckt ist.

Die letzten Zeilen dieses Beispiels demonstrieren die Verwendung der Methode ! (Aus-rufezeichen, auch Bang genannt), mit der man unserem Aktor eine Nachricht übermittelnkann. Wenn Sie einmal Aktoren in Erlang gesehen haben, wird Ihnen diese Syntaxbekannt vorkommen. Der Aktor steht immer auf der linken Seite des Ausrufezeichens,und die an den Aktor zu sendende Nachricht auf der rechten. Wenn Sie eine Eselsbrückefür dieses syntaktische Zuckerstückchen benötigen, stellen Sie sich vor, Sie wären einwütender Regisseur, der seinen Akteuren Befehle zuruft.

Aktoren im DetailNachdem wir nun ein grundlegendes Verständnis davon erlangt haben, was Aktoren sindund wie sie in Scala verwendet werden, wollen wir sie zum Arbeiten bringen. In diesemFall werden wir sie dazu bringen, Haare zu schneiden. Um Probleme der Nebenläufigkeitund der Synchronisation zu demonstrieren, eignet sich sehr gut ein Satz hypothetischerAnnahmen, die als das Schlafender-Friseur-Problem (vgl. [SleepingBarberProblem]) be-kannt sind. Und das wollen wir unserem Experiment zugrunde legen.

Das Problem ist Folgendes: In einem hypothetischen Friseurladen gibt es genau einenFriseur mit einem Friseurstuhl und drei Stühlen, auf denen Kunden auf einen Haarschnittwarten können. Wenn gerade keine Kunden da sind, schläft der Friseur. Sobald ein Kundeden Laden betritt, wacht der Friseur auf, um dessen Haare zu schneiden. Wenn derFriseur beim Eintreffen eines Kunden bereits mit Haareschneiden beschäftigt ist, setzt sichder Kunde in einen verfügbaren Stuhl. Wenn kein Stuhl verfügbar ist, verlässt der Kundeden Laden.

Üblicherweise wird dieses Problem mithilfe von Semaphoren und Mutexen gelöst, aberwir haben bessere Werkzeuge zur Verfügung. Wir erkennen sofort einige Dinge, die wirals Aktoren modellieren können, dazu gehören natürlich der Friseur und die Kunden.Auch der Friseurladen könnte als Aktor modelliert werden; auch wenn wir Nachrichtenversenden, muss es in einem Aktor-System nicht unbedingt eine Parallele zur mündlichenKommunikation in der Wirklichkeit geben.

214 | Kapitel 9: Robuste, skalierbare Nebenläufigkeit mit Aktoren

Dean Wampler / Alex Payne, Programmieren mit Scala, O´Reilly, ISBN 9783897216471

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

Wir beginnen mit den Kunden des Friseurs, denn diese haben die geringsten Verantwort-lichkeiten:

// code-examples/Concurrency/sleepingbarber/customer.scala

package sleepingbarber

import scala.actors.Actorimport scala.actors.Actor._

case object Haircut

class Customer(val id: Int) extends Actor {var shorn = false

def act() = {loop {react {

case Haircut => {shorn = trueprintln("[c] Kunde " + id + " ist fertig.")

}}

}}

}

Zum größten Teil sollte Ihnen das ziemlich bekannt vorkommen: Wir deklarieren einPackage für dieses Programm, importieren Code aus dem Package scala.actors unddefinieren eine Klasse, die es Actor erweitert. Es gibt aber ein paar Einzelheiten, auf diesich hinzuweisen lohnt.

Als Erstes ist da unsere Deklaration des Case-Objekts Haircut. Es ist ein übliches Muster,ein case object zur Repräsentation einer Nachricht ohne interne Daten zu verwenden.Wenn wir beispielsweise die Zeit der Vollendung des Haarschnitts in die Nachrichtaufnehmen wollten, würden wir stattdessen eine case class verwenden. Wir deklarierenHaircut an dieser Stelle, weil es ein Nachrichtentyp ist, der ausschließlich an Kundengesannt wird.

Beachten Sie auch, dass wir in jedem Customer ein Bit veränderbaren Zustand speichern:Es enthält die Information, ob er seinen Haarschnitt bekommen hat oder nicht. In seinerinternen Schleife wartet jeder Customer auf eine Haircut-Nachricht, und wenn er eineerhalten hat, setzen wir die boolesche Variable shorn (geschoren) auf true. Customerverwendet die asynchrone Methode react, um auf eingehende Nachrichten zu reagieren.Wenn wir das Ergebnis der Verarbeitung zurückgeben müssten, würden wir receiveverwenden; da das hier aber nicht der Fall ist, können wir auf diese Weise einiges anSpeicher- und Threadbedarf hinter den Kulissen einsparen.

Nun wollen wir uns den Friseur selbst vornehmen. Da es nur einen einzigen Friseur gibt,könnten wir die oben erwähnte Technik mit der Factory-Methode actor anwenden, umihn zu erzeugen. Um die Zusammenhänge klarer zu machen, definieren wir aber unsereeigene Klasse Barber:

Aktoren in Scala | 215

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

// code-examples/Concurrency/sleepingbarber/barber.scala

package sleepingbarber

import scala.actors.Actorimport scala.actors.Actor._import scala.util.Random

case object BeginHaircut

class Barber (val shop:Shop) extends Actor {private val random = new Random()

def cutHair(customer: Customer) {shop ! BeginHaircutprintln("[b] Beginn Haarschnitt fur Kunde " + customer.id)Thread.sleep(100 + random.nextInt(400))customer ! Haircut

}

def act() {loop {react {

case customer: Customer => cutHair(customer)}

}}

}

Die Klasse Barber sieht im Kern der Klasse Customer sehr ähnlich. Auch hier definieren wiram Anfang ein einfaches object namens BeginHaircut, mit dem wir signalisieren wollen,dass der Friseur mit einem Haarschnitt begonnen hat und dadurch ein Wartestuhl freiwird.

Im Rumpf von Barber laufen wir in einer Schleife um react herum und warten auf einenbestimmten Objekttyp. Um diese Schleife kurz und lesbar zu halten, rufen wir, wenn einneuer Customer an den Friseur gesandt worden ist, die Methode cutHair auf. Innerhalbdieser Methode senden wir die Nachricht BeginHaircut an den Shop-Aktor, simulierenanschließend durch eine semizufällige Verzögerung (immer mindestens 100 Millisekun-den) die für den Haarschnitt benötigte Zeit und schicken schließlich eine Haircut-Nach-richt an den aktuellen Kunden. (Würden wir nicht versuchen, ein Szenario aus derWirklichkeit zu simulieren, würden wir natürlich den Aufruf von Thread.sleep() ent-fernen und unserem Friseur erlauben, mit voller Kraft zu arbeiten.)

Als Nächstes haben wir noch eine Klasse, die den Friseursalon selbst repräsentiert:

// code-examples/Concurrency/sleepingbarber/shop.scala

package sleepingbarber

import scala.actors.Actorimport scala.actors.Actor._

216 | Kapitel 9: Robuste, skalierbare Nebenläufigkeit mit Aktoren

Dean Wampler / Alex Payne, Programmieren mit Scala, O´Reilly, ISBN 9783897216471

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

class Shop extends Actor {var customerCount = 0val barber = new Barber(this)barber.start

private def helpCustomer(customer: Customer) {if (customerCount >= 3) {println("[b] Zu wenig Stuhle; Kunde " + customer.id + " wird abgelehnt.")

} else {println("[b] Kunde " + customer.id + " wird angenommen.")barber ! customercustomerCount += 1

}}

def act() {println("[s] Der Salon ist geoffnet")

loop {react {

case customer: Customer => helpCustomer(customer)case BeginHaircut => if (customerCount > 0) customerCount -= 1

}}

}}

Jeder Shop erzeugt und startet einen neuen Barber. Außerdem initialisiert er eine VariablecustomerCount mit null, mit der die Anzahl der zur Zeit auf Stühlen sitzenden undwartenden Kunden simuliert wird. Nach dem Start teilt der Aktor mit einer Meldung derWelt mit, dass der Salon jetzt geöffnet ist, und wartet dann in einer Schleife auf Kunden.

Wenn ein Customer hereinkommt, wird zunächst geprüft, ob noch ein Stuhl frei ist; das istder Fall, wenn die Variable customerCount kleiner als 3 ist. In diesem Fall wird der Kundean den Friseur gesandt und die Zahl der wartenden Kunden wird um 1 erhöht. Andern-falls wird der Kunde abgelehnt, das heißt, er wird nicht weiter verarbeitet.

Der Shop-Aktor kann noch einen zweiten Nachrichtentyp verarbeiten, und zwar Begin-Haircut, die der Friseur immer dann sendet, wenn er bei einem Kunden mit demHaareschneiden beginnt. Das bedeutet, dass ein Wartestuhl frei wird und wir die VariablecustomerCount um 1 verringern können. (Zur Erhöhung der Robustheit des Programmsprüfen wir aber vorher, ob der Wert der Variablen wirklich größer als 0 ist.)

Dass wir die Anzahl der wartenden Kunden mitzählen müssen, ist eigentlich etwasüberflüssig, denn genau die entsprechende Anzahl von Customer-Objekten wartet ja inder Mailbox des Barber-Aktors. Leider ist aber seit der Scala-Version 2.8 die Actor-Me-thode mailboxSize, mit der man die aktuelle Länge der Mailbox-Warteschlange auslesenkann, als protected[scala.actors] definiert, daher müssen wir hierfür eine eigene Logikimplementieren.

Aktoren in Scala | 217

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

Als Letztes brauchen wir noch ein Treiberprogramm für unsere Simulation:

// code-examples/Concurrency/sleepingbarber/barbershop-simulator.scala

package sleepingbarber

import scala.actors.Actor._import scala.collection.{immutable, mutable}import scala.util.Random

object BarbershopSimulator {private val random = new Random()private val customers = new mutable.ArrayBuffer[Customer]()private val shop = new Shop()

def generateCustomers {for (i <- 1 to 20) {val customer = new Customer(i)customer.start()customers += customer

}

println("[!] " + customers.size + " Kunden generiert.")}

// Die Kunden treffen in zufalligen Abstanden ein.def trickleCustomers {

for (customer <- customers) {shop ! customerThread.sleep(random.nextInt(450))

}}

def tallyCuts {// Warte auf den Abschluss eventueller nebenlaufiger Aktionen.Thread.sleep(2000)

val shornCount = customers.filter(c => c.shorn).sizeprintln("[!] " + shornCount + " Kunden haben heute eine Haarschnitt bekommen.")

}

def main(args: Array[String]) {println("[!] Friseursalon-Simulation wird gestartet.")shop.start()

generateCustomerstrickleCustomerstallyCuts

System.exit(0)}

}

Nachdem wir den »Salon geöffnet« haben, generieren wir eine Anzahl von Customer-Ob-jekten, weisen ihnen jeweils eine numerische ID zu und speichen sie alle zusammen ineinem ArrayBuffer. Danach »tröpfeln« wir die Kunden in den Salon, indem wir sie als

218 | Kapitel 9: Robuste, skalierbare Nebenläufigkeit mit Aktoren

Dean Wampler / Alex Payne, Programmieren mit Scala, O´Reilly, ISBN 9783897216471

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

Nachrichten an den Shop-Actor senden und in jedem Schleifendurchlauf eine semizufälligeZeit lang ruhen. Am Ende unseres simulierten Tages zählen wir die Kunden, die einenHaarschnitt bekommen haben, indem wir alle herausfiltern, deren shorn-Variable auf truegesetzt ist, und nach der Größe der daraus resultierenden Sequenz fragen.

Kompilieren und starten Sie das Programm im Verzeichnis sleepingbarber folgendermaßen:

fsc *.scalascala -classpath . sleepingbarber.BarbershopSimulator

Im gesamten Code haben wir die Konsolenmeldungen mit Abkürzungen für die Klassenversehen, von denen die Meldungen jeweils ausgegeben werden. Wenn wir uns einenBeispiellauf unseres Simulators ansehen, ist leicht zu erkennen, woher jede einzelneMeldung kommt:

[!] Friseursalon-Simulation wird gestartet.[s] Der Salon ist geoffnet[!] 20 Kunden generiert.[b] Kunde 1 wird angenommen.[b] Beginn Haarschnitt fur Kunde 1[c] Kunde 1 ist fertig.[b] Kunde 2 wird angenommen.[b] Beginn Haarschnitt fur Kunde 2[b] Kunde 3 wird angenommen.[c] Kunde 2 ist fertig.[b] Beginn Haarschnitt fur Kunde 3[b] Kunde 4 wird angenommen.[b] Kunde 5 wird angenommen.[c] Kunde 3 ist fertig.[b] Beginn Haarschnitt fur Kunde 4[b] Kunde 6 wird angenommen.[b] Kunde 7 wird angenommen.[c] Kunde 4 ist fertig.[b] Beginn Haarschnitt fur Kunde 5[b] Kunde 8 wird angenommen.[c] Kunde 5 ist fertig.[b] Beginn Haarschnitt fur Kunde 6[b] Kunde 9 wird angenommen.[c] Kunde 6 ist fertig.[b] Beginn Haarschnitt fur Kunde 7[b] Kunde 10 wird angenommen.[c] Kunde 7 ist fertig.[b] Beginn Haarschnitt fur Kunde 8[b] Kunde 11 wird angenommen.[b] Zu wenig Stuhle; Kunde 12 wird abgelehnt.[c] Kunde 8 ist fertig.[b] Beginn Haarschnitt fur Kunde 9[b] Kunde 13 wird angenommen.[b] Zu wenig Stuhle; Kunde 14 wird abgelehnt.[c] Kunde 9 ist fertig.[b] Beginn Haarschnitt fur Kunde 10[b] Kunde 15 wird angenommen.[b] Zu wenig Stuhle; Kunde 16 wird abgelehnt.[b] Zu wenig Stuhle; Kunde 17 wird abgelehnt.[b] Zu wenig Stuhle; Kunde 18 wird abgelehnt.

Aktoren in Scala | 219

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

[b] Beginn Haarschnitt fur Kunde 11[c] Kunde 10 ist fertig.[b] Kunde 19 wird angenommen.[c] Kunde 11 ist fertig.[b] Beginn Haarschnitt fur Kunde 13[b] Kunde 20 wird angenommen.[c] Kunde 13 ist fertig.[b] Beginn Haarschnitt fur Kunde 15[c] Kunde 15 ist fertig.[b] Beginn Haarschnitt fur Kunde 19[c] Kunde 19 ist fertig.[b] Beginn Haarschnitt fur Kunde 20[c] Kunde 20 ist fertig.[!] 15 Kunden haben heute eine Haarschnitt bekommen.

Sie werden feststellen, dass sich die Ausgaben, wie zu erwarten, bei jedem Programmlaufetwas unterscheiden. Immer wenn der Friseur länger für das Haareschneiden braucht, alsZeit zwischen dem Eintreffen der Kunden vergeht, füllen sich die »Stühle« und über-zählige Kunden werden fortgeschickt.

Natürlich müssen wir hier die Einschränkungen hinzufügen, die mit einfachen Beispieleneinhergehen. Zum einen ist es möglich, dass unser Beispiel nicht ausreichend zufällig ist,inbesondere wenn die Zufallszahlen innerhalb einer Millisekunde nacheinander gezogenwerden. Das ergibt sich aus der Art und Weise, wie die JVM Zufallszahlen generiert undist ein Hinweis darauf, dass man vorsichtig sein muss, wenn man Zufälligkeit in neben-läufigen Programmen nutzt. Außerdem sollte das sleep in tallyCuts durch ein klareresSignal dafür ersetzt werden, dass die verschiedenen Aktoren im System ihre jeweiligeTätigkeit abgeschlossen haben. Beispielsweise könnte man aus BarbershopSimulationeinen Aktor machen, an den Nachrichten gesandt werden, durch die der Abschluss derTätigkeit gemeldet wird.

Verändern Sie den Code, indem Sie mehr Kunden, zusätzliche Nachrichtentypen und/oder andere Verzögerungen einführen oder sämtliche Zufälligkeiten entfernen. Wenn Sieerfahren in der Multithread-Programmierung sind, können Sie Ihre eigene Implementie-rung des schlafenden Friseurs in einer anderen Sprache schreiben und sie dieser hiergegenüberstellen. Wir sind geneigt zu wetten, dass eine Implementierung in Scala mitAktoren kürzen und besser wartbar ist.

Effektive AktorenUm aus den Aktoren das meiste herauszuholen, gilt es einiges zu bedenken. Beachten Sieerstens, dass es verschiedene Methoden gibt, mit denen Sie Ihre Aktoren dazu bringenkönnen, sich unterschiedlich zu verhalten. Die Übersicht in Tabelle 9-1 soll deutlichmachen, für welche Situation sich welche Methode eignet.

220 | Kapitel 9: Robuste, skalierbare Nebenläufigkeit mit Aktoren

Dean Wampler / Alex Payne, Programmieren mit Scala, O´Reilly, ISBN 9783897216471

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

Tabelle 9-1: Aktor-Methoden

Methode Ergebnis Beschreibung

act Unit Abstrakte Methode auf der obersten Ebene für einen Aktor. Enthälttypischerweise eine der folgenden Methoden.

receive Ergebnis der Verarbeitungder Nachricht

Blockiert, bis eine Nachricht eines passenden Typs eingeht.

receiveWithin Ergebnis der Verarbeitungder Nachricht

Wie receive, wartet aber nur die angegebene Anzahl von Millisekunden.

react Nichts Erfordert geringere Ressourcen (Threads) als receive.

reactWithin Nichts Wie react, wartet aber nur die angegebene Anzahl von Millisekunden.

Normalerweise werden Sie möglichst react verwenden. Wenn Sie das Ergebnis derVerarbeitung einer Nachricht benötigen (d.h. wenn Sie eine synchrone Reaktion ausdem Versand einer Nachricht an einen Aktor brauchen), sollten Sie die Variante receive-Within verwenden, um das Risiko einer dauerhaften Blockade zu vermindern, wenn sichein Aktor verkeilt hat.

Eine andere Strategie, um auf Aktoren basierten Code asynchron zu behandeln, besteht inder Vewendung von Futures. Ein Future ist ein Platzhalterobjekt für einen Wert, der nochnicht von einem asynchronen Prozess zurückgeliefert worden ist. Sie können eine Nach-richt an einen Aktor mit der Methode !! senden; diese Variante ermöglicht Ihnen, einepartielle Funktion mitzugeben, die auf den Future-Wert angewandt wird. Wie Sie amfolgenden Beispiel erkennen können, ist das Auslesen eines Wertes aus einem Future soeinfach wie der Aufruf seiner apply-Methode. Beachten Sie aber, dass das Auslesen einesWerts aus einem Future eine blockierende Operation ist:

// code-examples/Concurrency/future-script.scalaimport scala.actors.Futures._

val eventually = future(5 * 42)println(eventually())

Jeder Aktor in Ihrem System sollte eine klar definierte Zuständigkeit haben. Benutzen Siekeine Aktoren für allgemeine, stark zustandsbehaftetet Aufgaben. Denken Sie eher wie einRegisseur: Was sind die spezifischen Rollen in dem »Drehbuch« Ihrer Anwendung, undwas ist der geringste Umfang an Informationen, die ein Aktor benötigt, um seine Aufgabeausführen zu können? Geben Sie jedem Aktor nur einige wenige Aufgaben und verwen-den Sie Nachrichten (normalerweise in der Form einer case class oder eines case object),um diese Aufgaben an andere Aktoren zu delegieren.

Zögern Sie nicht, Daten zu kopieren, wenn Sie Aktor-zentrischen Code schreiben. Je mehrIhr Design auf Unveränderbarkeit beruht, desto weniger wahrscheinlich ist es, dass SieSchwierigkeiten mit unerwarteten Zuständen bekommen. Je mehr Sie mittels Nachrichtenkommunizieren, desto weniger Sorgen müssen Sie sich über die Synchronisation machen.Die vielen Nachrichten und unveränderbaren Variablen mögen Ihnen übermäßig teuererscheinen, aber angesichts der heutzutage verfügbaren Hardware scheint der Preis des

Aktoren in Scala | 221

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

zusätzlichen Speicherbedarfs für das Mehr an Klarheit und Vorhersagbarkeit bei denmeisten Anwendungen mehr als angemessen zu sein.

Sie sollten aber auch wissen, wann Aktoren nicht das geeignete Mittel sind. Dass Aktoreneine hervorragende Möglichkeit sind, in Scala mit Nebenläufigkeit umzugehen, heißt, wiewir gleich sehen werden, nicht, dass sie die einzige Möglichkeit dafür darstellen. Traditio-nelles Threading und Locking kann geeigneter sein, wenn der kritische Pfad schreibbetontist und ein nachrichtenbasierter Ansatz zu viel Zusatzlast mit sich bringt. Unserer Erfahrungnach ist es sinnvoll, den Prototyp einer nebenläufigen Lösung mit einem rein Aktor-basier-ten Design zu erstellen und dann mithilfe von Profiling-Werkzeugen diejenigen Teile derAnwendung herauszufinden, die von einem anderen Ansatz profitieren könnten.

Traditionelle Nebenläufigkeit in Scala: Threading und EventsAktoren stellen eine hervorragende, aber nicht die einzige Möglichkeit dar, um in Scalamit Nebenläufigkeit umzugehen. Da Scala mit Java interoperieren kann, sind auch dieKonzepte für Nebenläufigkeit anwendbar, die Ihnen auf der JVM vertraut sind.

EinmalthreadsFür Anfänger bietet Scala eine praktische Möglichkeit, ein Stück Code in einem neuenThread laufen zu lassen:

// code-examples/Concurrency/threads/by-block-script.scala

new Thread { println("Das hier lauft in einem neuen Thread.") }

Ein ähnliches Konstrukt gibt es im Package scala.concurrent in der Form des ops-Objekts,mit dessen Methode spawn man einen Block asynchron laufen lassen kann:

// code-examples/Concurrency/threads/spawn.scala

import scala.concurrent.ops._

object SpawnExample {def main(args: Array[String]) {

println("Das hier lauft synchron.")

spawn {println("Das hier lauft asynchron.")

}}

}

Mit java.util.concurrent arbeitenWenn Sie mit dem ehrwürdigen Package java.util.concurrent vertraut sind, werden Siefeststellen, dass es von Scala aus genauso leicht (oder genauso schwer, abhängig von IhremStandpunkt) verwendbar ist. Wir benutzen Executors, um einen Thread-Pool zu erzeugen. In

222 | Kapitel 9: Robuste, skalierbare Nebenläufigkeit mit Aktoren

Dean Wampler / Alex Payne, Programmieren mit Scala, O´Reilly, ISBN 9783897216471

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

diesem Thread-Pool lassen wir eine einfache Klasse laufen, die das Java-Interface für Thread-freundliche Klassen Runnable implementiert und anzeigt, welcher Thread gerade läuft:

// code-examples/Concurrency/threads/util-concurrent-script.scala

import java.util.concurrent._

class ThreadIdentifier extends Runnable {def run {

println("Hallo von Thread " + currentThread.getId)}

}

val pool = Executors.newFixedThreadPool(5)

for (i <- 1 to 10) {pool.execute(new ThreadIdentifier)

}

Wie bei der standardmäßigen Nebenläufigkeit in Java ist es die run-Methode, mit der eineMultithreading-fähige Klasse startet. Jedesmal, wenn unser pool einen neuen ThreadIden-tifier startet, wird seine run-Methode aufgerufen. Ein Blick in die Ausgabe zeigt uns, dassdas Programm mit fünf Threads im Pool läuft, deren IDs von 9 bis 13 reichen:

Hallo von Thread 9Hallo von Thread 10Hallo von Thread 11Hallo von Thread 12Hallo von Thread 13Hallo von Thread 9Hallo von Thread 11Hallo von Thread 10Hallo von Thread 10Hallo von Thread 13

Damit kratzen wir natürlich nur an der Oberfläche von dem, was mit java.util.concur-rent möglich ist. Sie werden feststellen, dass alles, was Sie von Java her über Neben-läufigkeit wissen, auch in Scala anwendbar ist. Sie können aber dieselben Aufgaben mitweniger Code erledigen, was zur Pflegbarkeit und Produktivität beitragen sollte.

EventsThreading und Aktoren sind nicht die einzigen Möglichkeiten, um mit Nebenläufigkeit zuarbeiten. Ereignisbasierte Nebenläufigkeit, ein spezifischer Ansatz für asynchrone odernichtblockierende Ein-/Ausgaben (non-blocking, NIO), ist zur bevorzugten Methode fürdie Programmierung von Servern geworden, die für Tausende von gleichzeitig laufendenClients skalierbar sein müssen. Indem es die traditionelle Eins-zu-eins-Beziehung zwi-schen Threads und Clients vermeidet, legt dieses Modell der Nebenläufigkeit Ereignisseoffen, die dadurch ausgelöst werden, dass bestimmte Bedingungen zutreffen (zum Bei-spiel wenn von einem Client über ein Netzwerk-Socket Daten empfangen werden).Typischerweise verknüpft die Programmiererin mit jedem Ereignis, das für ihr Programmrelevant ist, eine Callback-Methode.

Traditionelle Nebenläufigkeit in Scala: Threading und Events | 223

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

Das Package java.nio bietet zwar eine Fülle nützlicher Detailfunktionen für nichtblo-ckiende I/O (Puffer, Kanäle usw.), aus diesen einfachen Teilen ein ereignisbasiertes,nebenläufiges Programm zusammenzuschustern, ist aber immer noch ein ziemlichesStück Arbeit. Hier kommt Apache MINA ins Spiel, das auf Java NIO aufbaut und aufseiner Homepage so beschrieben ist: »Ein Framework for Netzwerkanwendungen, dasAnwendern ermöglicht, Netzwerkanwendungen mit hoher Performanz und hoher Ska-lierbarkeit einfach zu entwickeln« (vgl. [MINA]).

Nun mag MINA einfacher anzuwenden sein als die eingebauten NIO-Bibliotheken vonJava, aber wir haben uns inzwischen an manche von Scala gebotene Bequemlichkeitgewöhnt, die in MINA natürlich nicht verfügbar ist. Deshalb fügt die Open Source-Biblio-thek Naggati (vgl. [Naggati]) eine Scala-freundliche Schicht oberhalb von MINA hinzu,die es ihrem Autor zufolge »einfacher macht, Protokollfilter in sequenziellem Stil« zubauen. Naggati ist im Prinzip eine DSL zum Parsen von Netzwerkprotokollen, die auf derBasis der mächtigen NIO-Fähigkeiten von MINA implementiert ist.

Wir wollen mithilfe von Naggati die Grundbestandteile eines SMTP-E-Mail-Serversbauen. Um das Ganze einfach zu halten, gehen wir dabei nur mit zwei SMTP-Befehlenum: HELO und QUIT. Der erste der beiden Befehle identifiziert einen Client und der zweitebeendet die Session eines Client.

Der Ordnung halber setzen wir auch eine Testsuite auf der Basis von Specs ein, derBibliothek für verhaltensgetriebene Softwareentwicklung (siehe den Abschnitt »Specs«auf Seite 395):

// .../smtpd/src/test/scala/com/programmingscala/smtpd/SmtpDecoderSpec.scala

package com.programmingscala.smtpd

import java.nio.ByteOrderimport net.lag.naggati._import org.apache.mina.core.buffer.IoBufferimport org.apache.mina.core.filterchain.IoFilterimport org.apache.mina.core.session.{DummySession, IoSession}import org.apache.mina.filter.codec._import org.specs._import scala.collection.{immutable, mutable}

object SmtpDecoderSpec extends Specification {private var fakeSession: IoSession = nullprivate var fakeDecoderOutput: ProtocolDecoderOutput = nullprivate var written = new mutable.ListBuffer[Request]

def quickDecode(s: String): Unit = {Codec.decoder.decode(fakeSession, IoBuffer.wrap(s.getBytes), fakeDecoderOutput)

}

"SmtpRequestDecoder" should {doBefore {written.clear()fakeSession = new DummySessionfakeDecoderOutput = new ProtocolDecoderOutput {

224 | Kapitel 9: Robuste, skalierbare Nebenläufigkeit mit Aktoren

Dean Wampler / Alex Payne, Programmieren mit Scala, O´Reilly, ISBN 9783897216471

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

override def flush(nextFilter: IoFilter.NextFilter, s: IoSession) = {}override def write(obj: AnyRef) = written += obj.asInstanceOf[Request]

}}

"parse HELO" in {quickDecode("HELO client.example.org\n")written.size mustEqual 1written(0).command mustEqual "HELO"written(0).data mustEqual "client.example.org"

}

"parse QUIT" in {quickDecode("QUIT\n")written.size mustEqual 1written(0).command mustEqual "QUIT"written(0).data mustEqual null

}}

}

Nach dem Aufsetzen einer Umgebung für alle Testläufe führt unsere Suite die beidenSMTP-Befehle aus, an denen wir interessiert sind. Der doBefore-Block läuft vor jedemeinzelnen Test und garantiert, dass die Mock-Session und die Ausgabepuffer in einemsauberen Zustand sind. In jedem Test übergeben wir unserem noch unimplementiertenCodec einen String mit hypothetischen Clienteingaben und verifizieren dann, ob derresultierende Request (eine case class) in den Feldern command und data den korrektenInhalt hat. Da der QUIT-Befehl keine weiteren Informationen vom Client benötigt, stellenwir hier nur sicher, dass data den Wert null hat.

Wenn die Tests bereit sind, wollen wir einen elementaren Codec (Encoder und Decoder)für SMTP implementieren:

// .../smtpd/src/main/scala/com/programmingscala/smtpd/Codec.scala

package com.programmingscala.smtpd

import org.apache.mina.core.buffer.IoBufferimport org.apache.mina.core.session.{IdleStatus, IoSession}import org.apache.mina.filter.codec._import net.lag.naggati._import net.lag.naggati.Steps._

case class Request(command: String, data: String)case class Response(data: IoBuffer)

object Codec {val encoder = new ProtocolEncoder {

def encode(session: IoSession, message: AnyRef, out: ProtocolEncoderOutput) = {val buffer = message.asInstanceOf[Response].dataout.write(buffer)

}

def dispose(session: IoSession): Unit = {// Leere Methode, wird vom Trait ProtocolEncoder benotigt.

Traditionelle Nebenläufigkeit in Scala: Threading und Events | 225

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

}}

val decoder = new Decoder(readLine(true, "ISO-8859-1") { line =>line.split(’ ’).first match {case "HELO" => state.out.write(Request("HELO", line.split(’ ’)(1))); Endcase "QUIT" => state.out.write(Request("QUIT", null)); Endcase _ => throw new ProtocolError("Fehlerhafte Request-Zeile: " + line)

}})

}

Zuerst definieren wir eine case class namens Request, in der eingende Request-Datengespeichert werden sollen. Dann spezifizieren wir den encoder-Teil unseres Codecs, dernur dazu da ist, Daten in den Puffer zu schreiben. Eine Methode dispose wird definiert(bleibt aber leer), um den Konrakt des Traits ProtocolEncoder zu erfüllen.

Der Dekoder ist der Teil, an dem wir eigentlich interessiert sind. readRequest liest eine Zeile,schneidet das erste Wort heraus und unterzieht es einem Pattern-Matching, um SMTP-Befehle zu finden. Im Fall des Befehls HELO greifen wir uns auch den restlichen Text in dieserZeile. Die Ergebnisse werden in ein Request-Objekt gepackt und in state hineingeschrieben.Sie können sich sicher denken, dass state unsere Fortschritte den ganzen Parse-Prozesshindurch speichert.

Auch wenn es trivial ist, zeigt das obige Beispiel, wie einfach es mit Naggati ist, Protokollezu parsen. Wo wir nun einen funktionierenden Codec haben, kombinieren wir Naggatiund Mina mit Aktoren, um den ganzen Server zu verdrahten.

Zuerst ein paar Zeilen Setup-Routinearbeit, um unseren SMPT-Server auf die Beine zustellen:

// .../smtpd/src/main/scala/com/programmingscala/smtpd/Main.scala

package com.programmingscala.smtpd

import net.lag.naggati.IoHandlerActorAdapterimport org.apache.mina.filter.codec.ProtocolCodecFilterimport org.apache.mina.transport.socket.SocketAcceptorimport org.apache.mina.transport.socket.nio.{NioProcessor, NioSocketAcceptor}import java.net.InetSocketAddressimport java.util.concurrent.{Executors, ExecutorService}import scala.actors.Actor._

object Main {val listenAddress = "0.0.0.0"val listenPort = 2525

def setMaxThreads = {val maxThreads = (Runtime.getRuntime.availableProcessors * 2)System.setProperty("actors.maxPoolSize", maxThreads.toString)

}

def initializeAcceptor = {var acceptorExecutor = Executors.newCachedThreadPool()

226 | Kapitel 9: Robuste, skalierbare Nebenläufigkeit mit Aktoren

Dean Wampler / Alex Payne, Programmieren mit Scala, O´Reilly, ISBN 9783897216471

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

var acceptor =new NioSocketAcceptor(acceptorExecutor, new NioProcessor(acceptorExecutor))

acceptor.setBacklog(1000)acceptor.setReuseAddress(true)acceptor.getSessionConfig.setTcpNoDelay(true)acceptor.getFilterChain.addLast("codec",

new ProtocolCodecFilter(smtpd.Codec.encoder, smtpd.Codec.decoder))acceptor.setHandler(

new IoHandlerActorAdapter(session => new SmtpHandler(session)))acceptor.bind(new InetSocketAddress(listenAddress, listenPort))

}

def main(args: Array[String]) {setMaxThreadsinitializeAcceptorprintln("smtpd: lauft und horcht an " + listenAddress + ":" + listenPort)

}}

Um sicherzustellen, dass wir das meiste aus den Aktor-Instanzen auf unserem Serverherausholen, setzen wir die System-Property actors.maxPoolSize auf das Doppelte derAnzahl der auf unserem Rechner verfügbarer Prozessoren. Dann initialisieren wir einenNioSocketAcceptor, ein Schlüsselelement der MINA-Maschinerie, das neue Verbindungenvon Clients annimmt. Die letzten drei Zeilen dieser Konfiguration sind insofern kritisch, alssie unseren Codec in Gang setzen, dem Acceptor mitteilen, dass er Requests mithilfe einesspeziellen Objekts abhandeln soll, und den Server starten, der nun an Port 2525 horchendauf neue Verbindungen wartet (richtige SMTP-Server laufen am privilegierten Port 25).

Das erwähnte spezielle Objekt ist ein Aktor, der in einem IoHandlerActorAdapter verpacktist. Dieser ist ein Teil der überbrückend Schicht zwischen Scala-Aktoren und MINA, diedurch Naggati bereitgestellt wird. Dies ist der Teil unseres Servers, der Antworten an denClient sendet. Da wir dank unserem Dekoder jetzt wissen, was der Client gesagt hat, sindwir auch in der Lage, entsprechend zu antworten!

// .../smtpd/src/main/scala/com/programmingscala/smtpd/SmtpHandler.scala

package com.programmingscala.smtpd

import net.lag.naggati.{IoHandlerActorAdapter, MinaMessage, ProtocolError}import org.apache.mina.core.buffer.IoBufferimport org.apache.mina.core.session.{IdleStatus, IoSession}import java.io.IOExceptionimport scala.actors.Actorimport scala.actors.Actor._import scala.collection.{immutable, mutable}

class SmtpHandler(val session: IoSession) extends Actor {start

def act = {loop {react {

case MinaMessage.MessageReceived(msg) =>handle(msg.asInstanceOf[smtpd.Request])

Traditionelle Nebenläufigkeit in Scala: Threading und Events | 227

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

case MinaMessage.SessionClosed => exit()case MinaMessage.SessionIdle(status) => session.closecase MinaMessage.SessionOpened => reply("220 localhost Tapir SMTPd 0.1\n")

case MinaMessage.ExceptionCaught(cause) => {cause.getCause match {

case e: ProtocolError => reply("502 Fehler: " + e.getMessage + "\n")case i: IOException => reply("502 Fehler: " + i.getMessage + "\n")case _ => reply("502 Fehler unbekannt\n")

}session.close

}}

}}

private def handle(request: smtpd.Request) = {request.command match {case "HELO" => reply("250 Hallo " + request.data + "\n")case "QUIT" => reply("221 Bis bald mal wieder\n"); session.close

}}

private def reply(s: String) = {session.write(new smtpd.Response(IoBuffer.wrap(s.getBytes)))

}

}

Wir erkennen sofort dasselbe Muster, das wir schon bei den Aktoren weiter oben imKapitel gesehen haben: Sie laufen in einer Schleife um einen react-Block, der ein Pattern-Matching mit einer kleinen Anzahl von Fällen durchführt. Im SmtpHandler sind alle dieseFälle Events, die von MINA bereitgestellt werden. Beispielsweise schickt MINA unsMinaMessage.SessionOpened, wenn sich ein Client verbindet, und MinaMessage.Session-Closed, wenn eine Clientverbindung beendet wird.

Der Fall, der uns am meisten interessiert, ist MinaMessage.MessageReceived. Wir bekom-men bei jeder neu eingegangenen gültigen Nachricht ein uns vertrautes Request-Objekt,mit dessen Feld command wir ein Pattern-Matching durchführen können, um die entspre-chende Handlung durchzuführen. Wenn der Client HELO sagt, antworten wir mit einerBegrüßung. Wenn der Client QUIT sagt, verabschieden wir uns und kappen die Verbin-dung mit ihm.

Nachdem wir nun alle Einzelteile beisammenhaben, wollen wir uns etwas mit unseremServer unterhalten:

[al3x@jaya ~]$ telnet localhost 2525Trying ::1...Connected to localhost.Escape character is ’^]’.220 localhost Tapir SMTPd 0.1HELO jaya.local250 Hallo jaya.local

228 | Kapitel 9: Robuste, skalierbare Nebenläufigkeit mit Aktoren

Dean Wampler / Alex Payne, Programmieren mit Scala, O´Reilly, ISBN 9783897216471

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

QUIT221 Bis baldConnection closed by foreign host.

Sicher, die Konversation ist kurz, aber unser Server funktioniert! Aber was geschieht,wenn wir ihn mit Unerwartetem füttern?

[al3x@jaya ~]$ telnet localhost 2525Trying ::1...Connected to localhost.Escape character is ’^]’.220 localhost Tapir SMTPd 0.1HELO jaya.local250 Hallo jaya.localBAD COMMAND502 Fehler: Fehlerhafte Request-Zeile: BAD COMMANDConnection closed by foreign host.

Auch damit geht er freundlich um. Gut, dass wir uns die Zeit genommen haben, dieseExceptions beim Erhalt eines MinaMessage.ExceptionCaught in unserem Aktor SmtpHandlereinzurichten.

Natürlich kann das, was wir hier gebaut haben, nur mit den Beginn und dem Ende einerkompletten SMTP-Konversation umgehen. Als Übung können Sie versuchen, die übrigenBefehle hinzuzufügen. Oder, wenn Sie einen Schritt weiter gehen und sich einen kom-pletten Mailserver ansehen möchten, der dem hier gebauten sehr ähnlich sieht, danngehen Sie zum Open Source Projekt Mailslot auf GitHub (siehe [Mailslot]).

Zusammenfassung und AusblickSie haben gelernt, wie man mithilfe der Aktor-Bibliothek von Scala eine skalierbare undrobuste nebenläufige Anwendung bauen kann und dabei die Probleme vermeidet, die mittraditionellen Ansätzen auf der Basis von synchronisierten Zugriffen auf gemeinsamen,veränderbaren Zustand verbunden sind. Außerdem haben wir demonstriert, dass dasleistungsfähige, in Java eingebaute Threading-Modell von Scala aus leicht zugänglich ist.Schließlich haben Sie erfahren, wie man Aktoren mit dem mächtigen NIO-FrameworkMINA und Naggati kombiniert, um mit wenigen Zeilen Code von Grund auf einenereignisgesteuerten, asynchronen Netzwerkserver zu entwickeln.

Das nächste Kapitel befasst sich mit der in Scala eingebauten Unterstützung für die Arbeitmit XML.

Zusammenfassung und Ausblick | 229

Dies ist ein A

uszug aus dem B

uch "Program

mieren m

it Scala", IS

BN

978-3-89721-647-1 http://w

ww

.oreilly.de/catalog/progscalager/ D

ieser Auszug unterliegt dem

Urheberrecht. ©

O’R

eilly Verlag 2010

Dean Wampler / Alex Payne, Programmieren mit Scala, O´Reilly, ISBN 9783897216471