To send
or receive a DatagramPacket
, you need to open a
datagram socket. In Java, a datagram socket is created and accessed
through the DatagramSocket
class:
public class DatagramSocket extends Object
All datagram sockets are bound to a local port, on which they listen
for incoming data and which they place in the header of outgoing
datagrams. If you’re writing a client, you don’t care
what the local port is, so you call a constructor that lets the
system assign an unused port (an anonymous port). This port number is
placed in any outgoing datagrams and will be used by the server to
address any response datagrams. If you’re writing a server,
clients need to know on which port the server is listening for
incoming datagrams; therefore, when a server constructs a
DatagramSocket
, it must specify the local port on
which it will listen. However, the sockets used by clients and
servers are otherwise identical: they differ only in whether they use
an anonymous (system-assigned) or a well-known port. There’s no
distinction between client sockets and server sockets, as there is
with TCP; there is no such thing as a
DatagramServerSocket
.
The DatagramSocket
class has three constructors that are used in different situations,
much like the DatagramPacket
class. The first
constructor opens a datagram socket on an anonymous local port. The
second constructor opens a datagram socket on a well-known local port
that listens to all local network interfaces. The third constructor
opens a datagram socket on a well-known local port on a specific
network interface. All three constructors deal only with the local
address and port. The remote address and port are stored in the
DatagramPacket
, not the
DatagramSocket
. Indeed, one
DatagramSocket
can send and receive datagrams from
multiple remote hosts and ports.
This constructor creates a socket that is bound to an anonymous port. For example:
try { DatagramSocket client = new DatagramSocket( ); // send packets... } catch (SocketException e) { System.err.println(e); }
You would use this constructor in a client that initiates a
conversation with a server. In this scenario, you don’t care
what port you are using, because the server will send its response to
the port from which the datagram originated. Letting the system
assign a port means that you don’t have to worry about finding
an unused port. If for some reason you need to know the local port,
you can find out with the getLocalPort( )
method
described later in this chapter.
The same socket may be used to receive the datagrams that a server
sends back to it. A SocketException
is thrown if
the socket can’t be created. It’s unusual for this
constructor to throw an exception; it’s hard to imagine
situations in which the socket could not be opened, since the system
gets to choose the local port.
This constructor creates a socket that listens for incoming datagrams
on a specific port, specified by the port
argument. You would use this constructor to write a server that has
to listen on a well-known port; if servers listened on anonymous
ports, clients would not be able to contact them. A
SocketException
is thrown if the socket
can’t be created. There are two common reasons for the
constructor to fail: the specified port is already occupied, or you
are trying to connect to a port below 1,024 and you don’t have
sufficient privileges (i.e., you are not root on a Unix system; for
better or worse, other platforms allow anyone to connect to
low-numbered ports).
TCP ports and UDP ports are not related. Two unrelated servers or
clients can use the same port number if one uses UDP and the other
uses TCP. Example 13.2 is a port scanner that looks
for UDP ports in use on the local host. It decides that the port is
in use if the DatagramSocket
constructor throws an
exception. As written, it looks at ports from 1,024 up to avoid
Unix’s requirement that it run as root to bind to ports below
1,024. You can easily extend it to check ports below 1,024, however,
if you have root access or are running it on Windows or a
Mac.
Example 13-2. Look for Local UDP Ports
import java.net.*; public class UDPPortScanner { public static void main(String[] args) { for (int port = 1024; port <= 65535; port++) { try { // the next line will fail and drop into the catch block if // there is already a server running on port i DatagramSocket server = new DatagramSocket(port); server.close( ); } catch (SocketException e) { System.out.println("There is a server on port " + port + "."); } // end try } // end for } }
The speed at which UDPPortScanner
runs depends
strongly on the speed of your machine and its UDP implementation.
I’ve clocked Example 13.2 at as little as two
minutes on a moderately powered SPARCstation and as long as an hour
on a PowerBook 5300. Here are the results from one SPARCstation:
% java UDPPortScanner
There is a server on port 2049.
There is a server on port 4045.
There is a server on port 32771.
There is a server on port 32773.
There is a server on port 32778.
There is a server on port 32779.
There is a server on port 32787.
There is a server on port 32788.
There is a server on port 32790.
There is a server on port 32793.
There is a server on port 32797.
There is a server on port 32804.
There is a server on port 32812.
There is a server on port 32822.
There is a server on port 32834.
There is a server on port 32852.
There is a server on port 32857.
There is a server on port 32858.
There is a server on port 32860.
There is a server on port 32871.
There is a server on port 32877.
There is a server on port 33943.
There is a server on port 34955.
There is a server on port 35977.
There is a server on port 35982.
There is a server on port 36000.
There is a server on port 36159.
There is a server on port 36259.
The high-numbered UDP ports in the 30,000 range are Remote Procedure Call (RPC) services. Aside from RPC, some common protocols that use UDP are NFS, TFTP, and FSP.
It’s much harder to scan UDP ports on a remote system than to scan for remote TCP ports. Whereas there’s always some indication that your TCP packet has been received by a listening port regardless of application layer protocol, UDP provides no such guarantees. To determine that a UDP server is listening, you have to send it a packet it will recognize and respond to.
This constructor is primarily used on multihomed hosts; it creates a
socket that listens for incoming datagrams on a specific port and
network interface. The port
argument is the port
on which this socket listens for datagrams. As with TCP sockets, you
need to be root on a Unix system to create a
DatagramSocket
on a port below 1,024. The
address
argument is an
InetAddress
object matching one of the
host’s network addresses. A SocketException
is thrown if the socket can’t be created. There are three
common reasons for this constructor to fail: the specified port is
already occupied, you are trying to connect to a port below 1,024 and
you don’t have sufficient privileges (i.e., you are not root on
a Unix system), or address
is not the address of
one of the system’s network interfaces.
The
primary task of the DatagramSocket
class is to
send and receive UDP datagrams. One socket can both send and receive.
Indeed, it can send and receive to and from multiple hosts at the
same time.
Once a DatagramPacket
is created and a
DatagramSocket
is constructed, you send the packet
by passing it to the socket’s send( )
method. For example, if
theSocket
is a DatagramSocket
object and theOutput
is a
DatagramPacket
object, you send
theOutput
using theSocket
like
this:
theSocket.send(theOutput);
If
there’s a problem sending the data, an
IOException
may be thrown. However, this is less
common with DatagramSocket
than
Socket
or ServerSocket
, since
the unreliable nature of UDP means you won’t get an exception
just because the packet doesn’t arrive at its destination. You
may get an IOException
if you’re trying to
send a larger datagram than your host’s native networking
software supports, but then again you may not. This depends heavily
on the native UDP software in the OS and the native code that
interfaces between this and Java’s
DatagramSocketImpl
class. This method may also
throw a SecurityException
if the
SecurityManager
won’t let you communicate
with the host to which the packet is addressed. This is primarily a
problem for applets.
Example 13.3 is a UDP-based discard client. It reads
lines of user input from System.in
, and sends them
to a discard server, which simply discards all the data. Each line is
stuffed in a DatagramPacket
. Many of the simpler
Internet protocols such as discard have both TCP and UDP
implementations.
Example 13-3. A UDP Discard Client
import java.net.*; import java.io.*; public class UDPDiscardClient { public final static int DEFAULT_PORT = 9; public static void main(String[] args) { String hostname; int port = DEFAULT_PORT; if (args.length > 0) { hostname = args[0]; try { port = Integer.parseInt(args[1]); } catch (Exception e) { } } else { hostname = "localhost"; } try { InetAddress server = InetAddress.getByName(hostname); BufferedReader userInput = new BufferedReader(new InputStreamReader(System.in)); DatagramSocket theSocket = new DatagramSocket( ); while (true) { String theLine = userInput.readLine( ); if (theLine.equals(".")) break; byte[] data = theLine.getBytes( ); DatagramPacket theOutput = new DatagramPacket(data, data.length, server, port); theSocket.send(theOutput); } // end while } // end try catch (UnknownHostException e) { System.err.println(e); } catch (SocketException se) { System.err.println(se); } catch (IOException e) { System.err.println(e); } } // end main }
The UDPDiscardClient
class should look familiar.
It has a single static field, DEFAULT_PORT
, which
is set to the standard port for the discard protocol (port 9), and a
single method, main( )
. The main( )
method reads a hostname from the command-line and
converts that hostname to the InetAddress
object
called server
. A BufferedReader
is chained to System.in
to read user input from
the keyboard. Next, a DatagramSocket
object called
theSocket
is constructed. After creating the
socket, the program enters an infinite while
loop
that reads user input line by line using readLine( )
. We are careful, however, to use only readLine( )
to read data from the console, the one place where it is
guaranteed to work as advertised. Since the discard protocol deals
only with raw bytes, we can ignore character encoding issues.
In the while
loop, each line is converted to a
byte array using the getBytes( )
method, and the
bytes are stuffed in a new DatagramPacket
,
theOutput
. Finally, theOutput
is sent over theSocket
, and the loop continues. If
at any point the user types a period on a line by itself, the program
exits. The DatagramSocket
constructor may throw a
SocketException
, so that needs to be caught.
Because this is a discard client, we don’t need to worry about
data coming back from the server.
This method receives a single UDP datagram from the network and
stores it in the pre-existing DatagramPacket
object dp
. Like the accept( )
method in the ServerSocket
class, this method
blocks the calling thread until a datagram arrives. If your program
does anything besides wait for datagrams, you should call
receive( )
in a separate thread.
The datagram’s buffer should be large enough to hold the data
received. If not, receive( )
places as much data
in the buffer as it can hold; the rest is lost. It may be useful to
remember that the maximum size of the data portion of a UDP datagram
is 65,507 bytes. (That’s the 65,536-byte maximum size of an IP
datagram minus the 20-byte size of the IP header and the 8-byte size
of the UDP header.) Some application protocols that use UDP further
restrict the maximum number of bytes in a packet; for instance, NFS
uses a maximum packet size of 8,192 bytes.
If there’s a problem receiving the data, an
IOException
may be thrown. In practice, this is
rare. Unlike send( )
, this method does not throw a
SecurityException
if an applet receives a datagram
from other than the applet host. However, it will silently discard
all such packets. (This behavior prevents a denial-of-service attack
against applets that receive UDP datagrams.)
Example 13.4 shows a UDP discard server that receives
incoming datagrams. Just for fun, it logs the data in each datagram
to System.out
so that you can see who’s
sending what to your discard server.
Example 13-4. The UDPDiscardServer
import java.net.*; import java.io.*; public class UDPDiscardServer { 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; byte[] buffer = new byte[MAX_PACKET_SIZE]; try { port = Integer.parseInt(args[0]); } catch (Exception e) { } try { DatagramSocket server = new DatagramSocket(port); DatagramPacket packet = new DatagramPacket(buffer, buffer.length); while (true) { try { server.receive(packet); String s = new String(packet.getData(), 0, packet.getLength( )); System.out.println(packet.getAddress( ) + " at port " + packet.getPort( ) + " says " + s); // reset the length for the next packet packet.setLength(buffer.length); } catch (IOException e) { System.err.println(e); } } // end while } // end try catch (SocketException se) { System.err.println(se); } // end catch } // end main }
This is a simple class with a single method, main( )
. It reads the port for the server to listen to from the
command line. If the port is not specified on the command-line, it is
set to 9. It then opens a DatagramSocket
on that
port and creates a DatagramPacket
with a
65,507-byte buffer—large enough to receive any possible packet.
Then the server enters an infinite loop that receives packets and
prints the contents and the originating host on the console. A
high-performance discard server would skip this step. As each
datagram is received, the length of packet
is set
to the length of the data in that datagram. Consequently, as the last
step of the loop, the length of the packet is reset to the maximum
possible value. Otherwise, the incoming packets would be limited to
the minimum size of all previous packets. Try running the discard
client on one machine and connecting to the discard server on a
second machine to verify that both these programs work.
Calling a DatagramSocket
object’s
close( )
method frees the port occupied by that
socket. For example:
try { DatagramSocket theServer = new DatagramSocket( ); theServer.close( ); } catch (SocketException e) { System.err.println(e); }
It’s never a bad idea to close a
DatagramSocket
when you’re through with it;
it’s particularly important to close an unneeded socket if your
program will continue to run for a significant amount of time. For
example, the close( )
method was essential in
Example 13.2, UDPPortScanner
: if
this program did not close the sockets it opened, it would tie up
every UDP port on the system for a significant amount of time. On the
other hand, if the program ends as soon as you’re through with
the DatagramSocket
, you don’t need to close
the socket explicitly; the socket is automatically closed on garbage
collection. However, Java won’t run the garbage collector just
because you’ve run out of ports or sockets, unless by lucky
happenstance you run out of memory at the same time. On the gripping
hand, closing unneeded sockets never hurts and is good programming
practice.
A DatagramSocket
’s getLocalPort( )
method returns an
int
that represents the local port on which the
socket is listening. You would use this method if you created a
DatagramSocket
with an anonymous port and want to
find out what port you have been assigned. For example:
try { DatagramSocket ds = new DatagramSocket( ); System.out.println("The socket is using port " + ds.getLocalPort( )); } catch (SocketException e) { }
Unlike TCP sockets, datagram sockets aren’t very picky about whom they’ll talk to. In fact, by default they’ll talk to anyone. But this is often not what you want. For instance, applets are only allowed to send datagrams to and receive datagrams from the applet host. An NFS or FSP client should accept packets only from the server it’s talking to. A networked game should listen to datagrams only from the people playing the game. In Java 1.1, programs must manually check the source addresses and ports of the hosts sending them data to make sure they’re who they should be. However, Java 1.2 adds four methods that let you choose which host you can send datagrams to and receive datagrams from, while rejecting all others’ packets.
The connect( )
method doesn’t really
establish a connection in the TCP sense. However, it does specify
that the DatagramSocket
will send packets to and
receive packets from only the specified remote host on the specified
remote port. Attempts to send packets to a different host or port
will throw an IllegalArgumentException
. Packets
received from a different host or a different port will be discarded
without an exception or other
notification.
A security check is made when the connect( )
method is invoked. If the VM is allowed to send data to that host and
port, then the check passes silently. Otherwise, a
SecurityException
is thrown. However, once the
connection has been made, send( )
and
receive( )
on that
DatagramSocket
no longer make the security checks
they’d normally make.
The disconnect( )
method breaks the
“connection” of a connected
DatagramSocket
so that it can once again send
packets to and receive packets from any host and port.
The only socket option supported for datagram sockets in Java 1.1 is SO_TIMEOUT. Java 1.2 adds SO_SNDBUF and SO_RCVBUF.
SO_TIMEOUT is the amount of time, in milliseconds, that
receive( )
waits for an incoming datagram before
throwing an InterruptedIOException
. Its value must
be non-negative. If SO_TIMEOUT is 0, receive( )
never times out. This value can be changed with the
setSoTimeout( )
method and inspected with the
getSoTimeout( )
method:
public synchronized void setSoTimeout(int timeout) throws SocketException public synchronized int getSoTimeout( ) throws IOException
The default is to never time out, and indeed there are few situations in which you would need to set SO_TIMEOUT. You might need it if you were implementing a secure protocol that required responses to occur within a fixed amount of time. You might also decide that the host you’re communicating with is dead (unreachable or not responding) if you don’t receive a response within a certain amount of time.
The setSoTimeout( )
method sets the SO_TIMEOUT field for a
datagram socket. When the timeout expires, an
InterruptedIOException
is thrown. You should set
this option before you call receive( )
. You cannot change it while receive( )
is waiting for a datagram. The timeout
argument
must be greater than or equal to zero; if it is not,
setSoTimeout( )
throws a
SocketException
. For example:
try { buffer = new byte[2056]; DatagramPacket dp = new DatagramPacket(buffer, buffer.length); DatagramSocket ds = new ServerSocket(2048); ds.setSoTimeout(30000); // block for no more than 30 seconds try { ds.receive(dp); // process the packet... } catch (InterruptedIOException e) { ss.close( ); System.err.println("No connection within 30 seconds"); } catch (SocketException e) { System.err.println(e); } catch (IOException e) { System.err.println("Unexpected IOException: " + e); }
The getSoTimeout( )
method returns the current value of
this DatagramSocket
object’s SO_TIMEOUT
field. For example:
public void printSoTimeout(DatagramSocket ds) { int timeout = ds.getSoTimeOut( ); if (timeout > 0) { System.out.println(ds + " will time out after " + timeout + "milliseconds."); } else if (timeout == 0) { System.out.println(ds + " will never time out."); } else { System.out.println("Something is seriously wrong with " + ds); } }
The SO_RCVBUF option of DatagramSocket
is closely
related to the SO_RCVBUF option of Socket
. It
determines the size of the buffer used for network I/O. Larger
buffers tend to improve performance for reasonably fast (say,
Ethernet-speed) connections because they can store more incoming
datagrams before overflowing. Sufficiently large receive buffers are
even more important for UDP than for TCP, since a UDP datagram that
arrives when the buffer is full will be lost, whereas a TCP datagram
that arrives at a full buffer will eventually be retransmitted.
Furthermore, SO_RCVBUF sets the maximum size of datagram packets that
can be received by the application. Packets that won’t fit in
the receive buffer are silently discarded.
Starting in Java 1.2, DatagramSocket
has methods
to get and set the suggested receive buffer size used for network
input:
public void setReceiveBufferSize(int size) throws SocketException, IllegalArgumentException public int getReceiveBufferSize( ) throws SocketException
The setReceiveBufferSize( )
method suggests a number of bytes to
use for buffering input from this socket. However, the underlying
implementation is free to ignore this suggestion. For instance, many
4.3 BSD-derived systems have a maximum receive buffer size of about
52K and won’t let you set a limit higher than this. Other
systems raise this to about 240K. The details are highly
platform-dependent. Consequently, it’s a good idea to check the
actual size of the receive buffer with getReceiveBufferSize( )
after setting it. The
getReceiveBufferSize( )
method returns the number
of bytes in the buffer used for input from this socket.
Both methods throw a SocketException
if the
underlying socket implementation does not recognize the SO_RCVBUF
option. This might happen on a non-POSIX operating system. The
setReceiveBufferSize( )
method throws an
IllegalArgumentException
if its argument is less
than or equal to zero.
Starting in Java 1.2, DatagramSocket
has methods
to get and set the suggested send buffer size used for network
output:
public void setSendBufferSize(int size) throws SocketException, IllegalArgumentException public int getSendBufferSize( ) throws SocketException
The setSendBufferSize( )
method suggests a number of bytes to
use for buffering output on this socket. Once again, however, the
operating system is free to ignore this suggestion. Consequently,
you’ll want to check the result of setSendBufferSize( )
by immediately following it with a call to
getSend BufferSize( )
to find out what the buffer size really
is.
Both methods throw a SocketException
if the
underlying native network software doesn’t understand the
SO_SNDBUF
option. The setSendBufferSize( )
method also throws an
IllegalArgumentException
if its argument is less
than or equal to zero.
3.145.111.125