JMU
JMeetUp
An Example Using TCP and UDP


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Overview
Some Needs
Some Requirements
Handling Commands
images/JMeetUp-commands.gif
Talk
images/JMeetUp-talk.gif
Propose, Check and Choose
images/JMeetUp-proposecheckchoose.gif
A Proposal
javaexamples/jmeetup/Proposal.java
        package jmeetup;

import java.util.*;

/**
 * A Proposal in JMeetUp
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 */
public class Proposal
{
    private Hashtable<String,String>   proponents; // Thread safe
    private int                        id;
    private String                     description;


    /**
     * Default Constructor
     */
    public Proposal()
    {
        this(-1, "Default Proposal");
    }

    /**
     * Explicit Value Constructor
     */
    public Proposal(int id, String description)
    {
        this.id = id;
        this.description = description;
        proponents = new Hashtable<String,String>();
    }


    /**
     * Add a new proponent
     *
     * @param proponent   The new proponent
     */
    public void addProponent(String proponent)
    {
        proponents.put(proponent, proponent);
    }


    /**
     * Get the ID of this Proposal
     *
     * @return   The ID
     */
    public int getID()
    {
        return id;
    }


    /**
     * Get all of the proponents for this Proposal
     *
     * @return   The proponents
     */
    public Enumeration<String> getProponents()
    {
        return proponents.elements();
    }


    /**
     * Remove a proponent
     *
     * @param proponent   The proponent to remove
     */
    public void removeProponent(String proponent)
    {
        proponents.remove(proponent);
    }


    /**
     * Return a String representation of this Proposal
     *
     * @return  The String
     */
    public String toString()
    {
        Enumeration<String>  e;
        String       s;

        s = "("+id+") "+description+"\t\t";

        s += "Proponents: ";
        e = proponents.elements();
        while (e.hasMoreElements()) 
        {
            s += e.nextElement() + "  ";
        }

        return s;
    }
}
        
The Client
javaexamples/jmeetup/Client.java
        package jmeetup;

import java.awt.*;
import java.io.*;
import java.net.*;

import gui.*;
import internet.*;


/**
 * A JMeetUp Client
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 */
public class Client
{
    private BufferedReader          in;
    private UDPMessageReceiver      messageReceiver;
    private PrintWriter             out;
    private Socket                  s;
    private String                  username;


    /**
     * Constructor
     */
    public Client(String username, String host, 
                  int tcpPort, int udpPort) throws IOException, 
                                                   SocketException
    {
       MessageWindow     messageWindow;


       this.username = username;

       s   = new Socket(host, tcpPort);
       out = new PrintWriter(s.getOutputStream());
       in  = new BufferedReader(
          new InputStreamReader(s.getInputStream()));


       // Send the login information
       send("login:"+username);
       send("port:"+udpPort);


       // Create the GUI components
       messageWindow = new MessageWindow();


       // Start the pieces
       messageReceiver = new UDPMessageReceiver(udpPort);
       messageReceiver.addMessageListener(messageWindow);
       messageReceiver.start();       
       processCommands();
       
       // Stop the pieces
       System.out.print("Bye, ");       
       messageReceiver.stop();
       messageWindow.setVisible(false);
       messageWindow.dispose();       
       System.out.print("bye...");       
    }



    /**
     * Read commands from the console, send them to the server, and
     * print the responses
     */
    private void processCommands()
    {
       BufferedReader  cin;
       String          line;

       cin = new BufferedReader(
          new InputStreamReader(System.in));

       try 
       {
          while ((line=cin.readLine()) != null) 
          {
             send(line);
             line = receive();
             System.out.println(line.replaceAll("<BR/>","\n")+"\n");
          }
          send("logout:"+username);
       } 
       catch (IOException ioe) 
       {
          ioe.printStackTrace();
       }
    }


    /**
     * Receive a line of text
     *
     * @return   The text
     */
    private String receive()
    {
       String   line;


       line = null;
       try 
       {
          line = in.readLine();
       }
       catch (IOException ioe) 
       {
          // Ignore it
       }

       return line;
    }


    /**
     * Send a line of text
     *
     * @param text   The text to send
     */
    private void send(String text)
    {
       out.println(text);
       out.flush();
    }
}
        
The Server
javaexamples/jmeetup/Server.java
        package jmeetup;

import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;

import gui.*;
import internet.*;


/**
 * The JMeetUp server
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 */
public class Server implements Runnable
{
    private volatile boolean              keepRunning;
    private Hashtable<ConnectionHandler,
                      ConnectionHandler>  tcpHandlers; // Thread safe
    private Hashtable<ConnectionHandler,
                      UDPMessageSender>   udpHandlers; // Thread safe
    private ServerSocket                  ss;
    private Thread                        controlThread;
    private Vector<Proposal>              proposals;   // Thread safe

