Lab-3 Description

advertisement
Lab 3 - A Socket-Based Calculator Distribution
The Simple Socket-Based Cookie Server
There is no actual programming involved in this exercise but it will your first sample of
Java socket communication.
Take a copy of the CookieServer directory from the Lab3 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.*;
public class BetterSocket{
Socket theSocket;
BufferedReader theReader;
PrintWriter theWriter;
The actual
socket
The buffered
reader & writer
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(
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);
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
In case we need
direct access to the
socket
readLine and
println for
receiving and
sending data
}
}
//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);
SimpleClient is an Applet
This is the port we will
use
Two buttons & a text
area - simple UI!
BetterSocket theSocket;
public void init(){
setLayout( new BorderLayout());
init() sets out the
Panel p = new Panel();
screen and assigns
add( p, BorderLayout.NORTH );
add( theResponse, BorderLayout.SOUTH );
listeners to the two
p.setLayout( new GridLayout(1,0));
buttons
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 ){
ex.printStackTrace();
that closes the socket
try{
as well
theSocket.close();
}
catch( Exception ex1 ){;}
Hello button pressed so:
}
class HelloHandler implements ActionListener{
Make a socket ( talking to
public void actionPerformed( ActionEvent evt ){
try{
ourselves ), say "Hello"
theResponse.setText("");
and then get three cookies
theSocket = new BetterSocket(
getCodeBase().getHost(), PORT );
theSocket.println( "Hello" );
theResponse.append( theSocket.readLine() + "\n" );
theResponse.append( theSocket.readLine() + "\n" );
theResponse.append( theSocket.readLine() + "\n" );
theSocket.close();
}
catch( Exception ex ){ fatalError(ex); }
Bye button pressed so:
}
Make a socket ( talking to
}
class ByeHandler implements ActionListener{
ourselves ), say "Bye" and
public void actionPerformed( ActionEvent evt ){
try{
then get three cookies
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 ){
new SimpleServer().init();
init() starts by reading the
}
hello & goodbye messages
public void init(){
try{
from the two text files into
BufferedReader in;
String line;
two Vectors
in = new BufferedReader(
new InputStreamReader(
The server is
new FileInputStream( "hello.txt" )));
while(( line = in.readLine()) != null )
an application
theHello.addElement( line );
- not an
in.close();
in = new BufferedReader(
applet.
new InputStreamReader(
This is the
new FileInputStream( "bye.txt" )));
while(( line = in.readLine()) != null )
ServerSocket that will
theBye.addElement( line );
in.close();
wait for requests
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();
System.err.println( "Read " + s + " from client" );
ServerSocket to start
if( s.equalsIgnoreCase( "hello" ))
waiting and when a
sendGreeting( theHello );
else if ( s.equalsIgnoreCase( "bye" ))
message arrives wrap
sendGreeting( theBye );
Check the request, send
the bare ServerSocket
else
System.err.println(
"Invalid
request:
" + s ); response
the appropriate
with a BetterSocket
theClient.close();
System.err.println( "Processing complete" );
}
}
Select a random
catch( Exception ex ){
ex.printStackTrace();
response from the
}
vector and send it
}
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) );
}
}
}
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!
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
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
test");
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 + “ “ +
}
catch( Exception uh ){
System.out.println( uh );
}
return( 42);
}
b );
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.
 Report on your experiments with the Socket-Based Calculator Distribution (the
guideline will be provided.)
Download