Lab 4 - Advanced - Developing the Socket

advertisement
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.
Download