The connections weve talked about up to now have all been stream connections. However, just as in J2SE, UDP is a notable exception. UDP, whether implemented through standard I/O or the Generic Connection Framework, just isn suited to a stream metaphor. In GCF, UDP is handled through the DatagramConnection and Datagram classes. These take the place of DatagramSocket and DatagramPacket in J2SE. Like the socket protocol, support for UDP and the datagram protocol is optional. Some devices support it; some don .
Datagram URLs take two forms. Client URLs for sending data look like this:
datagram://server.example.com:2546
This indicates a UDP connection to server.example.com on port 2546.
Datagram server URLs for receiving data look like this:
datagram://:2546
This indicates a server listening for incoming UDP datagrams on port 2546. Datagram URLs do not have any path or parameters. In theory, other schemes could be used to support different kinds of datagrams, such as raw IP or USB datagrams. However, Im unaware of any such implementations.
Opening a DatagramConnection is straightforward and works much like opening any other sort of connection:
Connection connection = Connector.open("datagram://rama.poly.edu:13");
This method will throw a ConnectionNotFoundException if the device does not support UDP.
You cannot use Connector.openInputStream( ) and Connector.openOutputStream( ) methods with datagram URLs because these protocols don support streaming. For the same reason, the connection returned by open( ) is neither an input nor an output connection. Instead, it should be cast to DatagramConnection:
DatagramConnection dgramConnection = (DatagramConnection) connection;
The DatagramConnection interface provides methods for creating new datagrams and for sending and receiving datagrams:
public void send(Datagram dgram) throws IOException public void receive(Datagram dgram) throws IOException public Datagram newDatagram(int size) throws IOException public Datagram newDatagram(int size, String address) throws IOException public Datagram newDatagram(byte[] buffer, int size) throws IOException public Datagram newDatagram(byte[] buffer, int size, String address) throws IOException
It also has two methods that return the maximum and expected length of each datagram:
public int getMaximumLength( ) throws IOException public int getNominalLength( ) throws IOException
To send datagrams to a server:
Open a connection with a datagram URL such as datagram://server.example.com:13.
Cast the connection object to DatagramConnection.
Pass the data to send, the length of the data, and the address to newDatagram( ). Check getMaximumLength( ) to make sure you don overflow the datagram.
Pass the resulting Datagram object to the send( ) method.
To receive datagrams sent by a server:
Open a connection with a datagram URL such as datagram://:13.
Cast the connection object to DatagramConnection.
Pass the length of the data to receive to newDatagram( ).
Pass the resulting Datagram object to the receive( ) method. This method blocks until some data is received.
Read the content received out of the Datagram.
In both server and client mode, datagrams are represented by instances of the Datagram interface summarized below. This interface includes methods for putting data in a datagram to send and getting data out of a received datagram. Besides the ones listed here, it has all the methods of DataInput and DataOutput, such as readInt( ), writeInt( ), readChar( ), writeChar( ), and so forth.
public interface Datagram extends DataInput, DataOutput { public String getAddress( ) public byte[] getData( ) public int getLength( ) public int getOffset( ) public void setAddress(String address) throws IOException public void setAddress(Datagram reference) public void setLength(int lenght) public void setData(byte[] buffer, int offset, int length) public void reset( ) }
Example 24-6 is a UDP time client. This needs to function as both a sender and a receiver. First it sends a packet of data to the server at time-a.nist.gov on port 37. (The contents of this packet don matter.) The server responds with the current time represented as a 4-byte unsigned big-endian integer. Of course, since UDP is unreliable, theres no guarantee the server response will arrive. Consequently, I set a timer that grabs the time from the local clock and shuts down the MIDlet if no response is received within 60 seconds.
import java.io.IOException; import java.util.*; import javax.microedition.io.*; import javax.microedition.lcdui.*; import javax.microedition.midlet.*; public class TimeClient extends MIDlet { private Form form; private final String server = "datagram://time-a.nist.gov:37"; public TimeClient( ) { form = new Form("TimeClient"); form.append("The time is "); Display.getDisplay(this).setCurrent(form); } protected void startApp( ) { Timer timer = new Timer( ); TimerTask task = new TimerTask( ) { public void run( ) { form.append(new Date().toString( )); destroyApp(true); TimeClient.this.notifyDestroyed( ); } }; timer.schedule(task, 60000); // 60 seconds from now byte[] ping = {(byte) 50}; // any byte will do DatagramConnection connection = null; try { connection = (DatagramConnection) Connector.open(server); Datagram dgram = connection.newDatagram(ping, ping.length); Datagram response = connection.newDatagram(4); connection.send(dgram); connection.receive(response); byte[] result = response.getData( ); if (result.length != 4) { form.append("Unrecognized response format"); return; } long differenceBetweenEpochs = 2208988800L; long secondsSince1900 = 0; for (int i = 0; i < 4; i++) { secondsSince1900 = (secondsSince1900 << 8) | (result[i] & 0x000000FF); } long secondsSince1970 = secondsSince1900 - differenceBetweenEpochs; long msSince1970 = secondsSince1970 * 1000; Date time = new Date(msSince1970); form.append(time.toString( ) + " "); } catch (IOException ex) { Alert alert = new Alert("UDP Error"); alert.setTimeout(Alert.FOREVER); alert.setString(ex.getMessage( )); Display.getDisplay(this).setCurrent(alert, form); } finally { timer.cancel( ); try { if (connection != null) connection.close( ); } catch (IOException ex) { } } } protected void pauseApp( ) {} protected void destroyApp(boolean unconditional) {} } |
If you have trouble getting this program to show the time, make sure your firewall is not blocking UDP traffic or try using a time server on your local subnet instead.
A server is no harder to implement. The primary difference is that it simply waits for incoming packets and then responds to each one immediately. It doesn need to worry about timeouts. Example 24-7 demonstrates.
|
import java.io.IOException; import java.util.Date; import javax.microedition.io.*; import javax.microedition.lcdui.Alert; import javax.microedition.lcdui.Display; import javax.microedition.midlet.*; public class TimeServer extends MIDlet { protected void startApp( ) { DatagramConnection connection; try { connection = (DatagramConnection) Connector.open("datagram://:37"); Datagram incoming = connection.newDatagram(128); Datagram response = connection.newDatagram(4); while (true) { try { connection.receive(incoming); response.reset( ); response.setAddress(incoming); response.setData(getTime( ), 0, 4); connection.send(response); incoming.reset( ); } catch (IOException ex) { // As long as its just an error on this one connection // we can ignore it } } } catch (IOException ex) { // If we can open the channel, put up an Alert Alert alert = new Alert("UDP Error"); alert.setTimeout(Alert.FOREVER); alert.setString("Could not connect to port 37. Needs root privileges?"); Display.getDisplay(this).setCurrent(alert); } } private byte[] getTime( ) { byte[] result = new byte[4]; Date now = new Date( ); // The time protocol uses an unsigned 4-byte int, so we have // to do all the arithmetic with longs and then extract the // four low-order bytes long secondsSince1970 = now.getTime( )/1000; long differenceBetweenEpochs = 2208988800L; long secondsSince1900 = differenceBetweenEpochs + secondsSince1970; result[0] = (byte) ((secondsSince1900 & 0xFF000000) >>> 24); result[1] = (byte) ((secondsSince1900 & 0xFF0000) >>> 16); result[2] = (byte) ((secondsSince1900 & 0xFF00) >>> 8); result[3] = (byte) (secondsSince1900 & 0xFF); return result; } protected void pauseApp( ) {} protected void destroyApp(boolean unconditional) {} } |
This example is perhaps not as artificial as it might seem at first glance. I could easily see adding a time server to a digital clock or a smart appliance that includes a clock, such as a microwave. It could then provide time services to other devices on the LAN. This would be a very nice use for J2ME.