A Network Server Class

In the socket networking model, the server side has to read from or write to many sockets that are connected to many clients. We already know that by reading data from a socket in a separate thread, we solve the problem of hanging while we’re waiting for data. Threading on the server side has an additional benefit: by having a thread associated with each client, we no longer need to worry about other clients within any single thread. This simplifies our server-side programming: we can code our classes as if we were handling a single client at a time.

In this section, we’ll develop such a server. But before we dive right in, let us review some networking basics.

Figure 5.2 shows the data connections between several clients and a server. The server-side socket setup is implemented in two steps. First, a socket is used for the purpose of listening on a port known to the client. The client connects to this port as a means to negotiate a private connection to the server.

Network connections between clients and server

Figure 5-2. Network connections between clients and server

Once a data connection has been negotiated, the server and client then communicate through this private connection. In general, this process is generic: most programmers are concerned with the data sockets (the private connection). Furthermore, the data sockets on the server side are usually self-contained to a particular client. While it is possible to have different mechanisms that deal with many data sockets at the same time, generally the same code is used to deal with each of the data sockets independently of the other data sockets.

Since the setup is generic, we can place it into a generic TCPServer class and not have to implement the generic code again. Basically, this TCPServer class creates a ServerSocket and accepts connection requests from clients. This is done in a separate thread. Once a connection is made, the server clones (makes a copy of) itself so that it may handle the new client connection in a new thread:

import java.net.*;
import java.io.*;

public class TCPServer implements Cloneable, Runnable {
    Thread runner = null;
    ServerSocket server = null;
    Socket data = null;
    volatile boolean shouldStop = false;

    public synchronized void startServer(int port) throws IOException {
        if (runner == null) {
            server = new ServerSocket(port);
            runner = new Thread(this);
            runner.start();
        }
    }

    public synchronized void stopServer() {
        if (server != null) {
            shouldStop = true;
            runner.interrupt();
            runner = null;
            try {
                server.close();
            } catch (IOException ioe) {}
            server = null;
        }
    }

    public void run() {
        if (server != null) {
            while (!shouldStop) {
                try {
                    Socket datasocket = server.accept();
                    TCPServer newSocket = (TCPServer) clone();

                    newSocket.server = null;
                    newSocket.data = datasocket;
                    newSocket.runner = new Thread(newSocket);
                    newSocket.runner.start();
                } catch (Exception e) {}
            }
        } else {
            run(data);
        }
    }
 
    public void run(Socket data) {
 
    }
}

Considering the number of threads started by the TCPServer class, the implementation of the class is simple. First, the TCPServer class implements the Runnable interface; we will be creating threads that this class will execute. Second, the class is cloneable, so that a copy of this class can be created for each connection. And since the copy of the class is also runnable, we can create another thread for each client connection. Since the original TCPServer object must operate on the server socket, and the clones must operate on the data sockets, the TCPServer class must be written to service both the server and data sockets.

To begin, once a TCPServer object has been instantiated, the startServer() method is called:

public synchronized void startServer(int port) throws IOException {
    if (runner == null) {
        server = new ServerSocket(port);
        runner = new Thread(this);
        runner.start();
    }
}

This method creates a ServerSocket object and a separate thread to handle the ServerSocket object. By handling the ServerSocket in another thread, the startServer() method can return immediately, and the same program can act as multiple servers. We could have performed this initialization in the constructor of the TCPServer class; there’s no particular reason why we chose to do this in a separate method.

The stopServer() method is the cleanup method for the TCPServer class:

public synchronized void stopServer() {
    if (server != null) {
        shouldStop = true;
        runner.interrupt();
        runner = null;
        try {
            server.close();
        } catch (IOException ioe) {}
        server = null;
    }
}

This method cleans up what was done in the startServer() method. In this case, we need to terminate the thread we started; we do that by setting the flag that will be checked in the run() method. In addition, we interrupt that thread, in case the runner thread is hanging in the accept() method. Finally, we close() the socket that the thread was working on.

We also set the runner variable to null to allow the object to be reused: if the runner variable is null, the startServer() method can be called later to start another ServerSocket on the same port or on a different port.

Notice that the stopServer() method also checks to see if the server variable is null before trying to stop the server. The reason for this is that the TCPServer object will be cloned to handle the data sockets. Since this clone handles a data socket, we set the server variable to null in the clone. This extra check is done just in case the programmer decides to execute the stopServer() method from the clone instance that is handling a data socket.

The bulk of the logic comes in the run() method:

public void run() {
    if (server != null) {
        while (!shouldStop) {
            try {
                Socket datasocket = server.accept();
                TCPServer newSocket = (TCPServer) clone();

                newSocket.server = null;
                newSocket.data = datasocket;
                newSocket.runner = new Thread(newSocket);
                newSocket.runner.start();
            } catch (Exception e) {}
        }
    } else {
        run(data);
    }
}

