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

1 2 3 Page 3
Page 3 of 3

The two unimplemented methods shown in Listing 12 check a connection to see whether it is permitted and check disk limitations before writing the article database to disk, respectively.

  Hashtable loadArticles (String dbfn, String t) throws IOException {
    Hashtable returnArticles = new Hashtable();
    if (t == null || t.equals("")) {
      throw new RuntimeException ("You must define at least one discussion 
                                   thread in the configuration file.\n");
    }
    else {
      StringTokenizer strtok = new StringTokenizer (t, ",");
      while (strtok.hasMoreTokens()) 
    returnArticles.put (strtok.nextToken(), new Vector());
    }
Listing 13: ForumServer's loadArticles() method

The loadArticles() method, which is used by setup() to load the article database, creates a Hashtable called returnArticles that will be returned when the method exits.

returnArticles is first loaded with the keys derived by tokenizing the discussion threads listing string t. Each discussion thread is put in as the key for an empty Vector.

    if (dbfn != null && !dbfn.equals("")) {
      File db = new File (dbfn);
      System.out.println ("Threads loaded from filesystem:\n");
      try {
    FileInputStream f = new FileInputStream (db);
    DataInputStream dIn = new DataInputStream (f);
    int numThreads = 0;
      numThreads = dIn.readInt();
      for (int i = 1; i <= numThreads; i++) {
        String thread = dIn.readUTF();
        Vector v = new Vector();
        int numArts = 0;
        numArts = dIn.readInt();
        for (int j = 1; j <= numArts; j++) {
          String art = dIn.readUTF();
          v.addElement (art);
        }
        if (returnArticles.get (thread) != null) {
          returnArticles.put (thread, v);
          System.out.println ("Thread '" + thread + "' picked 
                                   up from database file.\n");     
        }
        else 
          System.out.println ("Thread '" + thread + "' ignored.\n");
      }
      } catch (IOException ex) {
    System.out.println ("Exception '" + ex + "' encountered while 
                             attempting to load articles from 
                             database " + dbfn + ".\n");
      }
    }
    return returnArticles;
  }
Listing 14: ForumServer's loadArticles() method

If an article database file name has been passed in as a parameter, the method next tries to deserialize an articles database from the file, as shown in Listing 14. A correctly formatted database file begins with an int that tells the number of threads it contains. Each thread is then listed, followed by the number of articles that it has, followed by the articles themselves. Any IOException that arises during the operation is caught -- throwing it would cause the run() method to exit.

The for loop loads each thread from the file system database. When the thread has been loaded, it is checked against returnArticles, which already has the correct list of threads taken from the config file. Threads read in from the file system that are not defined in the config file are ignored.

  void dumpArticlesToDatabase (String dbfn) throws IOException {
    if (articlesIsEmpty (articles)) {
      System.out.println ("No articles to save to database file.\n");
      return;
    }
Listing 15: ForumServer's dumpArticlesToDatabase() method

Not surprisingly, the dumpArticlesToDatabase() method, shown in Listing 15, is more or less the mirror image of the loadArticles() method. I won't go into detail here.

    int numThreads = articles.size();
    Enumeration threads = articles.keys();
    File db = new File (dbfn);
    DataOutputStream dOut = null;
    try {
      FileOutputStream f = new FileOutputStream (db);
      dOut = new DataOutputStream (f);
     // record the number of threads
      dOut.writeInt (numThreads);
      // put each thread in, followed by its articles
      while (threads.hasMoreElements()) {
        String curThread = (String) threads.nextElement();
        dOut.writeUTF (curThread);
    Vector curThreadArts = (Vector) articles.get (curThread);
        dOut.writeInt (curThreadArts.size());
    Enumeration allArts = curThreadArts.elements();
    while (allArts.hasMoreElements()) {
      String art = (String) allArts.nextElement();
      dOut.writeUTF (art);
    }
      }
      System.out.println ("Article database written to " + dbfn + ".\n");
    }
    catch (IOException ex) {
      throw new IOException ("\nException " + ex + " encountered while 
                              attempting to write to file " + dbfn + ".\n");
    }
    finally {
      try {
      if (dOut != null)
    dOut.close();
      } catch (IOException ex) {
        throw new IOException ("Exception " + ex + " was encountered while 
                                attempting to close file " + dbfn + ".\n");
      }
    }
  }
Listing 16: Saving threads to a file

The code in Listing 16 simply loops through the threads listed in the articles database and attempts to save them to the file dbfn in the correct format.

  boolean articlesIsEmpty (Hashtable a) {
    boolean result = true;
    if (a == null || a.size() == 0) 
       return true;
    Enumeration en = a.keys();
    while (en.hasMoreElements()) {
      Vector threadArts = (Vector) a.get (en.nextElement());
      if (threadArts.size() > 0) {
    result = false;
    break;
      }
    }
    return result;
  }
Listing 17: Checking for articles

This last method, articlesIsEmpty(), is simply a handy check to see if an article database has any articles in any of its threads. The Hashtable size() method won't do for this because it counts only the number of keys.

Conclusion

Pat yourself on the back. You've just created a killer, albeit simple, discussion forum. The

ForumComm

,

ForumConnectionHandler

, and

ForumServer

classes we examined today provide a basic networking and server package for the Interchange discussion system. Of course, you don't need to stop here. Have some fun with this project. Spruce it up and make it your own. You can easily extend these basic classes to provide more functionality. For example, you can code

ForumComm

to be multithreaded so that posts will not block, which means that the client has to wait (meaning that the user also has to wait) on the completion of a post attempt to the server. Along these same lines, a more advanced enhancement would be to make the Forum client itself multithreaded so that I/O wouldn't block the client. You can further extend the system to process more types of requests by adding new

case

clauses to both

ForumComm

and

ForumConnectionHandler

. This approach will help you to overcome the biggest limitation of our little system -- the massive download of all of a thread's articles when the thread is selected.

Michael ShoffnerShoffner is VP of strategic development at Prominence Dot Com. He is co-author of Java Network Programming (Manning), and Java Applets and Channels without Programming, due out from Manning Publications in March of this year. In his free time, Michael likes to go to theaters to see Star Wars, picking up where he left off 17 years ago when it finally stopped showing the first time. Java Step By Step is a monthly column devoted to Java development. Each month, the authors of Step by Step will guide you through the development of a real-world Java application. Step By Step is co-authored by Michael Shoffner, Maria Winslow, and Merlin Hughes, founders of Prominence Dot Com, a Java development firm in Chapel Hill, NC.

Learn more about this topic

Previous Step By Step articles

1 2 3 Page 3
Page 3 of 3