HW6 Solutions Page
Return to
homework page.
//Server.java - server in chat document program cs151 hw6
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.io.*;
import java.net.*;
import java.util.*;
/**
This is the Server in our client server chat document program.
As soon as rmiregistry is running on the appropriate port (54322),
it can be run from the command line on Unix with a line like:
java Server &
or
java Server socketPortNumber rmiPortNumber &
where these port numbers are the port numbers the server is listening
to. This program should be compiled with
javac Server.java
rmic -v1.2 Server
*/
public class Server extends UnicastRemoteObject
implements TextServer, Runnable, ChatConstants
{
/**
Constructor. sets up the the server, what ports it listens to,
rebinds to rmiregistry, and set up HashMap's of parties
for both socket and RMI.
@throws RemoteException - super() below
@throws MalformedURLException - rebind below
*/
public Server(int socketPort, int rmiPort) throws RemoteException,
MalformedURLException
{
super();
this.socketPort = socketPort;
this.rmiPort = rmiPort;
//LocateRegistry.createRegistry(rmiPort);
/*can uncomment the above line if only one object in rmiregistry
and don't want to have to type at command prompt rmiregistry& */
Naming.rebind("//localhost:"+rmiPort+"/ChatServer", this);
chatParties = new HashMap();
rmiParties = new HashMap();
}
/**
Called by main after calls above Constructor.
Sets ups ServerSocket. Launches a new thread
using this object for run() whenever a new connection
received.
*/
public void start()
{
try
{
ServerSocket s = new ServerSocket(socketPort);
while (true)
{
socket = s.accept();
System.out.println("Establishing connection.");
new Thread(this).start();
}
}
catch(IOException ie)
{
System.err.println("ServerSocket or Socket.accept error.");
ie.printStackTrace();
}
}
/**
Used by a Client to notify server it should be added to the
given party for rmi communication
@param party - chat party Client belongs to
@param obj - stub for client
@throws RemoteException
*/
public void addToRMIParty(String party, TextPushClient obj)
throws RemoteException
{
HashSet rmiNames = (HashSet)rmiParties.get(party);
if(rmiNames == null) rmiNames = new HashSet();
rmiNames.add(obj);
rmiParties.put(party, rmiNames);
}
/**
Used by a Client to set the text document accociated with a
given patry. Server then broadcasts using Client's textUpdate
method this document to all the Client's.
@param s - text document to send to server to broadcast to party
@param party - chat party Client belongs to
@throws RemoteException
*/
public void setText(String s, String rmiParty) throws RemoteException
{
HashSet rmiNames = (HashSet)rmiParties.get(rmiParty);
Iterator partyIterator = rmiNames.iterator();
while(partyIterator.hasNext())
{
TextPushClient obj = (TextPushClient)partyIterator.next();
obj.textUpdate(s);
}
}
/**
Used by a Client to notify server it should be deleted to the
given party for rmi communication
@param party - chat party Client belongs to
@param obj - stub for client
@throws RemoteException
*/
public void deleteFromRMIParty(String party, TextPushClient obj)
throws RemoteException
{
HashSet rmiNames = (HashSet)rmiParties.get(party);
if(rmiNames == null) return;
rmiNames.remove(obj);
rmiParties.put(party, rmiNames);
}
/**
After a new socket connection has been established in
@see start(), a new Thread is launched which uses the
code below to run. This Thread sets up i/o with this
particular client and adds the client to the appropriate
chat party. It then reads any communication from this client
and broadcasts it to other members in the Client's party.
When the Client disconnect, this Thread removes it from
the given chat party and finishes.
*/
public void run()
{
Socket socket = this.socket;
if(socket == null) return;
BufferedReader in;
PrintWriter out;
try
{
in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
out = new PrintWriter(new OutputStreamWriter(
socket.getOutputStream()));
}
catch(IOException ie)
{
System.err.println("Get streams error.");
return;
}
String party = modifyPartyMembers(in, out);
String line;
if( party == null) return;
try
{
while(true)
{
line = in.readLine();
if(line == null) break;
if(line.startsWith(endString))
{
broadcastParty("Goodbye, " +
line.substring(endString.length()), party);
break;
}
broadcastParty(line, party);
}
socket.close();
}
catch(IOException ie)
{
ie.printStackTrace();
}
deleteFromSocketParty(party, out);
}
/*
Driver for application.
Check if we want to use non-default ports. Then sets up
a Server and starts it running.
*/
public static void main(String[] args)
{
int socketPort;
int rmiPort;
if(args.length >= 2)
{
socketPort = Integer.parseInt(args[0]);
rmiPort = Integer.parseInt(args[1]);
}
else
{
socketPort = SOCKET_PORT;
rmiPort = RMI_PORT;
}
try
{
Server server = new Server(socketPort, rmiPort);
server.start();
}
catch(Exception e)
{
System.err.println("Server not initialized correctly.");
e.printStackTrace();
}
}
/*
Uses sockets to send the given line to all the Client's
in a given chat party. We have PrintWriter's to each of
these Client's through the HashSet in associated to
party in chatParties.
*/
protected void broadcastParty(String line, String party)
{
HashSet chatNames = (HashSet)chatParties.get(party);
Iterator partyIterator = chatNames.iterator();
while(partyIterator.hasNext())
{
PrintWriter out = (PrintWriter)partyIterator.next();
out.println(line);
out.flush();
}
}
/*
Removes the PrintWriter out from the HashSet corresponding
to the String party in the ArrayList of chatParties.
Called by thread used to handle a particular Client when
Client disconnects.
*/
protected void deleteFromSocketParty(String party, PrintWriter out)
{
HashSet chatNames = (HashSet)chatParties.get(party);
chatNames.remove(out);
chatParties.put(party, chatNames);
}
/*
Reads from in to find out who established in's Socket
(echoed but not used) and what party they belong to then adds
out to the HashSet associated with that party in chatParties.
*/
protected String modifyPartyMembers(BufferedReader in, PrintWriter out)
{
String party;
String name;
try
{
party = in.readLine();
name = in.readLine();
System.out.println("Name: "+name);
System.out.println("Adding to:"+party);
}
catch(IOException ie)
{
System.err.println("Get chat party error.");
return null;
}
HashSet chatNames = (HashSet)chatParties.get(party);
if(chatNames == null) chatNames = new HashSet();
chatNames.add(out);
chatParties.put(party, chatNames);
broadcastParty(name+" has joined the group.", party);
return party;
}
protected Socket socket; //most recent socket
protected int socketPort; /* port socket communication goes through
default is SOCKET_PORT */
protected int rmiPort; /* port rmi communication goes through
default is RMI_PORT */
protected HashMap rmiParties; /* map of parties and rmi object's
of Client's in parties.
*/
protected HashMap chatParties; /* map of parties and HashSet of
PrintWriter's through Sockets
to Client's in parties
*/
}
//Client.java -- client in chat document program cs151 hw6
import java.rmi.*;
import java.rmi.server.*;
import java.io.*;
import java.net.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
import java.awt.event.*;
/**
This is the client in our client server chat document program.
It can be run from the command line on Unix with a line like:
java Client &
or
java Client socketPortNumber rmiPortNumber &
where these port numbers are the port numbers the server is listening
to. This program should be compiled with
javac Client.java
rmic -v1.2 Client
*/
public class Client extends JFrame implements TextPushClient,
ActionListener, Runnable, ChatConstants
{
/**
Constructor. Creates a Client with the given socket and rmi ports.
@param socketPort - the socket port number
@param rmiPort - the rmiPort number
*/
public Client(int socketPort, int rmiPort)
{
super("Chat Client");
this.socketPort = socketPort;
this.rmiPort = rmiPort;
Box box = Box.createVerticalBox();
connected = false;
setUpTextComponents(box);
setUpButtons(box);
}
/**
Sets the text in the clients text document area.
Called by Server.
@param s -text to set in client text document area
@throws RemoteException
*/
public void textUpdate(String s) throws RemoteException
{
documentArea.setText(s);
}
/**
Handles all button, text events for this app.
Calls the appropriate handler method.
@param e - event we're processing.
*/
public void actionPerformed(ActionEvent e)
{
Object o = e.getSource();
if(o == chatInput) transmitChat();
else if(o == connectButton) connect();
else if (o == setTextButton) setText();
else if (o == disconnectButton) disconnect();
}
/**
Once a connection is established there is a Client Thread
which just listens for chat rebroadcast from the Server that
are being ``chatted'' about by other Client's in this
Client's chat party. This method is the code that this
dedicated Client thread runs.
*/
public void run()
{
String line;
while(connected)
{
try
{
line = in.readLine();
chatArea.append(line+"\n");
}
catch(IOException ie)
{
ie.printStackTrace();
disconnect();
break;
}
}
}
/**
check if non-default ports provided in the command line and
otherwise creates and starts a Client application.
*/
public static void main(String[] args)
{
int socketPort;
int rmiPort;
if(args.length >= 2)
{
socketPort = Integer.parseInt(args[0]);
rmiPort = Integer.parseInt(args[1]);
}
else
{
socketPort = SOCKET_PORT;
rmiPort = RMI_PORT;
}
Client frame = new Client(socketPort, rmiPort);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.show();
}
/*
Sets up all the text components in our client application.
Adds them to the passed box.
Also sets up chatInput's listener.
*/
protected void setUpTextComponents(Box box)
{
chatName = (JTextField)addTextComponent(TEXTFIELD,
"Chat Name: ", true, box);
chatParty = (JTextField)addTextComponent(TEXTFIELD,
"Chat Party: ", true, box);
serverURL = (JTextField)addTextComponent(TEXTFIELD,
"Server URL: ", true, box);
documentArea= (JTextArea)addTextComponent(TEXTAREA,
"Text Document: ", false, box);
chatArea = (JTextArea)addTextComponent(TEXTAREA,
"Chat Text: ", false, box);
chatInput = (JTextField)addTextComponent(TEXTFIELD,
"Chat Input Line: ", true, box);
getContentPane().add(box);
chatInput.addActionListener( this);
}
/*
Sets up all the button and their listeners for this application
and adds them to the passed box
*/
protected void setUpButtons(Box box)
{
Box b = Box.createHorizontalBox();
connectButton = setUpButton("Connect", b);
setTextButton = setUpButton("Set Text", b);
disconnectButton = setUpButton("Disconnect", b);
box.add(b);
}
/*
Set up an individual JButton with String name using this
application as its listener. Add this button to Box box.
*/
protected JButton setUpButton(String name, Box box)
{
JButton button = new JButton(name);
button.addActionListener(this);
box.add(button);
return button;
}
/*
Adds a JLabel with name followed by
either a JTextField or a JTextArea (depending on type)
to the given Box box. editable says whether or not
this compoent can be changed by the user.
*/
protected JTextComponent addTextComponent(int type, String name,
boolean editable, Box box)
{
JTextComponent comp;
Box b= Box.createHorizontalBox();
b.add(new JLabel(name));
b.add(Box.createHorizontalGlue());
box.add(b);
if(type == TEXTAREA)
{
comp =
new JTextArea(TEXTAREA_HEIGHT, TEXTAREA_WIDTH);
box.add(new JScrollPane(comp));
}
else
{
comp = new JTextField(TEXTFIELD_WIDTH);
b.add(comp);
}
comp.setEditable(editable);
box.add(Box.createVerticalStrut(SEPARATION_HEIGHT));
return comp;
}
/*
Called when return hit in chat input field. Send current
chat input line to server then clear this field.
*/
protected void transmitChat()
{
if(!connected)
{
chatArea.append("You must establish a connection to chat.\n");
return;
}
out.println(chatName.getText()+": "+chatInput.getText());
//note we send who we are as well as what we are saying
out.flush();
chatInput.setText("");
}
/*
Called when connect button clicked. Sets up both socket and
RMI connection with server. Tells server which socket and
rmi party it should be added to. Starts the thread to listen to
socket for what is being ``chatted'' about in this chat party.
*/
protected void connect()
{
if(chatName.getText().length() == 0 ||
chatParty.getText().length() == 0 ||
serverURL.getText().length() == 0
)
{
chatArea.append(
"You must fill the first three fields to connect.\n");
return;
}
if(connected)
{
chatArea.append(
"Please disconnect from current connection before\n"
+"making a new connection.\n");
return;
}
try
{
socket = new Socket( serverURL.getText(), socketPort);
out = new PrintWriter(new OutputStreamWriter(
socket.getOutputStream()));
in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
out.println(chatParty.getText());
out.println(chatName.getText());
out.flush();
connected = true;
chatThread = new Thread(this);
chatThread.start();
}
catch(IOException ie)
{
chatArea.append(
"Error Connecting to Server.\n"+
"Please check the URL.\n");
ie.printStackTrace();
}
try
{
UnicastRemoteObject.exportObject(this);
server = (TextServer)Naming.lookup("rmi://"+serverURL.getText()+
":"+rmiPort+"/ChatServer");
server.addToRMIParty(chatParty.getText(), this);
}
catch(Exception e)
{
e.printStackTrace();
}
}
/*
Called when Set Text button clicked. Uses a JFileChooser
to select a text document. Method then reads in document
and calls TextServer's setText method to send to Server.
*/
protected void setText()
{
if(server == null)
{
documentArea.append("Must connect to server first.\n");
return;
}
JFileChooser choose = new JFileChooser();
choose.setFileSelectionMode( JFileChooser.FILES_ONLY);
int result;
result = choose.showOpenDialog(this);
if(result == JFileChooser.CANCEL_OPTION)
return;
File file = choose.getSelectedFile();
if(file == null)
{
JOptionPane.showMessageDialog(
this,
"Bad File Name", "Bad File Name",
JOptionPane.ERROR_MESSAGE );
return;
}
String fileData = "";
String line;
try
{
BufferedReader in = new BufferedReader(
new FileReader(file));
while((line= in.readLine()) != null)
{
fileData += line + "\n";
}
in.close();
server.setText(fileData, chatParty.getText());
}
catch(RemoteException re)
{
documentArea.append("Couldn't set text to server.\n");
re.printStackTrace();
}
catch(Exception e)
{
documentArea.append("Couldn't read file.\n");
e.printStackTrace();
}
}
/*
Called when disconnect button clicked.
Notifies server that no longer connect by sending endString
then closes socket.
*/
protected void disconnect()
{
if(!connected)
{
chatArea.append("You were not connected.\n");
return;
}
connected = false;
try
{
out.println(endString+chatName.getText());
out.flush();
socket.close();
chatArea.append("Disconnected.\n");
}
catch(IOException ie)
{
chatArea.append("Error disconnecting.\n");
ie.printStackTrace();
}
}
protected Socket socket; /* socket used for chat communication
through server */
protected int socketPort; /* port we do socket communication with
server. By default SOCKET_PORT */
protected TextServer server; /* rmi stub for text document server
communicating with */
protected int rmiPort; /* port on which rmi communication done
By default RMI_PORT */
protected PrintWriter out; //to write out through socket
protected BufferedReader in; //to read in through socket
protected Thread chatThread; //handles reading chat coming from server
protected boolean connected; //tells chatThread if still connected
/*
The next group of fields correspond to the
GUI components on the application
*/
protected JTextField chatName;
protected JTextField chatParty;
protected JTextField serverURL;
protected JTextField chatInput;
protected JTextArea documentArea;
protected JTextArea chatArea;
protected JButton connectButton;
protected JButton setTextButton;
protected JButton disconnectButton;
/*
Constants -- probably should make final
*/
protected int TEXTAREA = 0; //tells addTextComponent to make textareas
protected int TEXTFIELD = 1;//tells addTextComponent to make textfields
protected int TEXTFIELD_WIDTH=15; //default text field size
protected int TEXTAREA_WIDTH=30; //default dimensions of textareas
protected int TEXTAREA_HEIGHT=7;
protected int SEPARATION_HEIGHT=8; // vertical gap between GUI's
}
//TextServer.java - interface of Server that Client uses
import java.rmi.*;
/**
RMI server interface via which Client can send a text document
to the Server and via which it can let the Server know it wants
to be notified of any documents sent by other members of Client's
chat party.
*/
public interface TextServer extends Remote
{
/**
Used by a Client to notify server it should be added to the
given party for rmi communication
@param party - chat party Client belongs to
@param obj - stub for client
@throws RemoteException
*/
public void addToRMIParty(String party, TextPushClient obj)
throws RemoteException;
/**
Used by a Client to set the text document accociated with a
given patry. Server then broadcasts using Client's textUpdate
method this document to all the Client's.
@param s - text document to send to server to broadcast to party
@param party - chat party Client belongs to
@throws RemoteException
*/
public void setText(String s, String chatParty)
throws RemoteException;
/**
Used by a Client to notify server it should be deleted to the
given party for rmi communication
@param party - chat party Client belongs to
@param obj - stub for client
@throws RemoteException
*/
public void deleteFromRMIParty(String party, TextPushClient obj)
throws RemoteException;
}
//TextPushClient.java - interface server uses to set client text doc
import java.rmi.*;
/**
This interface is extended by our Client class and is used by
the Server class when it broadcasts updates to the text document
that is to be viewed by everyone in a chat party
*/
public interface TextPushClient extends Remote
{
/**
Sets the text in the clients text document area
@param s -text to set in client text document area
@throws RemoteException
*/
public void textUpdate(String s)
throws RemoteException;
}
//ChatConstants.java
/**
These are constants which are used by both the Server and Client
classes.
*/
public interface ChatConstants
{
public final int SOCKET_PORT = 54321; // port for socket communication
public final int RMI_PORT = SOCKET_PORT+1; // port for rmi communication
public final String endString = "BYE::::"; /* text used to terminate
socket communication */
}