Multithreading and Multiplexing
3.3 Multithreaded Servers
There is a fundamental and important limitation associated with all the server pro- grams encountered so far:
• they can handle only one connection at a time.
This restriction is simply not feasible for most real-world applications and would render the software useless. There are two possible solutions:
• use a non-blocking server;
• use a multithreaded server.
Before J2SE 1.4, there was no specifi c provision for non-blocking I/O in Java, so the multithreaded option was the only feasible one for Java programmers.
The introduction of non-blocking I/O in 1.4 was a major advance for Java network programmers and will be covered in the latter part of this chapter. For the time being, though, we shall restrict our attention to the more long-standing (and still widely used) implementation of servers via multithreading.
Though inferior to the non-blocking approach, the multithreaded technique has a couple of signifi cant benefi ts:
• it offers a ‘clean’ implementation, by separating the task of allocating connec- tions from that of processing each connection;
• it is robust, since a problem with one connection will not affect other connections.
The basic technique involves a two-stage process:
1. the main thread (the one running automatically in method main ) allocates indi- vidual threads to incoming clients;
2. the thread allocated to each individual client then handles all subsequent interac- tion between that client and the server (via the thread’s run method).
Since each thread is responsible for handling all further dialogue with its particu- lar client, the main thread can ‘forget’ about the client once a thread has been allocated to it. It can then concentrate on its simple tasks of waiting for clients to make connection and allocating threads to them as they do so. For each client- handling thread that is created, of course, the main thread must ensure that the client-handling thread is passed a reference to the socket that was opened for the associated client.
The separation of responsibilities means that, if a problem occurs with the con- nection to a particular client, it has no effect on the connections to other clients and there is no general loss of service. This is a major benefi t, of course.
Example
This is another echo server implementation, but one that uses multithreading to return messages to multiple clients. It makes use of a support class called ClientHandler that extends class Thread . Whenever a new client makes connection, a ClientHandler thread is created to handle all subsequent communication with that particular client.
When the ClientHandler thread is created, its constructor is supplied with a reference to the relevant socket.
Here’s the code for the server…
import java.io.*;
import java.net.*;
public class MultiEchoServer {
private static ServerSocket serverSocket;
private static fi nal int PORT = 1234;
public static void main(String[] args)
throws IOException {
try {
serverSocket = new ServerSocket(PORT);
}
catch (IOException ioEx) {
System.out.println("\nUnable to set up port!");
System.exit(1);
} do {
//Wait for client…
Socket client = serverSocket.accept();
System.out.println("\nNew client accepted.\n");
//Create a thread to handle communication with //this client and pass the constructor for this //thread a reference to the relevant socket…
ClientHandler handler =
new ClientHandler(client);
handler.start();//As usual, method calls run . }while (true);
} }
class ClientHandler extends Thread {
private Socket client;
private Scanner input;
private PrintWriter output;
public ClientHandler(Socket socket) {
//Set up reference to associated socket…
client = socket;
try {
input = new Scanner(client.getInputStream());
output = new PrintWriter(
client.getOutputStream(),true);
}
catch(IOException ioEx)
{
ioEx.printStackTrace();
} }
public void run() {
String received;
do {
//Accept message from client on //the socket's input stream…
received = input.nextLine();
//Echo message back to client on //the socket's output stream…
output.println("ECHO: " + received);
//Repeat above until 'QUIT' sent by client…
}while (!received.equals("QUIT"));
try {
if (client!=null) {
System.out.println(
"Closing down connection…");
client.close();
} }
catch(IOException ioEx) {
System.out.println("Unable to disconnect!");
} } }
The code required for the client program is exactly that which was employed in the TCPEchoClient program from the last chapter. However, since (i) there was only a modest amount of code in the run method for that program, (ii) we should avoid confusion with the run method of the Thread class and (iii) it’ll make a change (!) without being harmful, all the executable code has been placed inside main in the MultiEchoClient program below.
import java.io.*;
import java.net.*;
import java.util.*;
public class MultiEchoClient {
private static InetAddress host;
private static fi nal int PORT = 1234;
public static void main(String[] args) {
try {
host = InetAddress.getLocalHost();
}
catch(UnknownHostException uhEx) {
System.out.println("\nHost ID not found!\n");
System.exit(1);
}
sendMessages();
}
private static void sendMessages() {
Socket socket = null;
try {
socket = new Socket(host,PORT);
Scanner networkInput =
new Scanner(socket.getInputStream());
PrintWriter networkOutput = new PrintWriter(
socket.getOutputStream(),true);
//Set up stream for keyboard entry…
Scanner userEntry = new Scanner(System.in);
String message, response;
do {
System.out.print(
"Enter message ('QUIT' to exit): ");
message = userEntry.nextLine();
//Send message to server on the //socket's output stream…
//Accept response from server on the //socket's intput stream…
networkOutput.println(message);
response = networkInput.nextLine();
//Display server's response to user…
System.out.println(
"\nSERVER> " + response);
}while (!message.equals("QUIT"));
}
catch(IOException ioEx) {
ioEx.printStackTrace();
} fi nally {
try {
System.out.println(
"\nClosing connection…");
socket.close();
}
catch(IOException ioEx) {
System.out.println(
"Unable to disconnect!");
System.exit(1);
} } } }
If you wish to test the above application, you should start the server running in one command window and then start up two clients in separate command windows.
Sample output from such an arrangement is shown in Fig. 3.3 .