What is interesting about this class is that the run() method contains some conditional code. Since the server instance variable is set in the startServer() method, the if statement in the run() method always succeeds. Later, we will be cloning this TCPServer object and starting more threads using the clone. The conditional code differentiates the clone from the original.

The handling of the ServerSocket is straightforward. We just need to accept() connections from the clients. All the details of binding to the socket and setting up the number of listeners are handled by the ServerSocket class itself. Once we have accepted a network connection from a client, we once again have a situation that benefits from threading.

However, in this case, instead of using a different Runnable class, we use the TCPServer class: more precisely, we clone our TCPServer object and configure it to run as a runnable object in a newly created thread. This is why the TCPServer’s run() method checks to see if a ServerSocket object is available or not. The reason we cloned our TCPServer object was so we can have private data for each thread. By making a copy of the object, we make a copy of the instance variables that can then be set to the values needed by the newly created thread.

All code that handles the ServerSocket is in the while loop of the run() method. The rest of the run() method handles the client data socket:

public void run() {
    if (server != null) {
        ...
    } else {
        run(data);
    }
}
 
public void run(Socket data) {
}

The newly created thread running with the newly cloned runnable object first calls the run() method; for a data socket, the run() method just calls the overloaded run(data) method. As can be seen from the code, this run(data) method does absolutely nothing; using the TCPServer class by itself does nothing with the data sockets. To have a useful TCPServer, you must extend it:

import java.net.*;
import java.io.*;

public class ServerHandler extends TCPServer {
    public void run(Socket data) {
        try {
            InputStream is = data.getInputStream();
            OutputStream os = data.getOutputStream();

            // Process the data socket here.
        } catch (Exception e) {}
    }
}

All we need to do in our subclass is override the run(data) method; we only need to handle one data socket in the run(data) method. We do not have to worry about the ServerSocket or any of the other data sockets. When the run(data) method is called, it is running in its own thread with its own copy of the TCP-Server object. All the details of the ServerSocket and the other data sockets are hidden from this instance of the TCPServer class.

Once we have developed a specific version of the TCPServer class (in this case, the ServerHandler class), we create an instance of the class and start the server. An example usage of the ServerHandler class is as follows:

import java.net.*;
import java.io.*;

public class MyServer {
    public static void main(String args[]) throws Exception {
        TCPServer serv = new ServerHandler();

        serv.startServer(300);
    }
}

Using this ServerHandler class is simple. We just need to instantiate a TCPServer object and call its startServer() method. Since the ServerHandler object is also a TCPServer object, it behaves just like a TCPServer object; the only difference is that each data socket will have code that is specific to the ServerHandler class executed on its behalf.

What other threading issues, most notably synchronization issues, are we concerned with in our TCPServer class? Basically, there are no issues we have not already seen. The startServer() and stopServer() methods are synchronized because they examine common instance variables that may change. The run() method does not have to be synchronized because the startServer() method is written to guarantee that the run() method is called only once.

Since all the calls to the run() method in each connection are done in a clone() of the TCPServer object, there is no reason to synchronize the data socket threads because they will be changing and examining different instances of the TCP-Server class. The separate threads that handle the data sockets are not sharing data and hence do not need to be synchronized. And if the ServerHandler class needed to share data, then the synchronization that would be done would be in the ServerHandler or one of its supporting classes.

In this example, we used the Runnable interface technique. Could we have derived from the Thread class directly instead of using the Runnable interface? Yes, we could have. However, using the Runnable interface makes it possible for the TCPServer class to start another thread with a clone of itself. Deriving from the Thread class requires a different implementation. This implementation probably requires that a new TCPServer class be instantiated instead of simply cloned.

We are not keeping a reference of the “data socket” thread objects anywhere; is this a problem? It is not a problem. As noted earlier, the threading system keeps an internal reference to every active thread in the system. As long as the stop() method has not been called on the thread or the run() method has not completed, the thread is considered active, and a reference is kept somewhere in the threading system. While removing all references to a thread object prevents the TCPServer from arranging for this data socket thread to terminate, the garbage collector cannot act on the thread object because the thread system still has a reference to it.

Have you noticed that it is difficult to tell that the ServerHandler class and the MyServer class are threaded? This is the goal that we have been trying to achieve. Threads are a tool, and the threading system is a service. In the end, the classes we create are designed to accomplish a task. This class, if designed correctly, does not need to show what tools it is using. Our ServerHandler class just needs to specify code that will handle one data socket, and the MyServer class just needs to start the ServerHandler service. All the threading stuff is just implementation detail. This concept shouldn’t be that surprising: it’s one of the benefits of object-oriented programming.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset