Lab 4 - 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 multi-threaded 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 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 multithreaded, 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()(the while in the run method) 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. So instead of: while(true) { try { … } catch (Exception e) { … } } you could have something like: int i =0; while(i!=1) { try { … } catch (Exception e) { i=1; ….. } } 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 Lab4 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. (If you want to send back a Double, use the writeObj method of theClient object) The Statistical Impl Save a copy of calc.StatCalculatorImpl in your socket directory and begin to modify it: Change the package to socket. 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. (So when you test the calculator, first you need to select the socket distribution so that you connect to the server. Then this connection will be reused when you select and work with the stat interface). 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. (In the implementation of these methods, you need to send the appropriate messages to the server to either “sum” or “average” your data. It is up to you to decide what format to send the data in, but make sure you read it accordingly at the server in the CalcServer file. For instance you can send the data in two parts. First send the string “sum” and then the vector containing the data to sum. Accordingly you must do a readLine() and then do a readObj() on the client socket at the server. In the above case, you may want to put the “plus”, “minus”, “multiply” and “divide” implementations after the “sum” and “average” implementations at the server, in order to avoid confusion that can occur with the use of nextToken() function. For “sum” and “average” there is no nextToken() in the string, so you need readObj() instead. For the sum function, if you are sending back a “Double” from the server, instead of a string, if you want, you can use the following, to read the sum in the “sum” method at the client in the StatCalculatorImpl file: sum_received_double= ((Double)theSocket.readObj()).doubleValue(); and then return sum_received_double. You can similarly add code for the average method. Report on your experiments with the Advanced Socket-Based Calculator Distribution The guideline will be provided shortly.