The University of Adelaide Australia

The University of Adelaide

School of Computer Science

Computer Science 1B

The Client-Server Practical

Due Date

Close of business, Tuesday, Week 5. This practical is to be checked by your practical supervisor. For more details see the handin instructions at the bottom of this document.

Introduction

In the lectures we have talked about Java I/O streams and how protocols like HTTP work. In this practical, you will build a simple client/server system in which the server takes “commands” from the client and executes them.

This is something like the HTTP protocol and somewhat like the Bank example in Chapter 22 of the text book (but is neither!).

This document has a section on how I'd go about implementing a solution to this problem. You don't need to follow my suggestion, but if this is the first client/server application you've ever built, it might provide some guidance on how to go about thinking about it. The how-to framework is roughly the equivalent of design fragments I'd be doodling in a lab notebook.

The Server

Your server is to be an on-line shop. Your shop has various goods for sale and your client is to connect to the shop, select some goods to be purchased and then purchase them.

Your list of goods and prices is entirely up to you. You should have at least 5 in your shop. You could use a Java array and initialise it in the program, or better still – read it in from a file! The choice is yours.

Of course, in real life, your server would actually be part of a web based application, perform some serious security checks on the purchasors etc. We will conveniently ignore this!

The Client

You should only implement the client if you have enough time. The server is enough work for a week and if you get it working properly, that's good enough. The main reason you'd want the client (apart from having a client) is to test your implementation of the protocol. This is no small feat.

Your client is to list the goods available from the shop and permits the user to select any of the goods and then purchase them. You can make the client/user interface a GUI if you feel the need, or a simple text based interface (reading from System.in). The client should permit the user to select a number of goods.

Read on for what to do before you actually have a client.

The Client/Server Protocol

There are a limited number of messages that the client and server can exchange. They are formatted simply and are all text based. All messages have the same format: a 4 character message identifier followed by some optional strings. Every message is acknowledged by an HTTP style response: 200 OK for success, 400 <error message> for failure. As in most of the real networking protocols, you can send extra information in the OK responses (that is ignored). See the exampe.

Client to Server Messages

HELO id – Initiates connection with the server. Id is your customer identifier.

LIST – requests a list of products available. These come back 1 per line in triples (prod, cost, description) from the server. The list is terminated by a blank line (as in HTTP).

PRCH prod - Asks the server to add product prod to your shopping basket.

COMT – Commits the transaction. Causes purchase of all the goods in your basket. Server responds with a total purchase price for all the goods purchased.

QUIT – Terminates the session.

In the above descriptions, all product identifiers and customer identifiers are integers as is the purchase price (the price is in cents).

Server to Client Messages

The server never sends any messages to the client, except in response to a client command. Every client command must be acknowledged (either positively or negatively) – this includes the QUIT command.

Example Session

Here is an example of the set of messages exchanged between the client and the server. Client messages are in blue italics, server responses in plain type.

HELO 42  
200 OK G'Day customer #42, pleased to see you again at the Bakery!
LIST 
200 OK
1 100 Fresh Dinner Roll
2 50 Stale Dinner Roll from Yesterday
3 200 French Style Croissant
4 300 Sticky bun
5 300 Cream Cake
6 20 After Dinner Mint (They are only wafer thin)
This is a blank line
PRCH 7
400 Error – no such product
PRCH 1
200 OK You've bought a Fresh Dinner Role
COMT 
200 OK
100
This is a blank line
QUIT
200 OK Please Call Again!



The lines in red are precisely what they say – they are actually blank lines, but I wanted to ensure you saw them on this document. Blank lines look a bit anonymous otherwise!

HowTo

So, how to go about this? Well, there are two programs you need to write – the client and the server. I'll start with the server. The server reads it's instructions from an I/O stream – so we will need to create a socket and read from that with a BufferedInputStream. Our responses go back the same way. Both commands and responses are text – big hint that.

The Server

So I'd be thinking along the lines of something like this for the server:

ServerSocket server = new ServerSocket(PORT);
Socket s= server.accept();
// We now have a client on the end of the socket s
BufferedReader in = new BufferedReader( new InputStreamReader( s.getInputStream()));
PrintWriter out = new PrintWriter(s.getOutputStream());

