Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
ENIC4023 - Networked Embedded Systems Development UDP and IGMP Lab Note: the examples in this lab require to be run on networked systems with IP addresses. Standalone PCs and some laptop systems without permanent IP addresses are not suitable. UDP Overview UDP (User Datagram Protocol) is a datagram-centric transport protocol. It differs from TCP in that it is connectionless. The implementation of UDP communication in Java is thus quite different from that of TCP. Java's implementation of UDP is split into two classes: DatagramPacket and DatagramSocket. The DatagramPacket class stuffs byte of data into UDP packets called datagrams and lets you unstuff datagrams that you receive. A DatagramSocket sends as well as receives UDP datagrams. To send data, you put it in a DatagramPacket and send the packet using a DatagramSocket. To receive data, you receive a DatagramPacket object from a DatagramSocket and then read the contents of the packet. The sockets are very simple: In UDP everything about a datagram, including the address to which it is directed, is included in the packet itself; the socket needs to know only the local ports on which to listen or send. The socket isn't dedicated to a single connection, as it is in TCP. UDP doesn't have any concept of a connection between two hosts; it only knows about individual datagrams. Hence UDP sockets work with individual datagram packets whereas TCP sockets treat a network connection as a stream. The Java DatagramPacket Class This class represents a datagram packet. Datagram packets are used to implement a connectionless packet delivery service. Each message is routed from one machine to another based solely on information contained within that packet. Multiple packets sent from one machine to another might be routed differently, and might arrive in any order. Packet delivery is not guaranteed. The class provides methods to get and set the source or destination address from the IP header, to get and set the source or destination port, to get and set the data, and to get and set the length of the data. The theoretical maximum length for the data is 65,507 bytes but some UDP implementations don't support more than 8,192 bytes, so applications should not normally exceed this. The constructors for the class are unusual in that you use different ones whether you are going to send data to the net of receive data from the net. All the constructors take as arguments a byte array that holds the datagram's data, and the number of bytes in that array to use for the datagram's data. An abridged class diagram for the DatagramPacket class is shown (see Java API docs for full listing): DatagramPacket <<constructors>> + DatagramPacket(byte[] + DatagramPacket(byte[] + DatagramPacket(byte[] + DatagramPacket(byte[] int port) .... buf, buf, buf, buf, <<query>> + InetAddress getAddress() + byte[] getData() + int getLength() + int getOffset() int int int int length) offset, int length) length, InetAddress address, int port) offset, int length, InetAddress address, + int getPort() .... <<update>> + void setAddress(InetAddress iaddr) + void setData(byte[] buf) + void setData(byte[] buf, int offset, int length) + void setLength(int length) + void setPort(int port) .... Using the DatagramPacket class is fairly straightforward. One difficulty that needs to be overcome though, is dealing with the byte array that forms the data part of the UDP datagram. Often the data will represent text, so it is useful to have means to convert between Strings and byte arrays. The data is extracted as a byte array from a received DatagramPacket using the getData() method. Conversion from a byte array to a String can be achieved using the following String constructor: public String(byte[] bytes, String charsetName) //see javadocs The first argument is the byte array to be converted; the second argument contains the name of the encoding used for this string such as ASCII or ISO-8859-1. Thus given a DatagramPacket dp received from the network, you can convert it to a String with: String s = new String(dp.getData(), "ASCII"); The most convenient way to convert Strings to byte arrays is with the following method of the String class: byte[] getBytes(String CharsetName) //see javadocs Thus given an ASCII String s, you can convert it to a byte array with: byte[] data = s.getBytes("ASCII") This byte array can then be used to construct the DatagramPacket for transmission. The Java DatagramSocket Class To send or receive a DatagramPacket you need to open a datagram socket. All datagram sockets are bound to a local port on which they listen for incoming datagrams and which they place in the header of outgoing datagrams. If writing a client, it doesn't matter what the local ports is, so you call a constructor that lets the system assign an unused port. This port number is placed in any outgoing datagrams and will be used by the server to address any response datagrams. If writing a server, clients need to know on which port the server is listening for incoming datagrams. Thus when a server constructs a DatagramSocket it must specify the local port on which it will listen. Other than this the sockets used by clients and servers are identical. An abridged class diagram for the DatagramSocket class is shown (see Java API docs for full listing): DatagramSocket <<constructors>> + DatagramSocket() + DatagramSocket(int port) .... <<query>> + int getLocalPort() .... <<update>> + void send(DatagramPacket p) + void receive(DatagramPacket p) + void close() .... Example - Port Scanner import java.net.*; public class UDPPortScanner { public static void main(String[] args) { for (int port = 0; port <= 65535; port++) { try { // the next line will fail and drop into the catch block if // there is already a server running on the port DatagramSocket server = new DatagramSocket(port); server.close(); if ((port % 100) == 0) System.out.print("."); } catch (SocketException e) { System.out.print("\nServer on UDP port " + port); } // try } // for } //main } //class The above 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. Unlike with TCP, it is not practical to scan UDP ports on a remote system. With TCP there is always some indication that your TCP packet has been received by a listening port regardless of application layer protocol. With UDP, to determine if a UDP server is listening, you have to send a packet that it will recognise and respond to. Example - Simple UDP Client The following is a simple UDP client application that sends a 1 byte datagram to a UDP server and outputs the textual response from the server (if any) on the screen (simplified version of Harold p436): import java.net.*; import java.io.*; public class UDPPoke { private DatagramSocket ds; private DatagramPacket out; private DatagramPacket in; public UDPPoke(InetAddress host, int port) throws SocketException { out = new DatagramPacket(new byte[1], 1, host, port); in = new DatagramPacket(new byte[8192], 8192); ds = new DatagramSocket(0); ds.connect(host, port); } public String poke() throws IOException { try { ds.send(out); ds.receive(in); // blocks until response is received return(new String(in.getData(), 0, in.getLength(), "ASCII")); } catch (IOException e) { return(""); } //catch } //poke public static void main(String[] args) { InetAddress host; int port = 0; try { host = InetAddress.getByName(args[0]); port = Integer.parseInt(args[1]); if (port < 1 || port > 65535) throw new Exception(); } catch (Exception e) { System.out.println("Usage: java UDPPoke host port"); return; } try { UDPPoke poker = new UDPPoke(host, port); String responce = poker.poke(); System.out.println(responce); } catch (Exception e) { System.err.println(e); } //catch } //main } //class Try the above out on a UDP Daytime server that your lab supervisor will have running for you. Example - UDP Daytime Server (The following two classes have been taken from Harold p440 and p444, both slightly modified) The UDPServer class is an abstract class that can be used to build a variety of UDP servers that respond in different ways to an incoming datagram (without consideration of the data that the incoming datagram contains): import java.net.*; import java.io.*; public abstract class UDPServer extends Thread { protected DatagramSocket ds; public UDPServer(int port) throws SocketException { this.ds = new DatagramSocket(port); } public void run() { byte[] buff = new byte[8192]; while (true) { DatagramPacket in = new DatagramPacket(buff, buff.length); try { ds.receive(in); this.respond(in); } catch (IOException e) { System.err.println(e); } //catch } //while } //run public abstract void respond(DatagramPacket request); } //class To use the UDPServer class, subclasses have to provide an implementation for the respond method. The following UDPDaytimeServer class inherits from the UDPServer class in order to implement a daytime server: import java.net.*; import java.io.*; import java.util.*; public class UDPDaytimeServer extends UDPServer { public UDPDaytimeServer() throws SocketException { super(13); } public void respond(DatagramPacket packet) { try { Date now = new Date(); String response = now.toString() + "\r\n"; byte[] data = response.getBytes("ASCII"); DatagramPacket out = new DatagramPacket(data, data.length, packet.getAddress(), packet.getPort()); ds.send(out); } catch (IOException e) { System.err.println(e); } //catch } //respond public static void main(String[] args) { try { UDPDaytimeServer server = new UDPDaytimeServer(); server.start(); } catch (SocketException e) { System.err.println(e); } //catch } //main } //class The following shows an Ethereal trace of the network activity when a UDPPoke client accesses a UDPDaytimeServer server. As both are running on hosts which are on the same network, the client requires the physical address of the server for packet delivery, hence it issues an ARP broadcast. The server replies with an ARP response. The client then sends a UDP packet to the server on the daytime protocol port (port 13). The source port has been chosen at random from those not used on the client machine, here it is port 1683. The server then responds by sending a single UDP packet with the requested data to the client on that port. Note the simplicity of UDP, there being no acknowledgements. If the response packet got lost, it would be up to the application software on the client to realise this and re-request it. IGMP Overview IP multicasting allows IP datagrams to be sent to explicit hosts on multiple networks simultaneously. IGMP provides a mechanism for IP devices to register with multicast routers, indicating the multicast groups they are interested in participating in. Java's implementation of multicasting is similar to that of UDP. The DatagramPacket is still used to contain the datagrams being sent and received, but instead of the DatagramSocket, the MulticastSocket class is used to do the sending and receiving. The Java MulticastSocket class The MulticastSocket class is a subclass of the DatagramSocket class. To receive data that is being multicast from a remote site, a MulticastSocket is first created with the MulticastSocket() constructor. Next the MulticastSocket needs to join a multicast group using its joinGroup() method. This signals the routers in the path between you (the application) and the server to start sending data your way and tells the local host that it should pass you IP packets addressed to the multicast group. Once you've joined the multicast group, you receive UDP data just as you would with a DatagramSocket. That is, you create a DatagramPacket with a byte array that serves as a buffer for data, and enter a loop in which you receive the data by calling the receive() method inherited from the DatagramSocket class. When you no longer want to receive data, you leave the multicast group by calling the socket's leaveGroup() method. The socket can then be closed with the close() method inherited from DatagramSocket. Sending data to a multicast address is similar to sending UDP data to a unicast address. You do not need to join a multicast group to send data to it. You create a new DatagramPacket, stuff the data and the address of the multicast group into it, and pass it to the send() method. The one difference is that you must explicitly specify the packets TTL value (using the setTimeToLive() method). An abridged class diagram for the MulticastSocket class is shown (see Java API docs for full listing): MulticastSocket <<constructors>> + MulticastSocket() + MulticastSocket(int port) .... <<query>> + int getLocalPort() + int getTimeToLive() .... <<update>> + void joinGroup(InetAddress mcastaddr) + void leaveGroup(InetAddress mcastaddr) + void setTimeToLive(int ttl) + void send(DatagramPacket p) + void receive(DatagramPacket p) + void close() .... Example - Multicast Client (modified version of Harold p470) import java.net.*; import java.io.*; public class MulticastSender { public static void main(String[] args) { InetAddress ia = null; int port = 0; // read the address from the command line try { ia = InetAddress.getByName(args[0]); port = Integer.parseInt(args[1]); } catch (Exception e) { System.out.println("Usage: java MulticastSender mcast_addr port"); System.exit(1); } try { InetAddress host = InetAddress.getLocalHost(); byte[] data = ("Multicast data from: " + host.toString()).getBytes(); DatagramPacket dp = new DatagramPacket(data, data.length, ia, port); MulticastSocket ms = new MulticastSocket(); ms.setTimeToLive(1); ms.send(dp); ms.close(); } catch (SocketException se) { System.err.println(se); } catch (IOException ie) { System.err.println(ie); } } } The above application sends a message to a multicast group and port specified on the command line. The message is in text form and includes the local host name. The data to be sent is put in a DatagramPacket together with the group address and port. A MulticastSocket is opened on an anonymous port and the TTL field is set to 1, ie for local multicasting. The DatagramPacket is sent by the MulticastSocket and the socket is then closed. Note that as this application only sends a packet to a multicast group, there is no need to join and leave the group. Example - Multicast Server (modified version of Harold p467) import java.net.*; import java.io.*; public class MulticastSniffer { public static void main(String[] args) { InetAddress group = null; int port = 0; //read the address from the command line try { group = InetAddress.getByName(args[0]); port = Integer.parseInt(args[1]); } catch (Exception e) { System.out.println("Usage: java MulticastSniffer mcast_addr port"); System.exit(1); } MulticastSocket ms = null; try { ms = new MulticastSocket(port); ms.joinGroup(group); byte[] buffer = new byte[8192]; while (true) { DatagramPacket dp = new DatagramPacket(buffer, buffer.length); ms.receive(dp); String s = new String(dp.getData(), 0, dp.getLength(), "ASCII"); System.out.println(s); } } catch (IOException e) { System.err.println(e); } finally { if (ms != null) { try { ms.leaveGroup(group); ms.close(); } catch (IOException e) {} } } } } The above application sets in a loop listening for datagrams and outputs their contents, interpreted as text, on the console. The multicast group and port that it listens on are specified as command line arguments. The multicast socket is opened for a named port (so that sending applications know which port to send the data to). The joingroup() method must be called to tell the local host to capture packets for that specific group. The local router will be informed but as we are only doing multicasting locally within the lab, this is not relevant. This shows an Ethereal trace of the network activity when the MulticastSender application is run. It has been run on three different hosts, each sends a packet to the multicast group 224.0.2.1 on port 8888. The source ports for each packet is different as anonymous ports have been used (ie the MulticastSocket constructor just chose an unused local port at random). Examining the data part of the first packet shows the ASCII coded message being sent. This shows an IGMP Membership Report packet. It was sent when the MulticastSniffer application was started and the joinGroup() method of the MulticastSocket class was called. Note that the multicast group that this report refers to is 224.0.2.1, and this is the group address to which the report is sent. Nowhere is there any port number specified. The IGMP Membership Report requests that the local multicast router send all traffic addressed to the group without regard to the port. This shows an IGMP Leave Report packet. This was sent when the MulticastSniffer application was closed, ie when the leaveGroup() method of the MulticastSocket class was called. Although the multicast group that this report refers to is 224.0.2.1, the Leave Report is sent to the "allrouters" group address of 224.0.0.2