The DatagramSocket Class

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 Constructors

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.

public DatagramSocket( ) throws SocketException

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.

public DatagramSocket(int port) throws SocketException

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.

public DatagramSocket(int port, InetAddress address) throws SocketException

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.

Sending and Receiving Datagrams

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.

public void send(DatagramPacket dp) throws IOException

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.

public void receive(DatagramPacket dp) throws IOException

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.

public void close( )

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.

public int getLocalPort( )

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) {
}
                  

Managing Connections

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.

public void connect(InetAddress host, int port) // Java 1.2

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.

public void disconnect( ) // Java 1.2

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.

public int getPort( ) // Java 1.2

If and only if a DatagramSocket is connected, the getPort( ) method returns the remote port to which it is connected. Otherwise, it returns -1.

public InetAddress getInetAddress( ) // Java 1.2

If and only if a DatagramSocket is connected, the getInetAddress( ) method returns the address of the remote host to which it is connected. Otherwise, it returns null.

Socket Options

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

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);
  }

}

SO_RCVBUF

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.

SO_SNDBUF

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.111.125