Scalability
When the demand on a server increases and decreases, it is desirable to change the resources dedicated to the server. The options available range from the use of manual threads to allow concurrent behavior to those embedded in specialized classes to handle thread pools and NIO channels.
Creating a threaded server
In this section, we will use threads to augment our simple echo server. The definition of the ThreadedEchoServer
class is as follows. It implements the Runnable
interface to create a new thread for each connection. The private Socket
variable will hold the client socket for a specific thread:
public class ThreadedEchoServer implements Runnable { private static Socket clientSocket; public ThreadedEchoServer(Socket clientSocket) { this.clientSocket = clientSocket; } ... }
Note
A thread is a block of code that executes concurrently with other blocks of code in an application. The
Thread
class supports threads in Java. While there are several ways of creating threads, one way is to pass an object that implements the Runnable
interface to its constructor. When the Thread
class' start
method is invoked, the thread is created and the Runnable
interface's run
method executes. When the run
method terminates, so does the thread.
Another way of adding the thread is to use a separate class for the thread. This can be declared separate from the ThreadedEchoServer
class or as an inner class of the ThreadedEchoServer
class. Using a separate class, better splits the functionality of the application.
The main
method creates the server socket as before, but when a client socket is created, the client socket is used to create a thread, as shown here:
public static void main(String[] args) { System.out.println("Threaded Echo Server"); try (ServerSocket serverSocket = new ServerSocket(6000)) { while (true) { System.out.println("Waiting for connection....."); clientSocket = serverSocket.accept(); ThreadedEchoServer tes = new ThreadedEchoServer(clientSocket); new Thread(tes).start(); } } catch (IOException ex) { // Handle exceptions } System.out.println("Threaded Echo Server Terminating"); }
The actual work is performed in the run
method as shown next. It is essentially the same implementation as the original echo server, except that the current thread is displayed to clarify which threads are being used:
@Override public void run() { System.out.println("Connected to client using [" + Thread.currentThread() + "]"); try (BufferedReader br = new BufferedReader( new InputStreamReader( clientSocket.getInputStream())); PrintWriter out = new PrintWriter( clientSocket.getOutputStream(), true)) { String inputLine; while ((inputLine = br.readLine()) != null) { System.out.println("Client request [" + Thread.currentThread() + "]: " + inputLine); out.println(inputLine); } System.out.println("Client [" + Thread.currentThread() + " connection terminated"); } catch (IOException ex) { // Handle exceptions } }
Using the threaded server
The following output shows the interaction between the server and two clients. The original echo client was started twice. As you can see, each client interaction is performed with a different thread:
Threaded Echo Server
Waiting for connection.....
Waiting for connection.....
Connected to client using [Thread[Thread-0,5,main]]
Client request [Thread[Thread-0,5,main]]: Hello from client 1
Client request [Thread[Thread-0,5,main]]: Its good on this side
Waiting for connection.....
Connected to client using [Thread[Thread-1,5,main]]
Client request [Thread[Thread-1,5,main]]: Hello from client 2
Client request [Thread[Thread-1,5,main]]: Good day!
Client request [Thread[Thread-1,5,main]]: quit
Client [Thread[Thread-1,5,main] connection terminated
Client request [Thread[Thread-0,5,main]]: So long
Client request [Thread[Thread-0,5,main]]: quit
The following interaction is from the first client's perspective:
Simple Echo Client
Waiting for connection.....
Connected to server
Enter text: Hello from client 1
Server response: Hello from client 1
Enter text: Its good on this side
Server response: Its good on this side
Enter text: So long
Server response: So long
Enter text: quit
Server response: quit
The following interaction is from the second client's perspective:
Simple Echo Client
Waiting for connection.....
Connected to server
Enter text: Hello from client 2
Server response: Hello from client 2
Enter text: Good day!
Server response: Good day!
Enter text: quit
Server response: quit
This implementation permits multiple clients to be handled at a time. Clients are not blocked because another client is using the server. However, it also allows a large number of threads to be created. If there are too many threads in existence, then server performance can degrade. We will address these issues in Chapter 7, Network Scalability.