14.2 Working with Multicast Sockets

     

Enough theory. In Java, you multicast data using the java.net.MulticastSocket class, a subclass of java.net.DatagramSocket :

 public class MulticastSocket extends DatagramSocket 

As you would expect, MulticastSocket 's behavior is very similar to DatagramSocket 's: you put your data in DatagramPacket objects that you send and receive with the MulticastSocket . Therefore, I won't repeat the basics; this discussion assumes that you already know how to work with datagrams. If you're jumping around in this book rather than reading it cover to cover, now might be a good time to go back and read Chapter 13 on UDP.

To receive data that is being multicast from a remote site, first create a MulticastSocket with the MulticastSocket( ) constructor. Next, join a multicast group using the MulticastSocket 's joinGroup( ) method. This signals the routers in the path between you 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, leave the multicast group by invoking the socket's leaveGroup() method. You can then close the socket 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 the packet, and pass it to the send( ) method. The one difference is that you must explicitly specify the packet's TTL value.

There is one caveat to all this: multicast sockets are a security hole big enough to drive a small truck through. Consequently, untrusted code running under the control of a SecurityManager is not allowed to do anything involving multicast sockets. Remotely loaded code is normally allowed to send datagrams to or receive datagrams from the host it was downloaded from. However, multicast sockets don't allow this sort of restriction to be placed on the packets they send or receive. Once you send data to a multicast socket, you have very limited and unreliable control over which hosts receive that data. Consequently, most environments that execute remote code take the conservative approach of disallowing all multicasting.

14.2.1 The Constructors

The constructors are simple. Each one calls the equivalent constructor in the DatagramSocket superclass.

14.2.1.1 public MulticastSocket( ) throws SocketException

