Write your own threaded discussion forum: The communications and server components, part 2

Learn how to implement a simple communications protocol to get our forum discussion group up and running

Before we get going on this month's task -- creating the client-side networking and building the server for our Interchange discussion forum -- let's quickly review the work we've completed on our forum at this point.

In last month's edition, we discussed the specifications of the Interchange client -- the applet parameters, the GUI, the modes of operation, and the user's identity -- and worked through the ForumLauncher and Forum classes to implement them. ForumLauncher displays the icon that stores the client app, while Forum presents the GUI and contains the majority of the app's logic. If you haven't already done so, click on the icon at the top of this article to see the discussion forum applet in motion.

I told you we were only going to do a brief review. If you're feeling a bit behind the eight ball on these topics, I recommend that you review last month's installment and brush up before running headlong in to this month's article.

Oh, and if it's Java code you want, you'll find the full source for the forum here.

Developing lines of communication

As we saw last month, our system is going to follow a simple communications protocol to read and post topics. This simple protocol moves thread listings from server to client and articles from client to server (and vice versa). The networking classes (ForumComm, ForumConnectionHandler, and ForumServer), which we'll cover in detail shortly, will implement the following actions:

  • "Load all threads" retrieves all current threads from the server.
  • "Load all articles in thread T" retrieves all the articles in thread T from the server.
  • "Post article A to thread T" posts article A to the server under thread T.

The client drives the server's actions with requests. The client will likely make very few requests over the course of its life; users typically spend the majority of session time reading articles and composing posts. For this reason, as well as the fact that requests are all discreet in nature (as opposed to transmission of real-time data, for example), each client request is serviced in a single connection, which is then closed down. The other alternative -- leaving a connection open between each client and the server for some specified period of time -- would waste precious server resources.

Cutting through the static: Client-side communications

The communications code for the client is bundled up nicely in one class, ForumComm. This class, shown in Listing 1, is a communications library that implements client-side networking calls.

import java.net.*;
import java.util.*;
import java.io.*;
import ForumLauncher;
public class ForumComm {
  // port for server to listen for Forum clients
  static final int FORUM_PORT = 5000;
  // possible client requests
  static final int LOAD_ALL_THREADS = 1;
  static final int LOAD_THREAD_ARTICLES = 2;
  static final int POST_ARTICLE = 3;
  ForumLauncher gp;
  public ForumComm (ForumLauncher gparent) {
    gp = gparent;
  }
Listing 1: The ForumComm class

The top section of the code defines the TCP port used for the Interchange service, as well as the protocol requests that the client can send to the server. These definitions have identical counterpart definitions in the server's ForumConnectionHandler class. The constructor does nothing except provide a pointer to the ForumLauncher applet to allow access to its getCodeBase() method.

Each instance of ForumComm provides three methods: loadAllThreads(), loadThreadArticles(), and postArticle(), shown in Listings 2, 3, and 4. Let's take a look at each of these methods in more detail.

