pekko/akka-actors/src/test/scala/dispatch/HawtDispatcherEchoServer.scala

207 lines
5.8 KiB
Scala

/**
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.actor.dispatch
import scala.collection.mutable.ListBuffer
import java.util.concurrent.TimeUnit
import java.net.InetSocketAddress
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.channels.{SocketChannel, SelectionKey, ServerSocketChannel}
import se.scalablesolutions.akka.actor._
import se.scalablesolutions.akka.actor.Actor._
import se.scalablesolutions.akka.dispatch.HawtDispatcher
import org.fusesource.hawtdispatch.DispatchSource
import org.fusesource.hawtdispatch.ScalaDispatch._
/**
* This is an example of how to crate an Akka actor based TCP echo server using
* the HawtDispatch dispatcher and NIO event sources.
*/
object HawtDispatcherEchoServer {
private val hawt = new HawtDispatcher
var port=4444;
var useReactorPattern=true
def main(args:Array[String]):Unit = run
def run() = {
val server = actorOf(new Server(port))
server.start
Scheduler.schedule(server, DisplayStats, 1, 5, TimeUnit.SECONDS)
println("Press enter to shutdown.");
System.in.read
server ! Shutdown
}
case object Shutdown
case object DisplayStats
case class SessionClosed(session:ActorRef)
class Server(val port: Int) extends Actor {
self.dispatcher = hawt
var channel:ServerSocketChannel = _
var accept_source:DispatchSource = _
var sessions = ListBuffer[ActorRef]()
override def init = {
channel = ServerSocketChannel.open();
channel.socket().bind(new InetSocketAddress(port));
channel.configureBlocking(false);
// Setup the accept source, it will callback to the handler methods
// via the actor's mailbox so you don't need to worry about
// synchronizing with the local variables
accept_source = createSource(channel, SelectionKey.OP_ACCEPT, HawtDispatcher.queue(self));
accept_source.setEventHandler(^{ accept });
accept_source.setDisposer(^{
channel.close();
println("Closed port: "+port);
});
accept_source.resume
println("Listening on port: "+port);
}
private def accept() = {
var socket = channel.accept();
while( socket!=null ) {
try {
socket.configureBlocking(false);
val session = actorOf(new Session(self, socket))
session.start()
sessions += session
} catch {
case e: Exception =>
socket.close
}
socket = channel.accept();
}
}
def receive = {
case SessionClosed(session) =>
sessions = sessions.filterNot( _ == session )
session.stop
case DisplayStats =>
sessions.foreach { session=>
session ! DisplayStats
}
case Shutdown =>
sessions.foreach { session=>
session.stop
}
sessions.clear
accept_source.release
self.stop
}
}
class Session(val server:ActorRef, val channel: SocketChannel) extends Actor {
self.dispatcher = hawt
val buffer = ByteBuffer.allocate(1024);
val remote_address = channel.socket.getRemoteSocketAddress.toString
var read_source:DispatchSource = _
var write_source:DispatchSource = _
var readCounter = 0L
var writeCounter = 0L
var closed = false
override def init = {
if(useReactorPattern) {
// Then we will be using the reactor pattern for handling IO:
// Pin this actor to a single thread. The read/write event sources will poll
// a Selector on the pinned thread. Since the IO events are generated on the same
// thread as where the Actor is pinned to, it can avoid a substantial amount
// thread synchronization. Plus your GC will perform better since all the IO
// processing is done on a single thread.
HawtDispatcher.pin(self)
} else {
// Then we will be using sing the proactor pattern for handling IO:
// Then the actor will not be pinned to a specific thread. The read/write
// event sources will poll a Selector and then asynchronously dispatch the
// event's to the actor via the thread pool.
}
// Setup the sources, they will callback to the handler methods
// via the actor's mailbox so you don't need to worry about
// synchronizing with the local variables
read_source = createSource(channel, SelectionKey.OP_READ, HawtDispatcher.queue(self));
read_source.setEventHandler(^{ read })
read_source.setCancelHandler(^{ close })
write_source = createSource(channel, SelectionKey.OP_READ, HawtDispatcher.queue(self));
write_source.setEventHandler(^{ write })
write_source.setCancelHandler(^{ close })
read_source.resume
println("Accepted connection from: "+remote_address);
}
override def shutdown = {
closed = true
read_source.release
write_source.release
channel.close
}
private def catchio(func: =>Unit):Unit = {
try {
func
} catch {
case e:IOException => close
}
}
def read():Unit = catchio {
channel.read(buffer) match {
case -1 =>
close // peer disconnected.
case 0 =>
case count:Int =>
readCounter += count
buffer.flip;
read_source.suspend
write_source.resume
write()
}
}
def write() = catchio {
writeCounter += channel.write(buffer)
if (buffer.remaining == 0) {
buffer.clear
write_source.suspend
read_source.resume
}
}
def close() = {
if( !closed ) {
closed = true
server ! SessionClosed(self)
}
}
def receive = {
case DisplayStats =>
println("connection to %s reads: %,d bytes, writes: %,d".format(remote_address, readCounter, writeCounter))
}
}
}