These 4 lines of Java set up a server running on port PORT on the local computer, make this server wait for a connection from a client and then create a character based input stream from the client (in) and an output stream to the client (out). You need a port number, and this can't be a number < 1024. Choose something large, but be aware that the ServerSocket class might raise an exception if the port number you have chosen is already in use. Sadly, there is little you can do to ensure you have chosen an unused port number. These numbers must be < 65535.

Now, commands to the server have a simple format, so we can read them in as a string, and then maybe use the string tokeniser to deal with them, perhaps?

String line = in.readLine();
StringTokenizer tok = new StringTokenizer(line);
String command = tok.nextToken();

So now we have the command, we can decode what it is:

if (command.equals(“HELO”)) {
  ...

and so on. So developing the server doesn't look too hard does it? You'd need to write a loop around the stuff that reads in lines etc to make it real, but the code above looks like it'd work (note that I haven't tested this or cut-and-pasted from a solution – these are simply stream-of-consciousness lines of code!).

In implementing any real program, you don't go for a working solution straight away. The Java code I developed after writing the above is actually 93 lines of code for the server. Even such a small program demands careful implementation. The basic structure is clear: create a socket, get commands from the socket and write responses back there, then close the socket when you're done. The difficulty is in the detail of the protocol. I'd implement this command by command. Implement the HELO command and make sure it works (see the testing section). Then the QUIT (or maybe the HELO and QUIT at the same time, so you can QUIT!). Once you have your server accepting commands and generating responses, you can then implement the remaining commands. Be methodical – ensure you have the basic infrastructure working – then you are adding new functionality to code that already works. Working with a partial implementation is far better for the soul than battling Java and your design simultaneously. This process of step-wise implementation is precisely what every member of staff in the CS school does. It is not the “long way around” in writing code. It also means that should you run short of time, you'll have something to show the prac supervisors and get some marks!

When you start to type up your server code, you'll either discover (as I did) or follow these instructions that there is a problem with the name space in the Java I/O library. You can't put:

import java.io.*;

at the top of your Java server and use the code fragments I have typed above. Why? Because the Java compiler can not tell which readLine() method to use in the fragment of code that reads the command from the socket. To fix this problem, you can do one of two things, you can: explicitly state which readLine() or explicitly import the right classes. I did the latter:

import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.*; //lazy boy
import java.util.StringTokenizer;

Is a cut-and-paste from my actual working code. If you use this, you won't experience the error message from Java complaining that it can't resolve the symbol readLine().

Testing the Server

This is the next step I'd be implementing. This is a breeze – just use telnet like we did in lectures:

telnet localhost PORT

where PORT is the port number you have chosen for your server. You are now the client – so go on, type the protocol in and see what your server does. This is a nice way of implementing servers, huh?

Of course, you'll need to set your server running in another window, to connect to it! Don't forget that!

The other thing you might like to do when testing is to make the server print out the messages it reads from the socket. This gives you some re-assurance that things are at least getting to the server.

System.out.println(“Server received [“ + line + “]”);

is what I'd add. Why the []'s? Simply to delimit the string that was read – this makes it easy to spot extra spaces or things like that which might cause problems.

Implementing the Client

Well, no hints here. You just write the code to create a socket connected to the server socket; get the products from the server, print them out, prompt the user for what to buy etc etc. The client is probably more complex than the server! The telnet interaction with the server is enough for the prac.

A Sample

My 93 line Java program that implements the shop server operates on port 7777 can be copied from here. This is a Java jar file – so you run it like so:

java -jar server.jar

This program has no handling of error conditions (it just throws the exception and dies, which is somewhat untidy). You can run this and interact with it via telnet to see how a rough implementation of the protocol works. This particular server continuously loops back and waits for client connections, so you'll need to abort the server when you're done running it.

Handing it in

You need to get this checked off by a lab supervisor (no matter what you have written). You will receive some marks for partially working solutions, so make sure you get this checked off!

Kevin Maciunas
2005