org.fusesource.hawtdispatch.example.UdpEchoServer.scala Maven / Gradle / Ivy
/**
* Copyright (C) 2012 FuseSource, Inc.
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.hawtdispatch.example
import java.io.{IOException}
import org.fusesource.hawtdispatch._
import java.nio.ByteBuffer
import java.nio.channels.{DatagramChannel, SelectionKey, ServerSocketChannel, SocketChannel}
import java.net.{SocketAddress, InetSocketAddress}
import java.util.LinkedList
/**
* A udp echo server example. Concurrently reads and writes
* packets by using 2 dispatch queues. Uses a custom dispatch
* source to handle coalescing interaction events between
* the sender and receiver queues.
*
* @author Hiram Chirino
*/
object UdpEchoServer {
var port=4444;
def main(args:Array[String]):Unit = {
run
}
def run() = {
val server = new Server(port)
server.start
println("Press enter to shutdown.");
System.in.read
server.close
}
class Server(val port: Int) {
val channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(port));
channel.configureBlocking(false);
println("Listening on port: "+port);
object receiver {
// All mutable state in this object is modified while executing
// on this queue
val queue = createQueue("receive")
private var outbound = 0
private val outbound_max = 1024;
val read_events = createSource(channel, SelectionKey.OP_READ, queue);
read_events.onEvent {
try {
val buffer = ByteBuffer.allocate(1024);
var address: SocketAddress = channel.receive(buffer);
if( address!=null ) {
buffer.flip;
outbound += 1
sender.outbound_events.merge((buffer, address))
// stop receiving until the outbound is drained (aka: flow control)
if ( outbound_max < outbound ) {
read_events.suspend
}
}
} catch {
case e:IOException => close
}
}
// outbound_ack_events is used to let the sender know when the sends complete
val outbound_ack_events = createSource(EventAggregators.INTEGER_ADD, queue)
outbound_ack_events.onEvent {
outbound -= outbound_ack_events.getData()
if(read_events.isSuspended)
read_events.resume()
}
outbound_ack_events.resume();
}
object sender {
// All mutable state in this object is modified while executing
// on this queue
val queue = createQueue("send")
// pick up outbound events
private val outbound = new LinkedList[(ByteBuffer, SocketAddress)]
// outbound_events is an event bridge between the receiver and the sender event queues
// It will merge multiple events from the receiver queue into 1 event that gets delivered
// to the sender queue
val outbound_events = createSource(new ListEventAggregator[(ByteBuffer, SocketAddress)], queue)
outbound_events.onEvent {
for( value <- outbound_events.getData() ) {
outbound.add(value)
}
drainOutbound
}
outbound_events.resume();
// We need to drain the list of outbound packets when socket reports it
// can be written to.
val write_events = createSource(channel, SelectionKey.OP_WRITE, queue);
write_events.onEvent(drainOutbound)
def drainOutbound:Unit = try {
while(!outbound.isEmpty) {
val (buffer, address) = outbound.peek();
channel.send(buffer, address)
if(buffer.remaining()==0) {
// Packet sent, let the receive know in case he stopped.
receiver.outbound_ack_events.merge(1)
outbound.poll()
} else {
// Could not complete the write, we may need
// to resume the write source
if(write_events.isSuspended)
write_events.resume()
return
}
}
// Nothing left? then stop looking for write events
if(!write_events.isSuspended)
write_events.suspend
} catch {
case e:IOException => close
}
}
def start() = {
receiver.read_events.resume
}
def close() = {
receiver.read_events.cancel
sender.write_events.cancel
channel.close
}
}
}