Lab-2 description

advertisement
CS551 Calculator Lab Assignment
Deadline: Oct 3 (F) Midnight
Submission: email a zipped package (containing source code and report) to both the
instructor (leeyu@umkc.edu) and the TA (copdk4@umkc.edu) before the deadline.
This documentation has three parts: Lab 2-A, Lab 2-B, and Report.
1. Lab 2-A : A Socket-Based Calculator Distribution
The Simple Socket-Based Cookie Server
There is no actual programming involved in this exercise but it will first teach the basics of Java
socket communication.
Take a copy of the CookieServer directory from the Lab2-A directory.:
BetterSocket.java - the buffering socket class
SimpleClient.java – the client applet
SimpleServer.java – the cookie server – delivers short messages
hello.txt – messages to deliver when client says “hello”
bye.txt – messages to deliver when client says “bye”
Inspect these files and notice the following points
//BetterSocket.java
import java.net.*;
import java.io.*;
The actual
socket
The buffered
reader & writer
public class BetterSocket{
Socket theSocket;
BufferedReader theReader;
PrintWriter theWriter;
public BetterSocket( String host, int port )
throws UnknownHostException, IOException
{
this( new Socket( host, port ));
}
public BetterSocket( Socket s )
throws UnknownHostException, IOException
{
theSocket = s;
theReader = new BufferedReader(
new InputStreamReader(
new BetterSocket(
host, port ) will call
BetterSocket( socket )
with a plain socket
for host & port
Use the input and
output stream of
the Socket for
our buffered
reader & writer
theSocket.getInputStream()));
theWriter = new PrintWriter(
theSocket.getOutputStream(), true );
}
public Socket getSocket()
{
return( theSocket );
}
public void close()
throws IOException
{
theSocket.close();
}
public String readLine()
throws IOException
{
return( theReader.readLine());
}
public void println( String s )
{
theWriter.println(s);
In case we need
direct access to the
socket
readLine and
println for
receiving and
sending data
}
}
SimpleClient is an Applet
//SimpleClient.java
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
public class SimpleClient extends Applet
{
public static final int PORT=1234;
Button theHelloButton = new Button( "Hello" );
Button theByeButton = new Button( "Bye" );
TextArea theResponse = new TextArea(5,64);
This is the port we will
use
Two buttons & a text
area - simple UI!
BetterSocket theSocket;
public void init(){
init() sets out the
setLayout( new BorderLayout());
screen and assigns
Panel p = new Panel();
listeners to the two
add( p, BorderLayout.NORTH );
buttons
add( theResponse, BorderLayout.SOUTH );
p.setLayout( new GridLayout(1,0));
p.add( theHelloButton );
p.add( theByeButton );
theResponse.setEditable(false);
theHelloButton.addActionListener( new HelloHandler());
theByeButton.addActionListener( new ByeHandler());
}
Simple error handler
public void fatalError( Exception ex ){
that closes the socket
ex.printStackTrace();
as well
try{
theSocket.close();
}
catch( Exception ex1 ){;}
}
Hello button pressed so:
class HelloHandler implements ActionListener{
Make a socket ( talking to
public void actionPerformed( ActionEvent evt ){
ourselves ), say "Hello"
try{
and then get three cookies
theResponse.setText("");
theSocket = new BetterSocket(
getCodeBase().getHost(), PORT );
theSocket.println( "Hello" );
theResponse.append( theSocket.readLine() + "\n" );
theResponse.append( theSocket.readLine() + "\n" );
Bye button pressed so:
theResponse.append( theSocket.readLine() + "\n" );
Make a socket ( talking to
theSocket.close();
ourselves ), say "Bye" and
}
then get three cookies
catch( Exception ex ){ fatalError(ex); }
}
}
class ByeHandler implements ActionListener{
public void actionPerformed( ActionEvent evt ){
try{
theResponse.setText("");
theSocket = new BetterSocket(
getCodeBase().getHost(), PORT );
theSocket.println( "Bye" );
theResponse.append( theSocket.readLine() + "\n" );
theResponse.append( theSocket.readLine() + "\n" );
theResponse.append( theSocket.readLine() + "\n" );
theSocket.close();
}
catch( Exception ex ){ fatalError(ex); }
}
}
}
//SimpleServer.java
import java.net.*;
import java.io.*;
import java.util.*;
public class SimpleServer
{
public static final int PORT=1234;
ServerSocket theSocket;
BetterSocket theClient;
Vector theHello = new Vector();
Vector theBye = new Vector();
Server will wait on this port (
the one the client will call
on! )
The ServerSocket waits for a request
and the BetterSocket wraps the
ServerSocket for buffered I/O
public static void main( String [] args ){
init() starts by reading the
new SimpleServer().init();
hello & goodbye messages
}
from the two text files into
public void init(){
try{
two Vectors
BufferedReader in;
String line;
in = new BufferedReader(
The server is
new InputStreamReader(
an application
new FileInputStream( "hello.txt" )));
- not an
while(( line = in.readLine()) != null )
applet.
theHello.addElement( line );
in.close();
in = new BufferedReader(
new InputStreamReader(
This is the
new FileInputStream( "bye.txt" )));
ServerSocket that will
while(( line = in.readLine()) != null )
wait for requests
theBye.addElement( line );
in.close();
theSocket = new ServerSocket(PORT);
while(true){
System.err.println( "Server is ready" );
theClient = new BetterSocket(theSocket.accept());
System.err.println( "Server processing request" );
Use accept() to tell the
String s = theClient.readLine();
ServerSocket to start
System.err.println( "Read " + s + " from client" );
waiting and when a
if( s.equalsIgnoreCase( "hello" ))
message arrives wrap
sendGreeting( theHello );
else if ( s.equalsIgnoreCase( "bye" ))
the bare ServerSocket
sendGreeting( theBye );
with a BetterSocket
else
System.err.println( "Invalid request: " + s );
theClient.close();
System.err.println( "Processing complete" );
Check the request, send
the appropriate response
}
}
catch( Exception ex ){
ex.printStackTrace();
}
}
public void sendGreeting( Vector v )
throws IOException{
int size = v.size();
for( int I=0; I<3; I++ ){
int n = (int)(Math.random() * size );
theClient.println((String) v.elementAt(n) );
}
}
}
Select a random
response from the
vector and send it
Running the CookieServer
Make the CookieServer directory your current directory
In a DOS box compile the files:
javac *.java
In a second DOS box start the server:
java SimpleServer
If the server is working you will see a “Server is ready” message.
Create an HTML file containing the Applet tag which you will find as a comment at the start of
the SimpleClient.java file.
SimpleClient is written as an Applet which you can run using an applet viewer.
Running SimpleClient in the Sun appletviewer:
appletviewer SimpleClient.html
 Note that the Sun appletviewer expects an HTML file as its parameter.
