13.5 DatagramChannel
Java 1.4 adds a
DatagramChannel class for use in non-blocking UDP
applications, just as it adds SocketChannel and
ServerSocketChannel for use in non-blocking TCP
applications. Like SocketChannel and
ServerSocketChannel,
DatagramChannel is a subclass of
SelectableChannel that can be registered with a
Selector. This is useful in servers where one
thread can manage communications with multiple different clients.
However, UDP is by its nature much more asynchronous than TCP so the
net effect is smaller. In UDP it's always been the
case that a single datagram socket can process requests from multiple
clients for both input and output. What the
DatagramChannel class adds is the ability to do
this in a non-blocking fashion, so methods return quickly if the
network isn't immediately ready to receive or send
data.
13.5.1 Using DatagramChannel
DatagramChannel is a near-complete alternate
abstraction for UDP I/O. You still need to use the
DatagramSocket class to bind a channel to a port.
However, you do not have to use it thereafter, nor do you ever use
DatagramPacket. Instead, you read and write
ByteBuffers, just as you do with a
SocketChannel.
13.5.1.1 Opening a socket
The
java.nio.channels.DatagramChannel class does not
have any public constructors. Instead, you create a new
DatagramChannel object using the static
open( ) method:
public static DatagramChannel open( ) throws IOExceptionFor example:
DatagramChannel channel = DatagramChannel .open( );This channel is not initially bound to any port. To bind it, you need
to access the channel's peer
DatagramSocket object using the socket() method:
public abstract DatagramSocket socket( )For example, this binds a channel to port 3141:
SocketAddress address = new InetSocketAddress(3141);
DatagramSocket socket = channel.socket( );
socket.bind(address);
13.5.1.2 Connecting
Like
DatagramSocket, a
DatagramChannel can be connected; that is, it can
be configured to only receive datagrams from and send datagrams to
one host. This is accomplished with the connect() method:
public abstract DatagramChannel connect(SocketAddress remote)However, unlike the connect( ) method of
throws IOException
SocketChannel, this method does not actually send
or receive any packets across the network because UDP is a
connectionless protocol. Thus this method returns fairly quickly, and
doesn't block in any meaningful sense.
There's no need here for a finishConnect() or isConnectionPending( ) method.
There is an isConnected() method that returns true if and only if
the DatagramSocket is connected:
public abstract boolean isConnected( )This tells you whether the DatagramChannel is
limited to one host. Unlike SocketChannel, a
DatagramChannel doesn't have to
be connected to transmit or receive data.Finally, there is a disconnect() method that breaks the connection:
public abstract DatagramChannel disconnect( ) throws IOExceptionThis doesn't really close anything because nothing
was really open in the first place. It just allows the channel to
once again send and receive data from multiple hosts.Connected channels may be marginally faster than unconnected channels
in sandbox environments such as applets because the virtual machine
only needs to check whether the connection is allowed on the initial
call to the connect( ) method, not every time a
packet is sent or received. As always, only concern yourself with
this if profiling indicates it is a bottleneck.
13.5.1.3 Receiving
The
receive( ) method reads one datagram packet from
the channel into a ByteBuffer. It returns the
address of the host that sent the packet:
public abstract SocketAddress receive(ByteBuffer dst) throws IOExceptionIf the channel is blocking (the default) this method will not return
until a packet has been read. If the channel is non-blocking, this
method will immediately return null if no packet is available to
read.If the datagram packet has more data than the buffer can hold,
the extra data is thrown away with no notification of the
problem. You do not receive a
BufferOverflowException or anything similar. UDP
is unreliable, after all. This behavior introduces an additional
layer of unreliability into the system. The data can arrive safely
from the network and still be lost inside your own program.Using this method, we can reimplement the discard server to log the
host sending the data as well as the data sent. Example 13-15 demonstrates. It avoids the potential loss of
data by using a buffer that's big enough to hold any
UDP packet and clearing it before it's used again.
Example 13-15. A UDPDiscardServer based on channels
import java.net.*;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class UDPDiscardServerWithChannels {
public final static int DEFAULT_PORT = 9;
public final static int MAX_PACKET_SIZE = 65507;
public static void main(String[] args) {
int port = DEFAULT_PORT;
try {
port = Integer.parseInt(args[0]);
}
catch (Exception ex) {
}
try {
DatagramChannel channel = DatagramChannel.open( );
DatagramSocket socket = channel.socket( );
SocketAddress address = new InetSocketAddress(port);
socket.bind(address);
ByteBuffer buffer = ByteBuffer.allocateDirect(MAX_PACKET_SIZE);
while (true) {
SocketAddress client = channel.receive(buffer);
buffer.flip( );
System.out.print(client + " says ");
while (buffer.hasRemaining( )) System.out.write(buffer.get( ));
System.out.println( );
buffer.clear( );
} // end while
} // end try
catch (IOException ex) {
System.err.println(ex);
} // end catch
} // end main
}
13.5.1.4 Sending
The
send( ) method writes one datagram packet into the
channel from a ByteBuffer to the address specified
as the second argument:
public abstract int send(ByteBuffer src, SocketAddress target) throwsThe source ByteBuffer can be reused if you want to
IOException
send the same data to multiple clients. Just don't
forget to rewind it first.The send( ) method returns the number of bytes
written. This will either be the number of bytes remaining in the
output buffer or zero. It is zero if there's not
enough room in the network interface's output buffer
for the amount of data you're trying to send.
Don't overstuff the buffer. If you put more data in
the buffer than the network interface can handle, it will never send
anything. This method will not fragment the data into multiple
packets. It writes everything or nothing.Example 13-16 demonstrates with a simple echo server
based on channels. The receive( ) method reads a
packet, much as it did in Example 13-15. However, this
time, rather than logging the packet on
System.out, it returns the same data to the client
that sent it.
Example 13-16. A UDPEchoServer based on channels
import java.net.*;This program is blocking and synchronous. This is much less of a
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class UDPEchoServerWithChannels {
public final static int DEFAULT_PORT = 7;
public final static int MAX_PACKET_SIZE = 65507;
public static void main(String[] args) {
int port = DEFAULT_PORT;
try {
port = Integer.parseInt(args[0]);
}
catch (Exception ex) {
}
try {
DatagramChannel channel = DatagramChannel.open( );
DatagramSocket socket = channel.socket( );
SocketAddress address = new InetSocketAddress(port);
socket.bind(address);
ByteBuffer buffer = ByteBuffer.allocateDirect(MAX_PACKET_SIZE);
while (true) {
SocketAddress client = channel.receive(buffer);
buffer.flip( );
channel.send(buffer, client);
buffer.clear( );
} // end while
} // end try
catch (IOException ex) {
System.err.println(ex);
} // end catch
} // end main
}
problem for UDP-based protocols than for TCP protocols. The
unreliable, packet-based, connectionless nature of UDP means that the
server at most has to wait for the local buffer to clear. It does not
have to and does not wait for the client to be ready to receive data.
There's much less opportunity for one client to get
held up behind a slower client.
13.5.1.5 Reading
Besides the special purpose
receive( ) method,
DatagramChannel has the usual
three read( ) methods:
public abstract int read(ByteBuffer dst) throws IOExceptionHowever, these methods can only be used on connected channels. That
public final long read(ByteBuffer[] dsts)throws IOException
public final long read(ByteBuffer[] dsts, int offset, int length)
throws IOException
is, before invoking one of these methods, you must invoke
connect( ) to glue the channel to a particular
remote host. This makes them more suitable for use with clients that
know who they'll be talking to than for servers that
must accept input from multiple hosts at the same time that are
normally not known prior to the arrival of the first packet.Each of these three methods only reads a single datagram packet from
the network. As much data from that datagram as possible is stored in
the argument ByteBuffer(s). Each method returns
the number of bytes read or -1 if the channel has been closed. This
method may return 0 for any of several reasons, including:The channel is non-blocking and no packet was ready.A datagram packet contained no data.The buffer is full.
As with the receive( ) method, if the datagram
packet has more data than the ByteBuffer(s) can
hold, the extra data is thrown away with no notification of
the problem. You do not receive a
BufferOverflowException or anything similar.
13.5.1.6 Writing
Naturally,
DatagramChannel has the three
write methods common to all writable,
scattering channels, which can be used instead of the send() method:
public abstract int write(ByteBuffer src) throws IOExceptionHowever, these methods can only be used on connected channels;
public final long write(ByteBuffer[] dsts)throws IOException
public final long write(ByteBuffer[] dsts, int offset, int length)
throws IOException
otherwise they don't know where to send the packet.
Each of these methods sends a single datagram packet over the
connection. None of these methods are guaranteed to write the
complete contents of the buffer(s). However, the cursor-based nature
of buffers enables you to easily call this method again and again
until the buffer is fully drained and the data has been completely
sent, possibly using multiple datagram packets. For example:
while (buffer.hasRemaining( ) && channel.write(buffer) != -1) ;We can use the read and write methods to implement a simple UDP echo
client. On the client side, it's easy to connect
before sending. Because packets may be lost in transit (always
remember UDP is unreliable), we don't want to tie up
the sending while waiting to receive a packet. Thus, we can take
advantage of selectors and non-blocking I/O. These work for UDP
pretty much exactly like they worked for TCP in Chapter 12. This time, though, rather than sending
text data, let's send one hundred ints from 0 to 99.
We'll print out the values returned so it will be
easy to figure out if any packets are being lost. Example 13-17 demonstrates.
Example 13-17. A UDP echo client based on channels
import java.net.*;There is one
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;
public class UDPEchoClientWithChannels {
public final static int DEFAULT_PORT = 7;
private final static int LIMIT = 100;
public static void main(String[] args) {
int port = DEFAULT_PORT;
try {
port = Integer.parseInt(args[1]);
}
catch (Exception ex) {
}
SocketAddress remote;
try {
remote = new InetSocketAddress(args[0], port);
}
catch (Exception ex) {
System.err.println("Usage: java UDPEchoClientWithChannels host [port]");
return;
}
try {
DatagramChannel channel = DatagramChannel.open( );
channel.configureBlocking(false);
channel.connect(remote);
Selector selector = Selector.open( );
channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
ByteBuffer buffer = ByteBuffer.allocate(4);
int n = 0;
int numbersRead = 0;
while (true) {
// wait one minute for a connection
selector.select(60000);
Set readyKeys = selector.selectedKeys( );
if (readyKeys.isEmpty( ) && n == LIMIT) {
// All packets have been written and it doesn't look like any
// more are will arrive from the network
break;
}
else {
Iterator iterator = readyKeys.iterator( );
while (iterator.hasNext( )) {
SelectionKey key = (SelectionKey) iterator.next( );
iterator.remove( );
if (key.isReadable( )) {
buffer.clear( );
channel.read(buffer);
buffer.flip( );
int echo = buffer.getInt( );
System.out.println("Read: " + echo);
numbersRead++;
}
if (key.isWritable( )) {
buffer.clear( );
buffer.putInt(n);
buffer.flip( );
channel.write(buffer);
System.out.println("Wrote: " + n);
n++;
if (n == LIMIT) {
// All packets have been written; switch to read-only mode
key.interestOps(SelectionKey.OP_READ);
} // end if
} // end while
} // end else
} // end while
} // end while
System.out.println("Echoed " + numbersRead + " out of " + LIMIT +
" sent");
System.out.println("Success rate: " + 100.0 * numbersRead / LIMIT +
"%");
} // end try
catch (IOException ex) {
System.err.println(ex);
} // end catch
} // end main
}
major difference between selecting TCP channels and selecting
datagram channels. Because datagram channels are truly connectionless
(despite the connect( ) method), you need to
notice when the data transfer is complete and shut down. In this
example, we assume the data is finished when all packets have been
sent and one minute has passed since the last packet was received.
Any expected packets that have not been received by this point are
assumed to be lost in the ether.A typical run produced output like this:
Wrote: 0Connecting to a remote server a couple of miles and seven hops away
Read: 0
Wrote: 1
Wrote: 2
Read: 1
Wrote: 3
Read: 2
Wrote: 4
Wrote: 5
Wrote: 6
Wrote: 7
Wrote: 8
Wrote: 9
Wrote: 10
Wrote: 11
Wrote: 12
Wrote: 13
Wrote: 14
Wrote: 15
Wrote: 16
Wrote: 17
Wrote: 18
Wrote: 19
Wrote: 20
Wrote: 21
Wrote: 22
Read: 3
Wrote: 23
...
Wrote: 97
Read: 72
Wrote: 98
Read: 73
Wrote: 99
Read: 75
Read: 76
...
Read: 97
Read: 98
Read: 99
Echoed 92 out of 100 sent
Success rate: 92.0%
(according to traceroute), I saw between 90% and 98% of the packets
make the round trip.
13.5.1.7 Closing
Just as with regular datagram
sockets, a channel should be closed when you're done
with it to free up the port and any other resources it may be using:
public void close( ) throws IOExceptionClosing an already closed channel has no effect. Attempting to write
data to or read data from a closed channel throws an exception. If
you're uncertain whether a channel has been closed,
check with isOpen( ):
public boolean isOpen( )This returns false if the channel is closed,
true if it's open.
• Table of Contents• Index• Reviews• Reader Reviews• Errata• AcademicJava Network Programming, 3rd EditionBy
Elliotte Rusty Harold Publisher: O'ReillyPub Date: October 2004ISBN: 0-596-00721-3Pages: 706
Thoroughly revised to cover all the 100+ significant updates
to Java Developers Kit (JDK) 1.5, Java Network
Programming is a complete introduction to
developing network programs (both applets and applications)
using Java, covering everything from networking fundamentals
to remote method invocation (RMI). It includes chapters on
TCP and UDP sockets, multicasting protocol and content
handlers, servlets, and the new I/O API. This is the
essential resource for any serious Java developer.