/* File: AwtMulticastRelayClient.java CVS Info: $Id: AwtMulticastRelayClient.java,v 1.1 1998/02/09 19:00:02 mcgredo Exp $ Compiler: jdk1.3 */ package mil.navy.nps.bridge; import mil.navy.nps.dis.*; import java.applet.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; import netscape.security.*; // to enable netscape to read from random datagram sockets // import mil.navy.nps.awt.*; /** A quick applet that does local relay work for multicast. This is a bit better than a plain command line application. Lots of verbose and silly J++ comments included below. *@version 1.0 *@author Don McGregor http://www.npsnet.org/~mcgredo */ public class AwtMulticastRelayClient extends Applet implements ActionListener, ItemListener { public static final String DEFAULT_SERVER = "devo.cs.nps.navy.mil"; public static final String DEFAULT_SERVER_PORT = "8010"; public static final String DEFAULT_MCAST_ADDRESS = "224.2.181.145"; public static final String DEFAULT_MCAST_PORT = "62040"; public static final int TIME_TO_LIVE = 15; public static final String DEFAULT_LOCAL_UNICAST = "8006"; public static final int MAX_PACKET_SIZE = 1500; public static final boolean DEBUG = true; public static final int SOCKET_TIMEOUT = 15000; // 15 sec timeout protected DatagramPacket replyPacket; boolean receivedFromServer, receivedFromLocalhostOrLAN; // STANDALONE APPLICATION SUPPORT: // m_fStandAlone will be set to true if applet is run standalone public static boolean m_fStandAlone = false; // The server we connect to over unicast protected static TextField mcastRelayServer; protected static TextField mcastRelayServerPort; // Local parameters: the mcast address we transmit on locally, (if mcast), // the port number we transmit on, and a count of how many packets we've // relayed. This is mostly eye candy to convince people it's working. protected static TextField sendAddress; protected static TextField port; // used for both mcast and unicast destinations protected static TextField localUnicastPort; // what port to send out on ON THIS MACHINE, if using unicast protected static TextField packetCount; // In the SocketStyleRegion panel protected static CheckboxGroup socketTypeRadio; // Radio for how to send info--unicast or multicast protected static Checkbox multicastCheckbox; // checkbox selection for multicast protected static Checkbox unicastCheckbox; // Checkbox selection for unicast protected static boolean sending = false; // Buttons for starting & stopping protected static Button startButton; protected static Button stopButton; // Sockets for receiving & sending protected static DatagramSocket unicastDatagramSocket; protected static MulticastSocket multicastSocket; /** Debugging output. Pass in a string, and it gets printed out on the console. You can pass in strings such as "foo " + bar.getName(). */ public static void debug(String pDebugString) { if(DEBUG) System.out.println("AwtMulticastRelayClient: " + pDebugString); } /** * Guaranteed debugging output. Pass in a string, and it gets printed out on the console. * You can pass in strings such as "foo " + bar.getName(). */ protected static void trace (String pDiagnostic) { System.out.println("AwtMulticastRelayClient: " + pDiagnostic); } /** Main constructor, which lays out fields in the AWT. */ public AwtMulticastRelayClient() { Panel serverRegion = new Panel(); // panel for server data Panel clientRegion = new Panel(); // client data region Panel checkboxRegion = new Panel(); // region for checkbox Panel clientTextfieldRegion = new Panel(); // Region for client textfield data Panel buttonRegion = new Panel(); // region that holds buttons GridLayout serverRegionLayout = new GridLayout(2,2); GridLayout clientRegionLayout = new GridLayout(1,1); GridLayout checkboxRegionLayout = new GridLayout(2,2); GridLayout clientTextfieldRegionLayout = new GridLayout(4,2); // The text fields for the server data portion serverRegion.setLayout(serverRegionLayout); serverRegion.add(new Label("Server Name:")); mcastRelayServer = new TextField(DEFAULT_SERVER, 18); serverRegion.add(mcastRelayServer); serverRegion.add(new Label("Server Port:")); mcastRelayServerPort = new TextField(DEFAULT_SERVER_PORT,5); serverRegion.add(mcastRelayServerPort); add(serverRegion); // Client data region: a checkbox and a few fields. // Add a radio button, unicast or multicast socketTypeRadio = new CheckboxGroup(); checkboxRegion.setLayout(checkboxRegionLayout); multicastCheckbox = new Checkbox("Multicast", socketTypeRadio, true); checkboxRegion.add(multicastCheckbox); unicastCheckbox = new Checkbox("Unicast", socketTypeRadio, false); checkboxRegion.add(unicastCheckbox); // Call us when the user hits the radio button so we can enable & disable fields unicastCheckbox.addItemListener(this); multicastCheckbox.addItemListener(this); clientRegion.add(checkboxRegion); // Add a bunch of text fields for client data clientTextfieldRegion.setLayout(clientTextfieldRegionLayout); // mcast address to send to locally clientTextfieldRegion.add(new Label("Address:")); sendAddress = new TextField(DEFAULT_MCAST_ADDRESS, 10); clientTextfieldRegion.add(sendAddress); // The port to send to localy, could be unicast or mcast clientTextfieldRegion.add(new Label("Port:")); port = new TextField(DEFAULT_MCAST_PORT, 10); clientTextfieldRegion.add(port); // eye candy: how many packets relayed clientTextfieldRegion.add(new Label("Packets Relayed:")); packetCount = new TextField("0", 10); clientTextfieldRegion.add(packetCount); clientRegion.add(clientTextfieldRegion); add(clientRegion); // Start & stop buttons startButton = new Button("Start"); startButton.addActionListener(this); buttonRegion.add(startButton); stopButton = new Button("Stop"); stopButton.setEnabled(false); stopButton.addActionListener(this); buttonRegion.add(stopButton); add(buttonRegion); } public static void main(String args[]) { // Create Toplevel Window to contain applet AwtMulticastRelayClient AwtMulticastRelayClientFrame frame = new AwtMulticastRelayClientFrame("MulticastRelayClient"); // Must show Frame before we size it so insets() will return valid values frame.show(); frame.setVisible(true); frame.setSize(frame.getInsets().left + frame.getInsets().right + 320, frame.getInsets().top + frame.getInsets().bottom + 240); // The following code starts the applet running within the frame window. // It also calls GetParameters() to retrieve parameter values from the // command line, and sets m_fStandAlone to true to prevent init() from // trying to get them from the HTML page. AwtMulticastRelayClient applet_MulticastRelayClient = new AwtMulticastRelayClient(); frame.add("Center", applet_MulticastRelayClient); applet_MulticastRelayClient.m_fStandAlone = true; applet_MulticastRelayClient.init(); applet_MulticastRelayClient.start(); frame.show(); } // APPLET INFO SUPPORT: // The getAppletInfo() method returns a string describing the applet's // author, copyright date, or miscellaneous information. //-------------------------------------------------------------------------- public String getAppletInfo() { return "Name: AwtMulticastRelayClient\r\n" + "Author: Don McGregor\r\n" + "GNU Copyleft applies"; } // The init() method is called by the AWT when an applet is first loaded or // reloaded. Override this method to perform whatever initialization your // applet needs, such as initializing data structures, loading images or // fonts, creating frame windows, setting the layout manager, or adding UI // components. //-------------------------------------------------------------------------- public void init() { // If you use a ResourceWizard-generated "control creator" class to // arrange controls in your applet, you may want to call its // CreateControls() method from within this method. Remove the following // call to resize() before adding the call to CreateControls(); // CreateControls() does its own resizing. //---------------------------------------------------------------------- resize(320, 240); // TODO: Place additional initialization code here } // Place additional applet clean up code here. destroy() is called when // when you applet is terminating and being unloaded. //------------------------------------------------------------------------- public void destroy() { // TODO: Place applet cleanup code here } // AwtMulticastRelayClient Paint Handler //-------------------------------------------------------------------------- public void paint(Graphics g) { } // The start() method is called when the page containing the applet // first appears on the screen. The AppletWizard's initial implementation // of this method starts execution of the applet's thread. //-------------------------------------------------------------------------- public void start() { // TODO: Place additional applet start code here } // The stop() method is called when the page containing the applet is // no longer on the screen. The AppletWizard's initial implementation of // this method stops execution of the applet's thread. //-------------------------------------------------------------------------- public void stop() { sending = false; trace ("AwtMulticastRelayClient.stop(): (boolean) sending = false;"); } /************************************************************************ check for hello packets from server and respond with "iAmHere" for each packet received. ***********************************************************************/ public String respondToServer(DatagramSocket pSocket, DatagramPacket pPacket, InetAddress address, int port) { String iAmHere = new String("iAmHere"); //set up reply message byte[] replyBuffer = new byte[iAmHere.length()]; replyBuffer = iAmHere.getBytes(); String s = new String(pPacket.getData(), 0, pPacket.getLength()); if (s.equals("hello")) //valid message { System.out.print("\"Hello\" from Server... "); try{ replyPacket = new DatagramPacket(replyBuffer, replyBuffer.length, address, port); pSocket.send(replyPacket); trace ("localhost replies \"" + iAmHere + "\"\n"); }//endtry catch (IOException e) { System.err.println("Had a problem sending reply, IOException: " + e); e.printStackTrace(); }//endcatch }//endif return s; }//end respondToServer() /** Does the dirty work of actually relaying the packets. Connects up to the server, reads the packets, and spits them back out again on this side. */ public void relayPackets() { byte outBuffer[] = new byte[MAX_PACKET_SIZE]; byte inBuffer[] = new byte[MAX_PACKET_SIZE]; ProtocolDataUnit aPdu; DatagramPacket incomingDatagramPacket=null, outgoingDatagramPacket=null; String connectCommand = new String("connect"); //connect command byte conbuf[] = new byte[connectCommand.length()]; // byte buffer for connect command String disconnectCommand = new String("disconnect"); InetAddress serverAddress = null; int serverPort = 0; String serverAddressValue = null; InetAddress destinationMulticastAddress = null; InetAddress destUnicastAddress = null; String destUnicastAddressValue = null; int destinationMulticastPort = 0; int packetSentCount = 0; boolean usingMulticast = true; // THIS IS NETSCAPE SPECIFIC. Switch on all access for sockets. PrivilegeManager privilegeManager; privilegeManager = PrivilegeManager.getPrivilegeManager(); privilegeManager.enablePrivilege("UniversalMulticast"); privilegeManager.enablePrivilege("UniversalConnect"); privilegeManager.enablePrivilege("UniversalAccept"); privilegeManager.enablePrivilege("UniversalListen"); // unicast or multicast? if(socketTypeRadio.getSelectedCheckbox() == multicastCheckbox) usingMulticast = true; else usingMulticast = false; try { // Create a unicast socket for sending commands and relaying packets unicastDatagramSocket = new DatagramSocket(); debug("Client unicast port is: " + unicastDatagramSocket.getLocalPort()); destinationMulticastPort = Integer.valueOf(port.getText()).intValue(); // If we're mcast, we need a second socket for sending out on that. if(usingMulticast) { destinationMulticastAddress = InetAddress.getByName(sendAddress.getText()); multicastSocket = new MulticastSocket(destinationMulticastPort); multicastSocket.joinGroup(destinationMulticastAddress); // trace ("default multicastSocket.getTTL()=" + // multicastSocket.getTTL()); // deprecated JDK 1.1.5 // see DatagramStreamBuffer try { /* If you have problems compiling, you'll probably need to upgrade to JDK 1.2.2 or else eliminate this method. */ trace ("default multicastSocket.getTimeToLive()=" + multicastSocket.getTimeToLive()); // JDK 1.2.2 } catch(Exception e) { trace (e.getMessage()); trace ("...try deprecated JDK 1.1 getTTL method instead"); try { trace ("default multicastSocket.getTTL()=" + multicastSocket.getTTL()); // JDK 1.1.8, deprecated } catch(Exception ee) { trace (ee.getMessage()); ee.printStackTrace(); } } catch(java.lang.NoSuchMethodError nsme) // likely java.lang.NoSuchMethodError if JDK 1.1 { trace (nsme.getMessage()); trace ("...try deprecated JDK 1.1 getTTL method instead"); try { trace ("default multicastSocket.getTTL()=" + multicastSocket.getTTL()); // JDK 1.1.8, deprecated } catch(Exception ee) { trace (ee.getMessage()); ee.printStackTrace(); } } } else { destUnicastAddressValue = sendAddress.getText(); if(destUnicastAddressValue.equalsIgnoreCase("localhost")) destUnicastAddress = InetAddress.getLocalHost(); else destUnicastAddress = InetAddress.getByName(serverAddressValue); } // Look up server address and server port number serverAddressValue = mcastRelayServer.getText(); if(serverAddressValue.equalsIgnoreCase("localhost")) serverAddress = InetAddress.getLocalHost(); else serverAddress = InetAddress.getByName(serverAddressValue); serverPort = Integer.valueOf(mcastRelayServerPort.getText()).intValue(); } catch (java.io.IOException e) { System.err.println("could not create datagram socket"); System.err.println(e); return; } catch(NumberFormatException nfe) { trace ("bad format for port, " + port.getText()); trace (nfe.getMessage()); return; } // Send a command to the server to connect us. First, find the address // of the server // Hmmm....packets are unreliable. What happens if dropped on floor? Seems to // work for now, anyway. try { conbuf = connectCommand.getBytes(); outgoingDatagramPacket = new DatagramPacket(conbuf, conbuf.length, serverAddress, serverPort); unicastDatagramSocket.send(outgoingDatagramPacket); //send connect } catch(IOException ioe) { trace ("unable to send connect command"); trace (ioe.getMessage()); return; } // Loop until they hit the stop button, relaying packets. while(sending) { try { incomingDatagramPacket = new DatagramPacket(inBuffer, inBuffer.length); unicastDatagramSocket.setSoTimeout(SOCKET_TIMEOUT); // many-second server response timeout if (unicastDatagramSocket.getSoTimeout() != SOCKET_TIMEOUT) trace ("unicastDatagramSocket timeout=" + unicastDatagramSocket.getSoTimeout() + " msec, not " + SOCKET_TIMEOUT); receivedFromServer = false; unicastDatagramSocket.receive(incomingDatagramPacket); receivedFromServer = true; // only reached if successful } catch(SocketException se) { trace ("" + se + ", can't set timeout on unicastDatagramSocket"); finalize(); return; } catch (IOException e) { // timed out on read, which is OK, continue to loop } if (receivedFromServer) { // debug (" Data received from MulticastRelayServer "); String s = respondToServer(unicastDatagramSocket, incomingDatagramPacket, serverAddress, serverPort); if (!(s.equals("hello"))){ aPdu = ProtocolDataUnit.datagramToPdu(incomingDatagramPacket); //convert datagram to PDU // resend from server to localhost/LAN!! try { if(usingMulticast) { incomingDatagramPacket.setAddress (destinationMulticastAddress); incomingDatagramPacket.setPort (destinationMulticastPort); multicastSocket.send(incomingDatagramPacket); debug("...sent datagram (" + aPdu.pduName() + ") from server to local multicastSocket"); } else { outgoingDatagramPacket = new DatagramPacket(outBuffer, outBuffer.length, destUnicastAddress, destinationMulticastPort); unicastDatagramSocket.send(outgoingDatagramPacket); debug("...sent datagram from server to the unicastDatagramSocket"); } packetSentCount++; packetCount.setText(Integer.toString(packetSentCount)); } catch(IOException ioe) { debug("couldn't relay packet from server to localhost, " + ioe); finalize(); return; } } } // listen for locally generated packet and send to server try{ outgoingDatagramPacket = new DatagramPacket(outBuffer, outBuffer.length); multicastSocket.setSoTimeout(SOCKET_TIMEOUT); // millisecond server response timeout receivedFromLocalhostOrLAN = false; multicastSocket.receive(outgoingDatagramPacket); receivedFromLocalhostOrLAN = true; // only reached if successful }//endtry catch(SocketException se) { trace ("" + se + ", can't receive on multicastSocket"); finalize(); return; } catch (IOException e) { // timed out on read, which is OK, continue to loop }//endcatch if (receivedFromLocalhostOrLAN) // resend from localhost/LAN to server!!! { trace ("Data received from " + outgoingDatagramPacket.getAddress().getHostAddress()); aPdu = ProtocolDataUnit.datagramToPdu(outgoingDatagramPacket); //convert datagram to PDU try { unicastDatagramSocket.send(outgoingDatagramPacket); trace ("sent datagram (" + aPdu.pduName() + ") from localhost/LAN to server on unicastDatagramSocket"); } catch(IOException ioe) { trace ("couldn't relay packet from localhost/LAN to server, " + ioe); finalize(); return; } } }//endwhile try { // disconnect cleanly from server. trace ("Sending \"" + disconnectCommand + "\" to server and exiting AwtMulticastRelayClient."); byte[] disconnectBuffer = new byte[disconnectCommand.length()]; disconnectBuffer = disconnectCommand.getBytes(); outgoingDatagramPacket = new DatagramPacket(disconnectBuffer, disconnectBuffer.length, serverAddress, serverPort); unicastDatagramSocket.send(outgoingDatagramPacket); unicastDatagramSocket.close(); if(usingMulticast) multicastSocket.leaveGroup(destinationMulticastAddress); sending = false; startButton.setEnabled(true); stopButton.setEnabled(false); } catch(IOException ioe) { trace ("couldn't disconnect"); finalize(); return; } }//end relayPackets /** ActionListener for the start and stop buttons. This uses the AWT 1.1 event model, and implemens the ActionListener interface. */ public void actionPerformed(ActionEvent ae) { Object source = ae.getSource(); if(source == startButton) { RelayThread relayThread = new RelayThread(this); sending = true; relayThread.start(); startButton.setEnabled(false); stopButton.setEnabled(true); } if(source == stopButton) // Stop button hit { sending = false; startButton.setEnabled(true); stopButton.setEnabled(false); } } // end of actionListener /** itemStateChanged is the implementation of the interface ItemListener, which is used by checkboxes. The checkboxes add us as a listener, and we get their events. This is based on the Java 1.1 event model. */ public void itemStateChanged(ItemEvent e) // interface for ItemListener { Object source = e.getSource(); if(source == unicastCheckbox) { sendAddress.setText("localhost"); port.setText(DEFAULT_LOCAL_UNICAST); } if(source == multicastCheckbox) { sendAddress.setText(DEFAULT_MCAST_ADDRESS); port.setText(DEFAULT_MCAST_PORT); } } // end of itemStateChanged protected void finalize() { sending = false; // turn off loop from the inside }//end finalize() // TODO: Place additional applet code here }