  Hashtable loadAllThreads() {
    Hashtable a = new Hashtable();
    String thread = "";
    try {
      URL serverURL = gp.getCodeBase();
      Socket server = new Socket (serverURL.getHost(), FORUM_PORT);
      InputStream in = new BufferedInputStream (server.getInputStream());
      OutputStream out = new BufferedOutputStream (server.getOutputStream());
      DataInputStream dIn = new DataInputStream (in);
      DataOutputStream dOut = new DataOutputStream (out);
      dOut.writeInt (LOAD_ALL_THREADS);
      dOut.flush();
      thread = dIn.readUTF();
      while (!thread.equals ("")) {
    a.put (thread, new Vector()); 
    thread = dIn.readUTF();
      }
    } catch (IOException ex) {
      System.out.println ("Error reading threads from server.");
    }     
    return a;
  }
Listing 2: The loadAllThreads() method

The loadAllThreads() method returns a Hashtable, which contains the server's discussion topics as keys, each with an empty Vector as its value.

The method first creates an empty Hashtable and tries to set up a Socket to the server's port FORUM_PORT. If the attempt succeeds, loadAllThreads gets the InputStream and OutputStream associated with the Socket and attaches a DataInputStream and DataOutputStream to the respective buffered I/O streams. Buffered I/O streams increase the efficiency of the networking calls.

The method next writes the LOAD_ALL_THREADS request to the DataOutputStream and flushes the stream to make sure that the request is sent immediately. When the server gets the LOAD_ALL_THREADS request, it replies with the discussion threads, ending with an empty string.

loadAllThreads() then does a dIn.readUTF() call for each thread the server sends, exiting when it finds an empty string. Each thread is sent in UTF format. UTF is the best way to communicate textual data between Java clients and servers because it preserves Unicode characters, allowing for the use of a wide range of non-ASCII character sets. Unfortunately, if the server is running under JDK 1.0.2, a bug will prevent it from handling anything but ASCII, Greek, Hebrew, and Arabic characters. (All is not lost, though: The release of JDK 1.1 should be available by the time you read this.)

When a new thread is read from the server, it is put into Hashtable a as a key for a new empty Vector. When all threads have been loaded, the method exits, returning the Hashtable containing the threads. If the loadAllThreads() method fails, the client prints out an error message to the system's Java console and returns an empty Hashtable.

Now let's examine the loadThreadArticles() method.

