grizzled.net.udp.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grizzled-scala_2.9.1-1 Show documentation
Show all versions of grizzled-scala_2.9.1-1 Show documentation
A general-purpose Scala utility library
The newest version!
/*
---------------------------------------------------------------------------
This software is released under a BSD license, adapted from
http://opensource.org/licenses/bsd-license.php
Copyright (c) 2009, Brian M. Clapper
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names "clapper.org", "Grizzled Scala Library", nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
package grizzled.net
import scala.annotation.tailrec
import java.net.{DatagramSocket => JDKDatagramSocket}
import java.net.{DatagramPacket => JDKDatagramPacket}
import java.net.InetAddress
/** A `UDPDatagramSocket` object represents a UDP datagram socket,
* providing a simpler interface to sending and receiving UDP packets than
* the one provided by the Java JDK.
*
* == Sending UDP Datagrams ==
*
* The easiest way to explain how to use this API is with some code. So,
* without further ado, the following example shows how you might send the
* string "foo" to port 2003 on host "foo.example.com".
*
* {{{
* // Import the appropriate stuff.
* import grizzled.net._
*
* // First, create an grizzled.net.IPAddress object for the destination
* // machine.
* val address = IPAddress("foo.example.com")
*
* // Next, create the socket object. Since we're sending the packet,
* // we don't care what the local port is. By not specifying one, we allow
* // the operating system to choose one one for us. Similarly, by not
* // passing an explicit address, we indicate that the API should bind to a
* // wildcard address, so that the packet can go out over any appropriate
* // interface.
* val socket = UDPDatagramSocket()
*
* // Next, use the sendString() method to send the string
* socket.sendString("foo", address, 2003)
*
* // Finally, close the socket.
* socket.close()
* }}}
*
* That's pretty simple. However, using the utility methods provided by the
* `UDPDatagramSocket` companion object, we can further simplify the
* above code:
*
* {{{
* // Import the appropriate stuff.
* import grizzled.net._
*
* UDPDatagramSocket.sendString("foo", IPAddress("foo.example.com"), 2003)
* }}}
*
* The `sendString()` method in the companion object takes care
* of allocating the socket, sending the packet, and closing the socket.
* Obviously, if you're planning on sending multiple packets at once, you'll
* want to use the first example (perhaps in a loop), so you're not constantly
* allocating and deallocating sockets. But sending a one-shot UDP packet
* can be as simple as one line of code, as shown in the second example.
*
* Sending binary data is not much more complicated. You have to convert
* the data to a stream of bytes, which the server must then decode. After
* that, however, sending the bytes isn't much more difficult than sending
* a string. Here's an example, which assumes that you have already encoded
* the data to be send into an array of bytes.
*
* {{{
* // Import the appropriate stuff.
* import grizzled.net._
*
* // Encode the data into bytes. (Not shown.)
* val data: Array[Byte] = encodeTheData()
*
* // Create the socket object.
* val socket = UDPDatagramSocket()
*
* // Send the data.
* socket.send(data, IPAddress("foo.example.com"), 2003)
*
* // Finally, close the socket.
* socket.close()
* }}}
*
* Once again, there's a simple utility method that does most of the work
* for you:
*
* {{{
* // Import the appropriate stuff.
* import grizzled.net._
*
* // Encode the data into bytes. (Not shown.)
* val data: Array[Byte] = encodeTheData()
*
* // Send the data.
* UDPDatagramSocket.send(data, IPAddress("foo.example.com"), 2003)
* }}}
*
* == Receiving UDP Datagrams ==
*
* When receiving UDP datagrams, you must first bind to an incoming
* socket. That is, you must listen on the port to which clients are sending
* their packets. In this example, we create the socket object with the
* receiving port, and we allow the wildcard address to be used on the
* local machine (permitting us to receive packets on any interface).
*
* {{{
* // Import the appropriate stuff.
* import grizzled.net._
*
* // Create the socket object.
* val socket = UDPDatagramSocket(2003)
* }}}
*
* Next, we want to receive and process the incoming data. Let's assume
* that we're receiving the "foo" string (or, for that matter, any string)
* being sent by the sample sender, above.
*
* {{{
* // Receive and print strings.
* while (true)
* println(socket.receiveString(1024))
* }}}
*
* That code says, "Wait for incoming strings, using a 1024-byte buffer.
* Then, decode the strings and print them to standard output.
*
* Receiving bytes isn't much more difficult.
*
* {{{
* // Allocate a byte buffer. For efficiency, we'll re-use the same buffer
* // on every incoming message.
* val buf = Array.make[Byte](1024, 0)
*
* // Receive and process the incoming bytes. The process() method isn't
* // shown.
* while (true)
* {
* val totalRead = socket.receive(buf)
* process(buf, totalRead)
* }
* }}}
*
* The above loop can be made even simpler (though a little more obscure),
* since the `receive()` method is filling our buffer and
* returning a count of the number of bytes it put into the buffer:
*
* {{{
* while (true)
* process(buf, socket.receive(buf))
* }}}
*/
trait UDPDatagramSocket {
/** Whether or not the socket's broadcast flag is set. The broadcast
* flag corresponds to the `SO_BROADCAST` socket option. When
* this option is enabled, datagram sockets will receive packets sent
* to a broadcast address, and they are permitted to send packets to a
* broadcast address.
*
* @return whether or not the broadcast flag is set
*/
def broadcast: Boolean
/** Change the value of the socket's broadcast flag. The broadcast flag
* corresponds to the `SO_BROADCAST` socket option. When this
* option is enabled, datagram sockets will receive packets sent to a
* broadcast address, and they are permitted to send packets to a
* broadcast address.
*
* @param enable `true` to enable broadcast, `false` to disable it.
*/
def broadcast_=(enable: Boolean)
/** The local port to which the socket is bound.
*/
def port: Int
/** The local address to which the socket is bound.
*/
def address: IPAddress
/** Close the socket.
*/
def close(): Unit
/** Send data over the socket. Accepts any sequence of bytes (e.g.,
* `Array[Byte]`, `List[Byte]`).
*
* @param data the bytes to send
*/
def send(data: Seq[Byte], address: IPAddress, port: Int): Unit
/** Send string data over the socket. The internal UTF-16 strings are
* converted to the specified encoding before being sent.
*
* @param data the string to send
* @param encoding the encoding to use
* @param address the IP address to receive the string
* @param port the destination port on the remote machine
*/
def sendString(data: String,
encoding: String,
address: IPAddress,
port: Int): Unit = {
import java.io.{ByteArrayOutputStream, OutputStreamWriter}
import java.nio.charset.Charset
val buf = new ByteArrayOutputStream()
val writer = new OutputStreamWriter(buf, Charset.forName(encoding))
writer.write(data)
writer.flush()
val bytes: Array[Byte] = buf.toByteArray
send(bytes, address, port)
}
/** Send string data over the socket. Converts the string to UTF-8
* bytes, then sends the bytes.
*
* @param data the string to send
* @param address the IP address to receive the string
* @param port the destination port on the remote machine
*/
def sendString(data: String, address: IPAddress, port: Int): Unit =
sendString(data, "UTF-8", address, port)
/** Receive a buffer of bytes from the socket. The buffer is dynamically
* allocated and returned. For more efficiency, use the version of
* `receive()` that takes a caller-supplied buffer.
*
* @param length maximum number of bytes to receive
*
* @return a tuple containing the buffer of data (of size `length`)
* and the actual number of bytes read. The buffer might be longer
* than the number of bytes read.
*/
def receive(length: Int): (Array[Byte], Int) = {
val buf = makeByteArray(length)
val total = receive(buf)
(buf, total)
}
/** Receive a buffer of bytes from the socket, writing them into a
* caller-supplied fixed-size buffer. One simple way to create such
* a buffer:
*
* {{{
* // Create a 512-byte buffer initialized to zeros.
* val buf = Array.make[Byte](512, 0)
* }}}
*
* @param buf the buf into which to read the data
*
* @return actual number of bytes read
*/
def receive(buf: Array[Byte]): Int
/** Receive a string from the socket. The string is assumed to have
* been encoded in the specified encoding and is decoded accordingly.
*
* @param length maximum number of bytes (not characters) expected
* @param encoding the encoding to use when decoding the string
*
* @return the string
*/
def receiveString(length: Int, encoding: String): String = {
val buf = makeByteArray(length)
receiveString(buf, encoding)
}
/** Receive a string from the socket. The string is assumed to have
* been encoded in UTF-8 for transmission and is decoded accordingly.
*
* @param length maximum number of bytes (not characters) expected
*
* @return the string
*/
def receiveString(length: Int): String =
receiveString(length, "UTF-8")
/** Receive a string from the socket, using a caller-supplied receive
* buffer to hold the bytes actually received over the wire. The string
* is assumed to have been encoded in the specified encoding and is
* decoded accordingly.
*
* @param buf the buf into which to read the data
* @param encoding the encoding to use when decoding the string
*
* @return the string
*/
def receiveString(buf: Array[Byte], encoding: String): String = {
import java.io.{ByteArrayInputStream, InputStreamReader, StringWriter}
import java.nio.charset.Charset
import scala.collection.mutable.ArrayBuffer
val total = receive(buf)
val bytes = (0 until total map (i => buf(i))).toArray
val stream = new ByteArrayInputStream(bytes)
val reader = new InputStreamReader(stream, Charset.forName(encoding))
val chars = new StringWriter()
@tailrec def readNextChar(): Unit = {
val c: Int = reader.read()
if (c != -1) {
chars.write(c)
readNextChar()
}
}
readNextChar()
chars.flush()
chars.toString
}
/** Receive a string from the socket, using a caller-supplied receive
* buffer to hold the bytes actually received over the wire. The string
* is assumed to have been encoded in UTF-8 for transmission and is
* decoded accordingly.
*
* @param buf the buf into which to read the data
*
* @return the string
*/
def receiveString(buf: Array[Byte]): String =
receiveString(buf, "UTF-8")
/** Make a byte array of a given length, initialized to zeros.
*
* @param len length
*
* @return the array of bytes, initialized to zeros
*/
private def makeByteArray(length: Int): Array[Byte] =
(1 to length map(_ => 0.toByte)).toArray
}
/** Actual implementation of a UDP datagram socket. Implemented as a private
* class that extends a trait primarily to hide the use of a JDK
* `DatagramSocket` object.
*/
private class UDPDatagramSocketImpl(val socket: JDKDatagramSocket)
extends UDPDatagramSocket {
def broadcast: Boolean = socket.getBroadcast()
def broadcast_=(enable: Boolean) = socket.setBroadcast(enable)
def port: Int = socket.getPort()
def address: IPAddress = socket.getInetAddress()
def close(): Unit = socket.close()
def send(data: Seq[Byte], address: IPAddress, port: Int): Unit = {
val buf = data toArray
val packet = new JDKDatagramPacket(buf, buf.length, address, port)
socket.send(packet)
}
def receive(buf: Array[Byte]): Int = {
val packet = new JDKDatagramPacket(buf, buf.length)
socket.receive(packet)
packet.getLength
}
}
/** Companion object for the `UDPDatagramSocket` trait, containing
* methods to simplify creation of `UDPDatagramSocket` objects, as
* well as some useful utility methods. See the documentation for the
* `UDPDatagramSocket` trait for a full treatment on this API.
*
* @see UDPDatagramSocket
*/
object UDPDatagramSocket {
/** Create a UDP datagram socket object that's bound to the specified
* local port and IP address. The broadcast flag will initially be
* clear.
*
* @param address the local IP address
* @param port the local port
*
* @return the `UDPDatagramSocket` object
*/
def apply(address: IPAddress, port: Int): UDPDatagramSocket =
new UDPDatagramSocketImpl(new JDKDatagramSocket(port, address))
/** Create a UDP datagram socket object that's bound to the specified
* local port and the wildcard IP address. The broadcast flag will
* initially be clear.
*
* @param port the local port
*
* @return the `UDPDatagramSocket` object
*/
def apply(port: Int): UDPDatagramSocket =
new UDPDatagramSocketImpl(new JDKDatagramSocket(port))
/** Create a UDP datagram socket object that's bound to any available
* local port and the wildcard IP address. The broadcast flag will
* initially be clear.
*
* @param port the local port
*
* @return the `UDPDatagramSocket` object
*/
def apply(): UDPDatagramSocket =
new UDPDatagramSocketImpl(new JDKDatagramSocket())
/** Utility method for sending a non-broadcast UDP packet. This method
* is equivalent to the following code snippet:
*
* {{{
* // Bind to a local (source) UDP port.
* val socket = UDPDatagramSocket.bind()
* val address = IPAddress( /* details omitted */ )
* val port: Int = ...
*
* // Send the bytes to the specified destination address and UDP port.
* socket.send(bytes, address, port)
*
* // Close the socket
* socket.close()
* }}}
*
* @param bytes sequence of bytes to send
* @param address IP address to which to send bytes
* @param port UDP port to which to send bytes
*/
def send(bytes: Seq[Byte], address: IPAddress, port: Int): Unit =
UDPDatagramSocket.send(bytes, address, port, false)
/** Utility method for sending a UDP packet. This method is equivalent
* to the following code snippet:
*
* {{{
* // Bind to a local (source) UDP port.
* val socket = UDPDatagramSocket()
* val address = IPAddress( /* details omitted */ )
* val port: Int = ...
* val broadcast: Boolean = ...
*
* // Set (or clear) the broadcast flag.
* socket.broadcast = broadcast
*
* // Send the bytes to the specified destination address and UDP port.
* socket.send(bytes, address, port)
*
* // Close the socket
* socket.close()
* }}}
*
* @param bytes sequence of bytes to send
* @param address IP address to which to send bytes
* @param port UDP port to which to send bytes
* @param broadcast whether or not to enable broadcast on the socket
*/
def send(bytes: Seq[Byte],
address: IPAddress,
port: Int,
broadcast: Boolean): Unit = {
// Bind a socket to a local (source) port.
val socket = UDPDatagramSocket()
// Set or clear the broadcast flag.
socket.broadcast = broadcast
// Send the encoded message to the broadcast address and destination
// port.
socket.send(bytes, address, port)
// Close the socket.
socket.close()
}
/** Utility method for sending a non-broadcast UDP packet consisting of
* string data. The string data is encoded the specified encoding
* before being sent. This method is equivalent to the following code
* snippet:
*
* {{{
* // Bind to a local (source) UDP port.
* val socket = UDPDatagramSocket.bind()
* val address = IPAddress( /* details omitted */ )
* val port: Int = ...
* val encoding: String = ...
*
* // Send the string to the specified destination address and UDP port.
* socket.send(string, encoding, address, port)
*
* // Close the socket
* socket.close()
* }}}
*
* @param data String data to send
* @param encoding Encoding to use
* @param address IP address to which to send bytes
* @param port UDP port to which to send bytes
*/
def sendString(data: String,
encoding: String,
address: IPAddress,
port: Int): Unit = {
UDPDatagramSocket.sendString(data, encoding, address, port, false)
}
/** Utility method for sending a non-broadcast UDP packet consisting of
* string data. The string data is encoded in UTF-8 before being sent.
* This method is equivalent to the following code snippet:
*
* {{{
* // Bind to a local (source) UDP port.
* val socket = UDPDatagramSocket.bind()
* val address = IPAddress( /* details omitted */ )
* val port: Int = ...
*
* // Send the string to the specified destination address and UDP port.
* socket.send(string, address, port)
*
* // Close the socket
* socket.close()
* }}}
*
* @param data String data to send
* @param address IP address to which to send bytes
* @param port UDP port to which to send bytes
*/
def sendString(data: String, address: IPAddress, port: Int): Unit =
UDPDatagramSocket.sendString(data, "UTF-8", address, port, false)
/** Utility method for sending a UDP packet consisting string data. The
* string data is encoded in UTF-8 before being sent. This method is
* equivalent to the following code snippet:
*
* {{{
* // Bind to a local (source) UDP port.
* val socket = UDPDatagramSocket()
* val address = IPAddress( /* details omitted */ )
* val port: Int = ...
* val broadcast: Boolean = ...
*
* // Set (or clear) the broadcast flag.
* socket.broadcast = broadcast
*
* // Send the string to the specified destination address and UDP port.
* socket.send(string, address, port)
*
* // Close the socket
* socket.close()
* }}}
*
* @param data String data to send
* @param address IP address to which to send bytes
* @param port UDP port to which to send bytes
* @param broadcast Whether to enable broadcast or not.
*/
def sendString(data: String,
address: IPAddress,
port: Int,
broadcast: Boolean): Unit = {
UDPDatagramSocket.sendString(data, "UTF-8", address, port, broadcast)
}
/** Utility method for sending a UDP packet consisting string data. The
* string data is encoded the specified encoding before being sent.
* This method is equivalent to the following code snippet:
*
* {{{
* // Bind to a local (source) UDP port.
* val socket = UDPDatagramSocket()
* val address = IPAddress( /* details omitted */ )
* val port: Int = ...
* val encoding: String = ...
* val broadcast: Boolean = ...
*
* // Set (or clear) the broadcast flag.
* socket.broadcast = broadcast
*
* // Send the string to the specified destination address and UDP port.
* socket.send(string, encoding, address, port)
*
* // Close the socket
* socket.close()
* }}}
*
* @param data String data to send
* @param encoding Encoding to use
* @param address IP address to which to send bytes
* @param port UDP port to which to send bytes
* @param broadcast Whether to enable broadcast or not.
*/
def sendString(data: String,
encoding: String,
address: IPAddress,
port: Int,
broadcast: Boolean): Unit = {
// Bind a socket to a local (source) port.
val socket = UDPDatagramSocket()
// Set or clear the broadcast flag.
socket.broadcast = broadcast
// Send the encoded message to the broadcast address and destination
// port.
socket.sendString(data, encoding, address, port)
// Close the socket.
socket.close()
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy