Serving multiple clients | CodeGuru

Serving multiple clients

Bruce Eckel’s Thinking in Java Contents | Prev | Next The JabberServer works, but it can handle only one client at a time. In a typical server, you’ll want to be able to deal with many clients at once. The answer is multithreading, and in languages that don’t directly support multithreading this means all sorts […]

Written By
CodeGuru Staff
CodeGuru Staff
Mar 1, 2001
4 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

The


JabberServer

works, but it can handle only one client at a time. In a typical server,


you’ll want to be able to deal with many clients at once. The answer is

multithreading,
and in languages that don’t directly support multithreading this means
all sorts of complications. In Chapter 14 you saw that multithreading in Java
is about as simple as possible, considering that multithreading is a rather
complex topic. Because threading in Java is reasonably straightforward, making
a server that handles multiple clients is relatively easy.

The


basic scheme is to make a single


ServerSocket

in the server and call


accept( )

to wait for a new connection. When


accept( )

returns, you take the resulting


Socket

and use it to create a new thread whose job is to serve that particular client.


Then you call


accept( )

again to wait for a new client.

In


the following server code, you can see that it looks similar to the


JabberServer.java

example except that all of the operations to serve a particular client have


been moved inside a separate thread class:

//: MultiJabberServer.java
// A server that uses multithreading to handle 
// any number of clients.
import java.io.*;
import java.net.*;
 
class ServeOneJabber extends Thread {
  private Socket socket;
  private BufferedReader in;
  private PrintWriter out;
  public ServeOneJabber(Socket s)
      throws IOException {
    socket = s;
    in =
      new BufferedReader(
        new InputStreamReader(
          socket.getInputStream()));
    // Enable auto-flush:
    out =
      new PrintWriter(
        new BufferedWriter(
          new OutputStreamWriter(
            socket.getOutputStream())), true);
    // If any of the above calls throw an 
    // exception, the caller is responsible for
    // closing the socket. Otherwise the thread
    // will close it.
    start(); // Calls run()
  }
  public void run() {
    try {
      while (true) {
        String str = in.readLine();
        if (str.equals("END")) break;
        System.out.println("Echoing: " + str);
        out.println(str);
      }
      System.out.println("closing...");
    } catch (IOException e) {
    } finally {
      try {
        socket.close();
      } catch(IOException e) {}
    }
  }
}
 
public class MultiJabberServer {
  static final int PORT = 8080;
  public static void main(String[] args)
      throws IOException {
    ServerSocket s = new ServerSocket(PORT);
    System.out.println("Server Started");
    try {
      while(true) {
        // Blocks until a connection occurs:
        Socket socket = s.accept();
        try {
          new ServeOneJabber(socket);
        } catch(IOException e) {
          // If it fails, close the socket,
          // otherwise the thread will close it:
          socket.close();
        }
      }
    } finally {
      s.close();
    }
  }
} ///:~ 

The


ServeOneJabber

thread


takes the


Socket

object that’s produced by


accept( )

in


main( )

every time a new client makes a connection. Then, as before, it creates a


BufferedReader

and auto-flushed


PrintWriter

object using the


Socket

.


Finally, it calls the special


Thread

method


start( )

,


which performs thread initialization and then calls


run( )

.


This performs the same kind of action as in the previous example: reading


something from the socket and then echoing it back until it reads the special


“END” signal.

The


responsibility for cleaning up the socket must again be carefully designed. In


this case, the socket is created outside of the


ServeOneJabber

so the responsibility can be shared. If the


ServeOneJabber

constructor fails, it will just throw the exception to the caller, who will


then clean up the thread. But if the constructor succeeds, then the


ServeOneJabber

object takes over responsibility for cleaning up the thread, in its


run( )

.

Notice


the simplicity of the


MultiJabberServer

.


As before, a


ServerSocket

is created and


accept( )

is called to allow a new connection. But this time, the return value of


accept( )

(a


Socket

)


is passed to the constructor for


ServeOneJabber,

which creates a new thread to handle that connection. When the connection is