Shut down the server with <ctrl>C
The Socket Calculator
Repeat all the steps for adding a distribution to your Calculator:
 Create a directory for your new package ( “socket” ) (create this in the calc folder)
Put a copy of BetterSocket.java in the directory – modify this file so that it is in the socket
package.
 Compile this file
 Put a copy of calc.PlainCalculatorImpl in the directory. Modify this file so that it will at
least compile:
 Rename the package
 Set a title that you like
 Delete the line that reads local=true. This statement suppresses the dialog that asks the
address of the remote server. You now want to be asked that question!
 Compile this file
Add a distribution type constant to calc.CalculatorImpl - remember that the string you define
must exactly match the name of the package directory
 Compile this file
Add a menu entry to calc.DistMenu
 MenuItem declaration
 Place MenuItem in menu
 Add an actionPerformed section
 Compile this file
Modify calc.CalculatorImplFactory:
 Add a case to DistAllowed – the socket distribution will work in either VM. Make sure that
all the checks are linked together with else if statements otherwise only the last of these
statements will take effect!
This much should give you a Socket Calculator entry in the Distribution menu which brings up a
separate Calculator – still a local one though! (make sure you have a classpath pointing to calc
folder)
Writing your Calculator server
This involves the following steps:
Change SimpleServer into a first try at a CalcServer
Use SimpleServer.java as the starting point for your server.
 Give the class and the file an appropriate name ( “CalcServer” ). Change the constructor
call appropriately.
 Make it part of the socket package
 Modify the main method to refer to CalcServer rather than SimpleServer
 Get rid of the Vectors containing the cookies
 Get rid of the code that reads the text files into the Vectors
 Get rid of the sendGreeting() method
 To begin with replace the calls to sendGreeting() with System.out.println calls that will