  Vector loadThreadArticles (String t) {
    Vector ta = new Vector();    
    String art = "";
    try {
      URL serverURL = gp.getCodeBase();
      Socket server = new Socket (serverURL.getHost(), FORUM_PORT);
      DataInputStream dIn = new DataInputStream (in);
      DataOutputStream dOut = new DataOutputStream (out);
      InputStream in = new BufferedInputStream (server.getInputStream());
      OutputStream out = new BufferedOutputStream (server.getOutputStream());
      dOut.writeInt (LOAD_THREAD_ARTICLES);
      dOut.writeUTF (t);
      dOut.flush();
      art = dIn.readUTF();
      while (!art.equals ("")) {
    ta.addElement (art);
    art = dIn.readUTF();
      }
    } catch (IOException ex) {
      System.out.println ("Error reading articles for thread '" + t  + "' 
                           from server.");
    } 
    return ta;
  }
Listing 3: The loadThreadArticles() method

The loadThreadArticles() method creates Vector ta. This Vector stores all articles in the discussion thread as elements. The method then sets up the same data streams that loadAllThreads() uses.

Next, loadThreadArticles() writes the LOAD_THREAD_ARTICLES request to dOut, follows that with a write of the selected thread t to dOut, and flushes the stream to make sure that the request goes out immediately. The server replies with a series of articles.

The method then attempts to read each article as it is sent by the server and add it to ta. When this operation is complete, the method returns ta and exits. If the method fails, the client prints out an error message to the system's Java console and returns an empty Vector.

Finally, we come to postArticle().

  boolean postArticle (String art, String t) {
    try {
      URL serverURL = gp.getCodeBase();
      Socket server = new Socket (serverURL.getHost(), FORUM_PORT);
      InputStream in = new BufferedInputStream (server.getInputStream());
      OutputStream out = new BufferedOutputStream (server.getOutputStream());
      DataInputStream dIn = new DataInputStream (in);
      DataOutputStream dOut = new DataOutputStream (out);
      dOut.writeInt (POST_ARTICLE);
      dOut.writeUTF (t);
      dOut.writeUTF (art);
      dOut.flush();
      return true;
    } catch (IOException ex) {
      System.out.println ("Error posting article in thread '" + t  + "' 
                           to server.");
      return false;
    }
  }
}
Listing 4: The postArticle() method

This method attempts to post an article to the thread t. It performs the same setup as the previous methods and follows that step with a POST_ARTICLE request to the server. The method then sends the post thread t and the article to the server and flushes the stream.

If these operations are successful, the method exits returning true. If the operations fail for some reason, such as a bad network connection, the method exits returning false.

Each ForumComm method provides its own local copy of the streams that it uses. This prevents collisions if more than one method is called simultaneously. Such a problem might occur in a multithreaded version of the client, so I included it in case you choose to optimize the system in such a way. (A little planning in the beginning sure can help you out when you decide to enhance later on!)

That's it for the client side of things. Let's now turn our attention to the server portion of our threaded discussion forum. I'll provide a bit of background before we get into the actual classes that implement the server side of our system.

Making the connection: Creating the server for the forum system

The server we're constructing will include the following features:

  • Multithreaded connection-handling facility
  • Storage and retrieval of article database
  • Configuration via configuration file
  • Administrator shutdown control from a terminal (console window)
  • Connection and status logging to terminal

Note: For tips on using the server, including installation, configuration, and starting and stopping, see the the sidebar Setting up the Interchange forum server.

Our first step is dealing with the connection request made by the client.

Class ForumConnectionHandler

Each time ForumServer (we'll get to the ForumServer class later on in the article) accepts a new connection, the connection is handed off to a new instance of ForumConnectionHandler for processing. ForumConnectionHandler, which is shown in Listing 5, talks directly to ForumComm.

import java.net.*;
import java.util.*;
import java.io.*;
public class ForumConnectionHandler extends Thread {
  // possible client requests
  static final int LOAD_ALL_THREADS = 1;
  static final int LOAD_THREAD_ARTICLES = 2;
  static final int POST_ARTICLE = 3;
  long id;
  Socket client;
  long memoryLimit;
  Hashtable articles;
  InputStream in;
  OutputStream out;
  DataInputStream dIn;
  DataOutputStream dOut;
  public ForumConnectionHandler (long i, Socket c, long m, 
                                 Hashtable ar, ThreadGroup h) {
    super (h, "Forum Connection Handler " + i);
    id = i;
    client = c;
    memoryLimit = m;
    articles = ar;
  }
Listing 5: The ForumConnectionHandler class.

The ForumConnectionHandler class extends Thread so that each instance will map to a new thread and therefore handle its connection separately. Language features like this make writing a server in Java a real treat.

The setup code declares the requests that it may receive from the client, as well as the instance variables to be used in this connection. The constructor sets these to the appropriate values as specified by parameters from the caller.

Each ForumConnectionHandler has an id number, which is the number of the last connection plus one, receives pointers to the connection's Socket, a memory limit restriction, and a pointer to the articles database. ThreadGroup h is passed in, and the superclass (Thread) constructor is called with ThreadGroup h and a text description as arguments. This technique puts all handler threads in the same group so that they can be stopped easily when the server shuts down.

  public void run() {
    try {
      in = new BufferedInputSteam (client.getInputStream());    
      out = new BufferedOutputStream (client.getOutputStream());
      dIn = new DataInputStream (in);
      dOut = new DataOutputStream (out);
      String t, type = "";
      int request = -1;
      Vector threadArts;
      request = dIn.readInt();
      switch (request) {

The run() method of ForumConnectionHandler does all the work involved in processing the connection associated with it. The first order of business is to instantiate the streams (which are buffered for efficiency) to be used for the connection. After that, a String is set up for the thread name and a message to be printed, and a Vector is defined to hold articles in a discussion thread. An int called request is assigned the value of the request read from the client. As soon as the request comes in, a switch statement routes it to the proper case statement.

Let's take a look at the possible requests.

      case LOAD_ALL_THREADS:
    Enumeration en = articles.keys();
    while (en.hasMoreElements()) 
      dOut.writeUTF ((String) en.nextElement());
    dOut.writeUTF("");
    dOut.flush();
    type = "LOAD_ALL_THREADS";
    break;
1 2 3 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more