terminated, the thread simply goes away.

If


the creation of the


ServerSocket

fails, the exception is again thrown through


main( )

.


But if it succeeds, the outer


try-finally

guarantees its cleanup. The inner


try-catch

guards only against the failure of the


ServeOneJabber

constructor; if the constructor succeeds, then the


ServeOneJabber

thread will close the associated socket.

To


test that the server really does handle multiple clients, the following program


creates many clients (using threads) that connect to the same server. Each


thread has a limited lifetime, and when it goes away, that leaves space for the


creation of a new thread. The maximum number of threads allowed is determined


by the


final
int maxthreads

.


You’ll notice that this value is rather critical, since if you make it


too high the threads seem to run out of resources and the program mysteriously


fails.

//: MultiJabberClient.java
// Client that tests the MultiJabberServer
// by starting up multiple clients.
import java.net.*;
import java.io.*;
 
class JabberClientThread extends Thread {
  private Socket socket;
  private BufferedReader in;
  private PrintWriter out;
  private static int counter = 0;
  private int id = counter++;
  private static int threadcount = 0;
  public static int threadCount() {
    return threadcount;
  }
  public JabberClientThread(InetAddress addr) {
    System.out.println("Making client " + id);
    threadcount++;
    try {
      socket =
        new Socket(addr, MultiJabberServer.PORT);
    } catch(IOException e) {
      // If the creation of the socket fails, 
      // nothing needs to be cleaned up.
    }
    try {
      in =
        new BufferedReader(
          new InputStreamReader(
            socket.getInputStream()));
      // Enable auto-flush:
      out =
        new PrintWriter(
          new BufferedWriter(
            new OutputStreamWriter(
              socket.getOutputStream())), true);
      start();
    } catch(IOException e) {
      // The socket should be closed on any 
      // failures other than the socket 
      // constructor:
      try {
        socket.close();
      } catch(IOException e2) {}
    }
    // Otherwise the socket will be closed by
    // the run() method of the thread.
  }
  public void run() {
    try {
      for(int i = 0; i < 25; i++) {
        out.println("Client " + id + ": " + i);
        String str = in.readLine();
        System.out.println(str);
      }
      out.println("END");
    } catch(IOException e) {
    } finally {
      // Always close it:
      try {
        socket.close();
      } catch(IOException e) {}
      threadcount--; // Ending this thread
    }
  }
}
 
public class MultiJabberClient {
  static final int MAX_THREADS = 40;
  public static void main(String[] args)
      throws IOException, InterruptedException {
    InetAddress addr =
      InetAddress.getByName(null);
    while(true) {
      if(JabberClientThread.threadCount()
         < MAX_THREADS)
        new JabberClientThread(addr);
      Thread.currentThread().sleep(100);
    }
  }
} ///:~ 

The


JabberClientThread

constructor takes an


InetAddress

and uses it to open a


Socket

.


You’re probably starting to see the pattern: the


Socket

is always used to create some kind of


Reader

and/or


Writer

(or


InputStream

and/or


OutputStream

)


object, which is the only way that the


Socket

can be used. (You can, of course, write a class or two to automate this process


instead of doing all the typing if it becomes painful.) Again,


start( )

performs thread initialization and calls


run( )

.


Here, messages are sent to the server and information from the server is echoed


to the screen. However, the thread has a limited lifetime and eventually


completes. Note that the socket is cleaned up if the constructor fails after


the socket is created but before the constructor completes. Otherwise the


responsibility for calling


close( )

for the socket is relegated to the


run( )

method.

The


threadcount

keeps track of how many


JabberClientThread

objects currently exist. It is incremented as part of the constructor and


decremented as


run( )

exits (which means the thread is terminating). In


MultiJabberClient.main( ),

you can see that the number of threads is tested, and if there are too many, no


more are created. Then the method sleeps. This way, some threads will


eventually terminate and more can be created. You can experiment with


MAX_THREADS

to see where your particular system begins to have trouble with too many


connections.


Contents

|

Prev

|

Next
CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.