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.)