2013-02-05 15:48:29 +01:00
|
|
|
/**
|
|
|
|
|
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
|
|
|
|
|
*/
|
|
|
|
|
package akka.io
|
|
|
|
|
|
|
|
|
|
import akka.actor.{ Actor, ActorLogging, ActorRef }
|
|
|
|
|
import akka.io.SelectionHandler._
|
|
|
|
|
import akka.io.UdpConn._
|
|
|
|
|
import akka.util.ByteString
|
2013-02-07 17:54:42 +01:00
|
|
|
import java.nio.ByteBuffer
|
2013-02-05 15:48:29 +01:00
|
|
|
import java.nio.channels.DatagramChannel
|
|
|
|
|
import java.nio.channels.SelectionKey._
|
2013-02-06 11:38:42 +01:00
|
|
|
import scala.annotation.tailrec
|
2013-02-07 17:54:42 +01:00
|
|
|
import scala.util.control.NonFatal
|
2013-02-05 15:48:29 +01:00
|
|
|
|
2013-02-15 11:59:01 +01:00
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2013-02-06 12:17:52 +01:00
|
|
|
private[io] class UdpConnection(val udpConn: UdpConnExt,
|
|
|
|
|
val commander: ActorRef,
|
|
|
|
|
val connect: Connect) extends Actor with ActorLogging {
|
2013-02-05 15:48:29 +01:00
|
|
|
|
|
|
|
|
def selector: ActorRef = context.parent
|
|
|
|
|
|
2013-02-06 12:17:52 +01:00
|
|
|
import connect._
|
2013-02-07 17:54:42 +01:00
|
|
|
import udpConn._
|
2013-02-05 15:48:29 +01:00
|
|
|
import udpConn.settings._
|
|
|
|
|
|
|
|
|
|
var pendingSend: (Send, ActorRef) = null
|
|
|
|
|
def writePending = pendingSend ne null
|
|
|
|
|
|
|
|
|
|
context.watch(handler) // sign death pact
|
|
|
|
|
val channel = {
|
|
|
|
|
val datagramChannel = DatagramChannel.open
|
|
|
|
|
datagramChannel.configureBlocking(false)
|
|
|
|
|
val socket = datagramChannel.socket
|
|
|
|
|
options.foreach(_.beforeDatagramBind(socket))
|
2013-02-06 12:17:52 +01:00
|
|
|
try {
|
2013-02-10 13:52:52 +01:00
|
|
|
localAddress foreach socket.bind
|
2013-02-06 12:17:52 +01:00
|
|
|
datagramChannel.connect(remoteAddress)
|
|
|
|
|
} catch {
|
|
|
|
|
case NonFatal(e) ⇒
|
2013-02-10 13:52:52 +01:00
|
|
|
log.error(e, "Failure while connecting UDP channel to remote address [{}] local address [{}]",
|
|
|
|
|
remoteAddress, localAddress.map { _.toString }.getOrElse("undefined"))
|
2013-02-06 12:17:52 +01:00
|
|
|
commander ! CommandFailed(connect)
|
|
|
|
|
context.stop(self)
|
|
|
|
|
}
|
2013-02-05 15:48:29 +01:00
|
|
|
datagramChannel
|
|
|
|
|
}
|
|
|
|
|
selector ! RegisterChannel(channel, OP_READ)
|
2013-02-10 13:52:52 +01:00
|
|
|
log.debug("Successfully connected to [{}]", remoteAddress)
|
2013-02-05 15:48:29 +01:00
|
|
|
|
|
|
|
|
def receive = {
|
|
|
|
|
case ChannelRegistered ⇒
|
2013-02-06 12:17:52 +01:00
|
|
|
commander ! Connected
|
2013-02-05 15:48:29 +01:00
|
|
|
context.become(connected, discardOld = true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def connected: Receive = {
|
|
|
|
|
case StopReading ⇒ selector ! DisableReadInterest
|
|
|
|
|
case ResumeReading ⇒ selector ! ReadInterest
|
2013-02-07 17:54:42 +01:00
|
|
|
case ChannelReadable ⇒ doRead(handler)
|
2013-02-05 15:48:29 +01:00
|
|
|
|
|
|
|
|
case Close ⇒
|
2013-02-10 13:52:52 +01:00
|
|
|
log.debug("Closing UDP connection to [{}]", remoteAddress)
|
2013-02-05 15:48:29 +01:00
|
|
|
channel.close()
|
|
|
|
|
sender ! Disconnected
|
2013-02-10 13:52:52 +01:00
|
|
|
log.debug("Connection closed to [{}], stopping listener", remoteAddress)
|
2013-02-05 15:48:29 +01:00
|
|
|
context.stop(self)
|
|
|
|
|
|
|
|
|
|
case send: Send if writePending ⇒
|
|
|
|
|
if (TraceLogging) log.debug("Dropping write because queue is full")
|
|
|
|
|
sender ! CommandFailed(send)
|
|
|
|
|
|
|
|
|
|
case send: Send if send.payload.isEmpty ⇒
|
|
|
|
|
if (send.wantsAck)
|
|
|
|
|
sender ! send.ack
|
|
|
|
|
|
|
|
|
|
case send: Send ⇒
|
|
|
|
|
pendingSend = (send, sender)
|
|
|
|
|
selector ! WriteInterest
|
|
|
|
|
|
|
|
|
|
case ChannelWritable ⇒ doWrite()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def doRead(handler: ActorRef): Unit = {
|
2013-02-06 11:38:42 +01:00
|
|
|
@tailrec def innerRead(readsLeft: Int, buffer: ByteBuffer): Unit = {
|
2013-02-05 15:48:29 +01:00
|
|
|
buffer.clear()
|
|
|
|
|
buffer.limit(DirectBufferSize)
|
|
|
|
|
|
2013-02-06 11:38:42 +01:00
|
|
|
if (channel.read(buffer) > 0) {
|
2013-02-07 17:54:42 +01:00
|
|
|
buffer.flip()
|
2013-02-06 11:38:42 +01:00
|
|
|
handler ! Received(ByteString(buffer))
|
|
|
|
|
innerRead(readsLeft - 1, buffer)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
val buffer = bufferPool.acquire()
|
|
|
|
|
try innerRead(BatchReceiveLimit, buffer) finally {
|
2013-02-05 15:48:29 +01:00
|
|
|
selector ! ReadInterest
|
|
|
|
|
bufferPool.release(buffer)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final def doWrite(): Unit = {
|
|
|
|
|
|
|
|
|
|
val buffer = udpConn.bufferPool.acquire()
|
|
|
|
|
try {
|
|
|
|
|
val (send, commander) = pendingSend
|
|
|
|
|
buffer.clear()
|
|
|
|
|
send.payload.copyToBuffer(buffer)
|
|
|
|
|
buffer.flip()
|
|
|
|
|
val writtenBytes = channel.write(buffer)
|
2013-02-10 13:52:52 +01:00
|
|
|
if (TraceLogging) log.debug("Wrote [{}] bytes to channel", writtenBytes)
|
2013-02-05 15:48:29 +01:00
|
|
|
|
|
|
|
|
// Datagram channel either sends the whole message, or nothing
|
|
|
|
|
if (writtenBytes == 0) commander ! CommandFailed(send)
|
|
|
|
|
else if (send.wantsAck) commander ! send.ack
|
|
|
|
|
} finally {
|
|
|
|
|
udpConn.bufferPool.release(buffer)
|
|
|
|
|
pendingSend = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def postStop() {
|
|
|
|
|
if (channel.isOpen) {
|
|
|
|
|
log.debug("Closing DatagramChannel after being stopped")
|
|
|
|
|
try channel.close()
|
|
|
|
|
catch {
|
|
|
|
|
case NonFatal(e) ⇒ log.error(e, "Error closing DatagramChannel")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|