    /**
     * Default Constructor
     */
    public Server()
    {
       this(9000);
    }


    /**
     * Explicit Value Constructor
     *
     * @param port  The port the server should listen to
     */
    public Server(int port)
    {
       try 
       {
          ss = new ServerSocket(port);

          // Make sure accept() will timeout so that we can stop
          ss.setSoTimeout(5000);

          tcpHandlers = new Hashtable<ConnectionHandler,ConnectionHandler>();
          udpHandlers = new Hashtable<ConnectionHandler,UDPMessageSender>();

          proposals = new Vector<Proposal>();
       }
       catch (Exception e) 
       {
          System.err.println(e);
          System.exit(1);
       }
    }


    /**
     * Add a proponent to a Proposal
     *
     * @param  id           The ID of the proposal
     * @param  username     The proponent
     */
    public synchronized void addProponent(int id,
                                          String username)
                                         
    {
       Enumeration<Proposal>    e;
       Proposal                 p;


       e = proposals.elements();
       while (e.hasMoreElements()) 
       {
          p = e.nextElement();
          if (p.getID() != id) p.removeProponent(username);
          else                 p.addProponent(username);
       }
    }


    /**
     * Add a Proposal
     *
     * @param  username     The user making the proposal
     * @param  description  Decscription of the proposal
     */
    public synchronized void addProposal(String username, 
                                         String description)
    {
       int        id;
       Proposal   p;


       id = proposals.size() + 1;
       p = new Proposal(id, description);
       proposals.add(p);
       addProponent(id, username);
    }


    /**
     * Broadcast a message
     *
     * @param message   The message
     */
    public synchronized void broadcast(String message)
    {
       Enumeration<ConnectionHandler>   e;
       ConnectionHandler                tcph;
       UDPMessageSender                 udph;

       e = tcpHandlers.elements();
       while (e.hasMoreElements())
       {
          tcph = e.nextElement();
          udph = udpHandlers.get(tcph);
          
          if (udph != null) udph.send(message);
       }
    }


    /**
     * Get all of the proposals
     *
     * @return  The proposals
     */
    public synchronized Enumeration<Proposal> getProposals()
    {
       return proposals.elements();
    }


    /**
     * Remove a connection handler
     *
     * @param name    The name
     */
    public void remove(ConnectionHandler handler)
    {
       ConnectionHandler   tcph;
       UDPMessageSender    udph;


       tcph = tcpHandlers.remove(handler);
       udph = udpHandlers.remove(handler);

       if (udph != null) udph.stop();
       if (tcph != null) tcph.stop();
    }


    /**
     * Remove all connection handlers
     *
     */
    public void removeAll()
    {
       Enumeration<ConnectionHandler>   e;
       ConnectionHandler                handler;

       e = tcpHandlers.elements();
       while (e.hasMoreElements())
       {
          handler = e.nextElement();
          remove(handler);
       }
    }


    /**
     * The "entry point" for the new thread of execution
     */
    public void run()
    {
       ConnectionHandler   tcph;
       UDPMessageSender    udph;
       Socket              s;

       while (keepRunning) 
       {
          try 
          {
             s   = ss.accept();
             udph = new UDPMessageSender(s.getInetAddress());
             udph.start();

             try 
             {
                tcph =new ConnectionHandler(s,this);
                    
                // Use the ConnectionHandler 
                // as the key in both cases since 
                // it will initiate the remove later
                tcpHandlers.put(tcph, tcph);
                udpHandlers.put(tcph, udph);

                tcph.start();
                    
             }
             catch (IOException ioet) 
             {
                ioet.printStackTrace();
             }
          }
          catch (SocketException se) 
          {
             // Unable to create UDP socket or accept() timed-out
          }
          catch (IOException ioes)
          {
             // Unable to accept
          }
       }
       
       // The thread is about to terminate so remove all of the
       // handlers
       removeAll();

       controlThread = null;
    }


    /**
     * Set the port for the UDPMessageSender
     *
     * @param handler The ConnectionHandler
     * @param port    The port to use
     */
    public void setSenderPort(ConnectionHandler handler,
                              int port)
    {
       ConnectionHandler      tcph;
       UDPMessageSender       udph;


       tcph = tcpHandlers.get(handler);
       udph = udpHandlers.get(handler);

       if (udph != null) udph.setPort(port);
    }



    /**
     * Start the new thread of execution
     */
    public void start()
    {
       if (controlThread == null)
       {
          keepRunning = true;
          controlThread = new Thread(this);
          controlThread.start();
       }
    }