indicate that a call has been made.
You should now have the bare bones of your first Calculator server but at the moment it still
responds to “hello” and “goodbye” – the command that the SimpleClient sends.
Start your CalcServer in a separate DOS box, current directory at the Calc.java level using the
command:
java socket.CalcServer
Try your server with the SimpleClient – nothing much will happen at the client but you should
see the extra lines of print out on the server.
The next task is to decide on the format of the instructions that your PlainCalculatorImpl (looks
like the client ) will send to your CalcServer. There are various possibilities and they will all need
some form of Java string manipulation:
- “plus 1 2”
- “+ 1 2”
The Java class that will be most useful for manipulating these command strings when they arrive
at the server is the StringTokenizer class. The following fragment from the Java Documentation
shows how to use this class:
StringTokenizer st = new StringTokenizer("this is a
test");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
This will print out the four words “this”, “is”, “a” & “test”
Modify your CalcServer so that the incoming string is used to create a StringTokenizer and then
use the nextToken() method of the StringTokenizer to extract the command ( first token ) from
the incoming string.
Modify the behaviour of the server so that it simply prints out this command.
The SimpleServer that we used as a starting point shuts down the socket after each request which
is exactly what SimpleClient expects. You will need to modify your CalcServer to avoid this:
 Change the way that the while() loop handles the opening and closing of connections:
 In SimpleServer a new connection was established for each request and these two lines
are inside the while() loop:
|
System.err.println("Server is ready.");
theClient = new BetterSocket(

theSocket.accept());
These lines need to be placed before the while() loop in order that the original
connection is used throughout the session.
The closing of the socket should be removed altogether.
Compile this class
Use calc.PlainCalculatorImpl to begin socket.PlainCalculatorImpl
You are now ready to start work on your socket.PlainCalculatorImpl class.
 This class needs to implement the two abstract methods newConnection() and
reuseConnection() that are defined in calc.CalculatorImpl. The purpose of these methods is
to ensure that a socket is opened the first time you use the calculator but that it is then reused
for each new calculator that you bring up using the menus.
 The newConnection() method needs to open a connection to the server using the hostname
or IP address that the user has entered and that has been passed in via the host parameter
 Declare a variable of type BetterSocket at the head of the class file and set it to null. By
putting it at the head of the file you make sure that it will stick around.
 Define a PORT constant exactly the way that your CalcServer does. If the port values
are not the same there will be no communication.
 These are both defined at the head of the file
 Use a try/catch to give variable a value
try{
theSocket = new BetterSocket(host, PORT);
return true;
}
catch( Exception uh ){
System.out.println( uh );
return false;
}
You are forced to use a try/catch because there might be an error on the network and
returning true makes sure that you will not be called again. Note that the try/catch is
used to cause the correct true/false value to be returned.
The above code is put within the newConnection() function
 The reuseConnection() simply retrieves the BetterSocket from the previous calcualtor and
assigns this to the variable theSocket. The parameter to this method is declared as being of
type calc.CalculatorImpl but what is being passed in will be an instance of a sub-class ( eg
socket.PlainCalculatorImpl ). We will need to cast the parameter to be of the more
restrictive type in order that we can retrieve a BetterSocket from it:
theSocket = //we are using the passed-in value
((PlainCalculatorImpl) impl) //cast impl
.theSocket; //so we can retrieve socket

The action for add() will look something like what is below. We are not trying to return a
result from the server yet – simply to send the command so we are returning 42 to remind
you that there is still work to do!
:
public double add( double a, double b ){
try{
theSocket.println("plus " + a + “ “ + b );
}
catch( Exception uh ){
System.out.println( uh );
}
return( 42);
}
Notice the spaces to separate the command and the parameters.
Change this action for add, subtract, divide and multiply, in socket.PlainCalculatorImpl.java.
 If you fire up a Calculator now and select the Socket Distribution you should see the
commands printed out on the server screen.
CalcServer is the server, Calc.java launches the client interface in the form of a GUI
calculator. So run the server first and then run the client.
Fix up the server so that it returns the result of a calculation



What remains for you to do is to cause the server to return a value to the
PlainCalculatorImpl. The actual returning of the value requires a BetterSocket
readLine() call but the value needs to be passed as a String. The hard part about this is
keeping track of numeric values as they move between the basic double type and the
Java String class.
The key to this is the Java Double class:
If you have a String and you want a double you need to create a Double object from the
String and get the value with the doubleValue() method:
double x = (new Double( xAsString)).doubleValue();
To go in the opposite direction you need to create a Double object from the double and then use
the toString() method:

String xAsString = (new Double(x)).toString();

Using this approach you will need to modify your CalcServer and PlainCalculatorImpl so that the
calculation is actually performed remotely.
2. Lab 2-B: Advanced - Developing the Socket-Based Calculator server
Adding Threading behaviour to your Server
The Socket-based calculator server that you developed last week has a major draw-back – only
one use can access the server at a time. The first exercise this week is to make this server multithreaded
To accomplish this you need to become really clear about the series of states that the server
moves through whilst it is servicing clients, and the relation of these states to the code you are
executing.
Your server will also need to be re-defined, as described in the lecture, as a class that implements
Runnable.
This is a pseudo code description of a multi-threaded server along with the Java code that
corresponds to each step.
Forever
Accept a new client
Socket s = theSocket.accept();
Start a new thread for that client (new CalcServer(s)).start();
Until client quits
run() method in CalcServer
Do calculations
End Until
Kill Client Thread
End Forever
You will need to make the following ( general ) changes to your socket.CalcServer:
Make the class implement the Runnable interface:
 Add the implements statement
 Add a public void run() method. This method needs to include all the existing code that
processes a single calculation.
Broadly speaking this will begin with the readLine() request, through to the "Processing
complete” message, along with the while(true) construct so that your server will keep on
doing calculations.
 You may find it useful to print out the name of the thread that is currently processing a
request. You can do this by adding the following expression to the “Processing request…”
message:
Thread.currentThread()
Get init() organised
What you did above will have reduced your init() method to just a few lines. init() is called when
you first fire up your CalcServer and its behaviour can be seen in the pseudo-code above –
namely to sit waiting for clients to arrive and to fire up a new thread for each client:
 Create a new ServerSocket and say that the server is ready
 In a while(true) loop accept new client connections and fire up a new thread for each
one:
Socket s = theSocket.accept();
new Thread( new CalcServer(s) ).start();
 This will leave a couple of lines of the original code that need to be dealt with:
 The original accept() call. This is now being done in the line above so we can
delete this line.

Write a couple of constructors for the CalcServer class:


One is the constructor that is being called in the new Thread call above. This constructor
is being told how to talk to the new client via the Socket parameter that is being passed
in.
The constructor needs to create the new BetterSocket that will be used to communicate
with the client.
public CalcServer( Socket s ){
try{
theClient = new BetterSocket(s);….
Because we have defined this one constructor we are also forced by the compiler to
define the default constructor – this is used once in the CalcServer main().
public void CalcServer(){. . .
Handle client shutdown



You have probably already noticed that when the client Calculator exits the server
generates an exception. The exception is a SocketException and occurs because the
remote end of an open connection simply “disappeared”. This behaviour, which shuts
the server down, is clearly unacceptable in a multi-threaded, possibly multi-user, server.
The server needs to catch that specific exception and then kill the specific thread that is
handling that connection.
To gracefully kill a thread you need a boolean that the main while() statement keeps
checking. ( instead of while(true)). Changing the value of this variable will cause the
while() loop to exit and bring this thread to the end of its run() method. At this point the
JVM will gracefully clean up.
The response to catching the SocketException clearly needs to be to set this boolean
appropriately.
A Socket-Based Statistical Calculator
The issue here is to use Object Serialisation to pass the Vector of data items to the server. Of
course there are other ways to do this – such as maintaining the Vector at the server but we are
setting out with two goals in mind:
Making our server based solution to Statistical calculations have the same interface as the local
one we have already implemented
Learning to use Object serialisation!
The Statistical server
Take a new copy of BetterSocket.java from the Lab2-B directory. This new copy has the
readObj() and writeObj() methods implemented in it that were discussed in lectures. Make sure
that you put the new copy in your socket directory. Compile this file.
 Inspect BetterSocket.java and note that readLine() and println() now also use the
underlying socket as an Object stream. It is not possible to mix Objects and plain data on a
Java Socket.
Modify your CalcServer.java so that it recognises the two new statistical commands “sum” and
“average”. These commands will be passed to the server as Strings, just as before, but they will
be followed by the Vector object rather than by the extra Strings that were used by the
PlainCalculator.
Depending on how you wrote your server you will need to take account of the different way in
which these two sets of commands work. It is no good calling nextToken() when the next thing is
a serialised object.
This method call, on the modified BetterSocket, will read in the Vector that is being passed:
Vector v = (Vector)theClient.readObj();
Arrange that the server performs the necessary calculations with this Vector. You can copy the
code fragments for this from calc.StatCalcualtorImpl
You have a choice as to whether you send the result back to the client as before ( as a String ) or
to use your new ability to serialise objects by sending a Double.
The Statistical Impl
Save a copy of calc.StatCalculatorImpl in your socket directory and begin to modify it:
 Change the package
 Make it extend the PlainCalculatorImpl class that you wrote in the last lab.
 Make sure that implements calc.StatCalculatorInterface
 Whenever we select an interface from the menu ( eg Statistical ) we will already have made
a particular distribution choice. This means that the Statistical calculator will never need to
make a new connection to the server – only reuse the one that was there. Hence we only
need implement resueConnection – we can borrow the code from our PlainCalcualtorImpl.
 Your methods will now send the Vector to the server – you can delete the code for
summing and averaging the vector – this is now being done at the server! To send an object
you use the writeObj( Object )of the new BetterSocket.
3. Report (2-3 pages):
1. Describe the Code: Describe your observations on socket based distribution (What it
is? How to achieve it? etc) in a report and document all modifications made in the code.
2. Reverse Engineering: Generate the class diagram of the codes (Lab2-A, Lab2-B)
using the Rational Rose and include the screenshots in this report. Describe the strength
and the weakness of the implementation design.
3. Improvement:
Identify and describe new interesting features to be added to the implementation design
(Lab2-A, Lab2-B).
Download