This constructor creates a socket that is bound to an anonymous port (i.e., an unused port assigned by the system). It is useful for clients (i.e., programs that initiate a data transfer) because they don't need to use a well-known port: the recipient replies to the port contained in the packet. If you need to know the port number, look it up with the getLocalPort( ) method inherited from DatagramSocket . This constructor throws a SocketException if the Socket can't be created. For example:

 try {   MulticastSocket ms = new MulticastSocket( );   // send some datagrams... } catch (SocketException se) {   System.err.println(se); } 

14.2.1.2 public MulticastSocket(int port) throws SocketException

This constructor creates a socket that receives datagrams on a well-known port. The port argument specifies the port on which this socket listens for datagrams. As with regular TCP and UDP unicast sockets, on a Unix system a program needs to be run with root privileges in order to create a MulticastSocket on a port numbered from 1 to 1,023.

This constructor throws a SocketException if the Socket can't be created. A Socket can't be created if you don't have sufficient privileges to bind to the port or if the port you're trying to bind to is already in use. Note that since a multicast socket is a datagram socket as far as the operating system is concerned , a MulticastSocket cannot occupy a port already occupied by a DatagramSocket , and vice versa. For example, this code fragment opens a multicast socket on port 4,000:

 try {   MulticastSocket ms = new MulticastSocket(4000);   // receive incoming datagrams... } catch (SocketException ex) {   System.err.println(ex); } 

14.2.1.3 public MulticastSocket(SocketAddress bindAddress) throws IOException // Java 1.4

Starting in Java 1.4, you can create a MulticastSocket using a SocketAddress object. If the SocketAddress is bound to a port, then this is pretty much the same as the previous constructor. For example, this code fragment also opens a MulticastSocket on port 4000 that listens on all network interfaces and addresses:

 try {   SocketAddress address = new InetSocketAddress(4000);   MulticastSocket ms = new MulticastSocket(address);   // receive incoming datagrams... } catch (SocketException ex) {   System.err.println(ex); } 

However, the SocketAddress can also be bound to a specific network interface on the local host, rather than listening on all network interfaces. For example, this code fragment also opens a MulticastSocket on port 4000 that only listens to packets arriving on 192.168.254.32:

 try {   SocketAddress address = new InetSocketAddress("192.168.254.32", 4000);   MulticastSocket ms = new MulticastSocket(address);   // receive incoming datagrams... } catch (SocketException ex) {   System.err.println(ex); } 

Finally, you can pass null to this constructor to create an unbound socket, which would later be connected with the bind() method. This is useful when setting socket options that can only be set before the socket is bound. For example, this code fragment creates a multicast socket with SO_REUSEADDR disabled (that option is normally enabled by default for multicast sockets):

 try {   MulticastSocket ms = new MulticastSocket(null);   ms.setReuseAddress(false);   SocketAddress address = new InetSocketAddress(4000);   ms.bind(address);   // receive incoming datagrams... } catch (SocketException ex) {   System.err.println(ex); } 

14.2.2 Communicating with a Multicast Group

Once a MulticastSocket has been created, it can perform four key operations:

  1. Join a multicast group.

  2. Send data to the members of the group.

  3. Receive data from the group.

  4. Leave the multicast group.

The MulticastSocket class has methods for operations 1, 2, and 4. No new method is required to receive data. The receive( ) method of the superclass, DatagramSocket , suffices for this task. You can perform these operations in any order, with the exception that you must join a group before you can receive data from it (or, for that matter, leave it). You do not need to join a group to send data to it, and the sending and receiving of data may be freely interwoven.

14.2.2.1 public void joinGroup(InetAddress address) throws IOException

To receive data from a MulticastSocket , you must first join a multicast group. To join a group, pass an InetAddress object for the multicast group to the joinGroup( ) method. If you successfully join the group, you'll receive any datagrams intended for that group. Once you've joined a multicast group, you receive datagrams exactly as you receive unicast datagrams, as shown in the previous chapter. That is, you set up a DatagramPacket as a buffer and pass it into this socket's receive( ) method. For example:

 try {   MulticastSocket ms = new MulticastSocket(4000);   InetAddress ia = InetAddress.getByName("224.2.2.2");   ms.joinGroup(ia);   byte[] buffer = new byte[8192];   while (true) {     DatagramPacket dp = new DatagramPacket(buffer, buffer.length);     ms.receive(dp);     String s = new String(dp.getData( ), "8859_1");     System.out.println(s);   } } catch (IOException ex) {   System.err.println(ex); } 

If the address that you try to join is not a multicast address (that is, it is not between 224.0.0.0 and 239.255.255.255), the joinGroup( ) method throws an IOException .

A single MulticastSocket can join multiple multicast groups. Information about membership in multicast groups is stored in multicast routers, not in the object. In this case, you'd use the address stored in the incoming datagram to determine which address a packet was intended for.

Multiple multicast sockets on the same machine and even in the same Java program can all join the same group. If so, they'll all receive all data addressed to that group that arrives at the local host.

14.2.2.2 public void joinGroup(SocketAddress address, NetworkInterface interface) throws IOException // Java 1.4

Java 1.4 adds this overloaded variant of joinGroup() that allows you to join a multicast group only on a specified local network interface. A proxy server or firewall might use this to specify that it will accept multicast data from the interface connected to the LAN, but not the interface connected to the global Internet, for instance.

For example, this code fragment attempts to join the group with IP address 224.2.2.2 on the network interface named "eth0", if such an interface exists. If no such interface exists, then it joins on all available network interfaces:

 MulticastSocket ms = new MulticastSocket(4000); SocketAddress group = new InetSocketAddress("224.2.2.2", 40); NetworkInterface ni = NetworkInterface .getByName("eth0"); if (ni != null) {   ms.joinGroup(group, ni); } else {   ms.joinGroup(group); } 

Other than the extra argument specifying the network interface to listen from, this behaves pretty much like the single argument joinGroup( ) method. For instance, passing a SocketAddress object that does not represent a multicast group as the first argument throws an IOException .

14.2.2.3 public void leaveGroup(InetAddress address) throws IOException

The leaveGroup( ) method signals that you no longer want to receive datagrams from the specified multicast group. A signal is sent to the appropriate multicast router, telling it to stop sending you datagrams. If the address you try to leave is not a multicast address (that is, if it is not between 224.0.0.0 and 239.255.255.255), the method throws an IOException . However, no exception occurs if you leave a multicast group you never joined.

14.2.2.4 public void leaveGroup(SocketAddress multicastAddress, NetworkInterface interface) throws IOException // Java 1.4

Java 1.4 also allows you to specify that you no longer want to receive datagrams on one particular network interface. Perhaps you do wish to continue receiving datagrams on other network interfaces. For instance, you could join on all interfaces, and then leave just one. To be honest, this is a bit of a stretch. This method was probably included mostly for symmetry with joinGroup( ) .

14.2.2.5 public void send(DatagramPacket packet, byte ttl) throws IOException

Sending data with a MulticastSocket is similar to sending data with a DatagramSocket . Stuff your data into a DatagramPacket object and send it off using the send( ) method inherited from DatagramSocket :

 public void send(DatagramPacket p) throws IOException 

The data is sent to every host that belongs to the multicast group to which the packet is addressed. For example:

 try {   InetAddress ia = InetAddress.getByName("experiment.mcast.net");   byte[] data = "Here's some multicast data\r\n".getBytes( );   int port = 4000;   DatagramPacket dp = new DatagramPacket(data, data.length, ia, port);     MulticastSocket ms = new MulticastSocket( );   ms.send(dp); } catch (IOException ex) {   System.err.println(ex); } 

However, the MulticastSocket class adds an overloaded variant of the send( ) method that lets you provide a value for the Time-To-Live field ttl . By default, the send( ) method uses a TTL of 1; that is, packets don't travel outside the local subnet. However, you can change this setting for an individual packet by passing an integer from 0 to 255 as the second argument to the send() method. For example:

 DatagramPacket dp = new DatagramPacket(data, data.length, ia, port);     MulticastSocket ms = new MulticastSocket( );   ms.send(dp, 64); 

14.2.2.6 public void setInterface(InetAddress address) throws SocketException

On a multihomed host, the setInterface() method chooses the network interface used for multicast sending and receiving. setInterface( ) throws a SocketException if the InetAddress argument is not the address of a network interface on the local machine. It is unclear why the network interface is immutably set in the constructor for unicast Socket and DatagramSocket objects but is variable and set with a separate method for MulticastSocket objects. To be safe, set the interface immediately after constructing a MulticastSocket and don't change it thereafter. Here's how you might use setInterface( ) :

 MulticastSocket ms; InetAddress ia; try {   ia = InetAddress.getByName("www.ibiblio.org");   ms = new MulticastSocket(2048);   ms.setInterface(ia);   // send and receive data... } catch (UnknownHostException ue) {   System.err.println(ue); } catch (SocketException se) {   System.err.println(se); } 

14.2.2.7 public InetAddress getInterface( ) throws SocketException

If you need to know the address of the interface the socket is bound to, call getInterface() . It isn't clear why this method would throw an exception; in any case, you must be prepared for it. For example:

 try {   MulticastSocket ms = new MulticastSocket(2048);   InetAddress ia = ms.getInterface( ); } catch (SocketException se) {   System.err.println(ue); } 

14.2.2.8 public void setNetworkInterface(NetworkInterface interface) throws SocketException // Java 1.4

The setNetworkInterface() method serves the same purpose as the setInterface( ) method; that is, it chooses the network interface used for multicast sending and receiving. However, it does so based on the local name of a network interface such as "eth0" (as encapsulated in a NetworkInterface object) rather than on the IP address bound to that network interface (as encapsulated in an InetAddress object). setNetworkInterface() throws a SocketException if the NetworkInterface passed as an argument is not a network interface on the local machine.

14.2.2.9 public NetworkInterface getNetworkInterface( ) throws SocketException // Java 1.4

The getNetworkInterface() method returns a NetworkInterface object representing the network interface on which this MulticastSocket is listening for data. If no network interface has been explicitly set in the constructor or with setNetworkInterface( ) , it returns a placeholder object with the address "0.0.0.0" and the index -1. For example, this code fragment prints the network interface used by a socket:

 NetworkInterface intf = ms.getNetworkInterface( ); System.out.println(intf.getName( )); 

14.2.2.10 public void setTimeToLive(int ttl) throws IOException // Java 1.2

The setTimeToLive() method sets the default TTL value used for packets sent from the socket using the send(Datagrampacket dp) method inherited from DatagramSocket (as opposed to the send(Datagrampacket dp , byte ttl) method in MulticastSocket ). This method is only available in Java 1.2 and later. In Java 1.1, you have to use the setTTL( ) method instead:

 public void setTTL(byte ttl) throws IOException 

The setTTL( ) method is deprecated in Java 2 and later because it only allows TTL values from 1 to 127 rather than the full range from 1 to 255.

14.2.2.11 public int getTimeToLive( ) throws IOException // Java 1.2

The getTimeToLive() method returns the default TTL value of the MulticastSocket . It's not needed very much. This method is also available only in Java 1.2 and later. In Java 1.1, you have to use the getTTL( ) method instead:

 public byte getTTL( ) throws IOException 

The getTTL( ) method is deprecated in Java 1.2 and later because it doesn't properly handle TTLs greater than 127it truncates them to 127. The getTimeToLive( ) method can handle the full range from 1 to 255 without truncation because it returns an int instead of a byte .

14.2.2.12 public void setLoopbackMode(boolean disable) throws SocketException // Java 1.4

Whether or not a host receives the multicast packets it sends is platform-dependentthat is, whether or not they loop back. Passing true to setLoopback() indicates you don't want to receive the packets you send. Passing false indicates you do want to receive the packets you send. However, this is only a hint. Implementations are not required to do as you request.

14.2.2.13 public boolean getLoopbackMode( ) throws SocketException // Java 1.4

Because loopback mode is only a hint that may not be followed on all systems, it's important to check what the loopback mode is if you're both sending and receiving packets. The getLoopbackMode() method returns true if packets are not looped back and false if they are. (This feels backwards to me. I suspect this method was written by a programmer following the ill-advised convention that defaults should always be true.)

If the system is looping packets back and you don't want it to, you'll need to recognize the packets somehow and discard them. If the system is not looping the packets back and you do want it to, store copies of the packets you send and inject them into your internal data structures manually at the same time you send them. You can ask for the behavior you want with setLoopback( ) , but you can't count on it.



Java Network Programming
Java Network Programming, Third Edition
ISBN: 0596007213
EAN: 2147483647
Year: 2003
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net