    /**
     * Stop the thread of execution
     */
    public void stop()
    {
       keepRunning = false;
    }

}
        
The Server (cont.)

The Connection Handler

javaexamples/jmeetup/ConnectionHandler.java
        package jmeetup;

import java.io.*;
import java.net.*;
import java.util.*;

/**
 * Handles an individual connection with a 
 * JMeetUp client
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 */
public class ConnectionHandler implements Runnable
{
    private volatile boolean       keepRunning;
    private BufferedReader         in;
    private PrintWriter            out;
    private Socket                 s;
    private String                 username;
    private Thread                 controlThread;
    private Server                 server;


    /**
     * Construct a new ConnectionHandler
     *
     * @param s        The Socket to use
     * @param server   The "controlling" server
     */
    public ConnectionHandler(Socket s, Server server)
       throws IOException
    {
       this.server = server;
       this.s = s;

       in = new BufferedReader(
          new InputStreamReader(
             s.getInputStream()));


       out = new PrintWriter(s.getOutputStream());
    }


    /**
     * Handle a check
     */
    private void handleCheck()
    {
       Enumeration<Proposal>   e;
       Proposal                p;


       e = server.getProposals();
       while (e.hasMoreElements()) 
       {
          p = e.nextElement();
          out.print(p+"<BR/>");
       }
       out.print("\n");
       out.flush();
    }


    /**
     * Handle a choice
     */
    private void handleChoose(String number)
    {
       int   id;

       try 
       {
          id = Integer.parseInt(number);
          server.addProponent(id, username);
          out.println("OK");
          out.flush();
       }
       catch (NumberFormatException nfe)
       {
          out.println(number+" is an invalid choice!");
          out.flush();
       }
    }


    /**
     * Handle a login
     */
    private void handleLogin(String name)
    {
       // Only change the username if it hasn't 
       // already been set
       if (username == null) 
       {
          username = name;
          System.out.println("["+username+"]  on "+
                             s.getInetAddress()+", "+
                             s.getPort());
       }
    }


    /**
     * Handle a logout
     */
    private void handleLogout()
    {
       // Close the socket and stop the thread
       stop();
    }


    /**
     * Handle a port change
     *
     * @param s   The port
     */
    private void handlePort(String s)
    {
       int    port;

       try 
       {
          port = Integer.parseInt(s);
          server.setSenderPort(this, port);
       }
       catch (NumberFormatException nfe) 
       {
          // Don't change the port
       }
    }


    /**
     * Handle a proposal
     *
     * @param description   The texr of the proposal
     */
    private void handlePropose(String description)
    {
       server.addProposal(username, description);
       out.println("OK");
       out.flush();
    }


    /**
     * Handle a talk command
     *
     * @param message   The message to send
     */
    private void handleTalk(String message)
    {
       server.broadcast("["+username+"]  "+message);
       out.println("OK");
       out.flush();
    }


    /**
     * Run this ConnectionHandler
     */
    public void run()
    {
       String             argument, command, line, name, value;
       StringTokenizer    st;


       while (keepRunning) 
       {
          try
          {
             line = in.readLine();

             command  = "";
             argument = "";
             st = new StringTokenizer(line, ":");

             // Parse the command
             if (st.hasMoreTokens()) command  = st.nextToken();
             if (st.hasMoreTokens()) 
             {
                argument = st.nextToken().trim();
             }
             else
             {
                if (!command.equals("check")) 
                {
                   argument = command;
                   command  = "talk";
                }
             }

             // Process the command
             //
             // Note: This could be improved with the 
             // Command Pattern
             if (command.equals("check"))
             {
                handleCheck();
             }
             else if (command.equals("choose"))
             {
                handleChoose(argument);
             } 
             else if (command.equals("login")) 
             {
                handleLogin(argument);
             } 
             else if (command.equals("logout")) 
             {
                handleLogout();
             } 
             else if (command.equals("port")) 
             {
                handlePort(argument);
             } 
             else if (command.equals("propose")) 
             {
                handlePropose(argument);
             } 
             else if (command.equals("talk")) 
             {
                handleTalk(argument);
             }
          } 
          catch (IOException ioe) 
          {
             // Ignore it
          }
       }

       // The thread is about to die
       try 
       {
          in.close();
          out.close();
          s.close();
       } 
       catch (IOException ioe) 
       {
          // Ignore
       }

       controlThread = null;
    }



    /**
     * Start the new thread of execution
     */
    public void start()
    {
       if (controlThread == null)
       {
          keepRunning = true;
          controlThread = new Thread(this);

          controlThread.start();
       }
    }


    /**
     * Stop the thread of execution
     */
    public void stop()
    {
       keepRunning = false;
    }
}