★ wanayoo — archive 1999 http://java.oreilly.com/bite-size/java_0299.htmlNouvelle recherche | Portail wanayoo

BITE-SIZE JAVA

Search Product
Index Press
Room

Perl Center
---
Java Center
---
Web Center
---
Linux Center
---
UNIX Center
---
Windows Center
---
Oracle Center
---
Sys Admin Center
---
Security
Center
---
C/C++ Programming Center
---
Design Center
---
O'Reilly Software Online
---
Online
Books Center
---
Web-Based Training
---

O'Reilly

O'Reilly Java Resource Center

Bite-Size Java Shooting Fish in a Barrel
by Jonathan Knudsen

Jonathan Knudsen

People say some crazy things about Java, but a lot of it is true. Java is sometimes called the language of the Internet. This just means that Java has a set of network classes that make it very easy to write applications that communicate over the Internet. How easy? Even in Java 1.0, you could write a Web server in about a page of code. In Java 2, you can write a Web browser in about the same amount of code. If you've ever programmed network applications in other languages, you'll really appreciate this simplicity.

To prove how easy it is to write network applications, I'll show you an Internet chat system. It includes both a server and a client, all in just about 150 lines of code. Along the way, you'll learn about Java's network capabilities as well as threaded programming.

Basic Structure

This chat system consists of two pieces, a client and a server. The server accepts incoming calls from clients (users) and connects them together. Whenever a user types something and sends it to the server, it's the server's job to send the text to everyone who is connected.

Internet connections are made between specific addresses and port numbers. When you run the server, you need to specify what port number it should monitor for incoming connections. To start up the client, on the other hand, you'll have to specify the address of the server and the correct port number. For example, suppose I had a machine whose address was bitesizejava.com. On that machine, I would run the server with the following command line (picking a port number more or less at random):

java Server "Bite-Size Chat Room" 799
This command line runs our Server class, specifying both a name for the server and a port number. Users anywhere on the Internet could connect to this server with a command line like the following:
javaw Client Bubber bitesizejava.com 799
Here, the user specifies a name (Bubber) and the address and port number of the server.

The Server

The Server class is astonishingly brief:

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

public class Server {
  public static void main(String[] args) throws IOException {
    String name = args[0];
    int port = Integer.parseInt(args[1]);
    new Server(name, port);
  }

  private List mClients;
  private String mName;
  
  public Server(String name, int port) throws IOException {
    mClients = new Vector();
    mName = name;
    ServerSocket serverSocket = new ServerSocket(port);
    while (true) addClient(serverSocket.accept());
  }
  
  public void addClient(Socket clientSocket) throws IOException {
    final PrintWriter out =
        new PrintWriter(clientSocket.getOutputStream(), true);
    mClients.add(out);
    
    out.println("[Welcome to " + mName + ".]");
    
    new Listener(clientSocket.getInputStream()) {
      public void processLine(String line) {
        if (line.length() == 0) mClients.remove(out);
        else sendToClients(line);
      }
    };
  }
  
  public void sendToClients(String line) {
    Iterator iterator = mClients.iterator();
    while(iterator.hasNext()) {
      PrintWriter out = (PrintWriter)iterator.next();
      out.println(line);
    }
  }
}

The main() method simply extracts the server name and port number from the command line. Then it creates a new Server.

Server's constructor saves the server name in a member variable, mName. It also initializes mClients, which is a list of output streams representing client connections. Then the constructor creates a ServerSocket. A ServerSocket is something that listens on the network for incoming connections at a particular port. Then the constructor enters its main loop, a single line of code:

    while (true) addClient(serverSocket.accept());
This loop calls the ServerSocket's accept() method, which waits for an incoming connection. When one is received, it returns a Socket that represents the connection to the client. The socket is passed off to addClient() and Server's constructor returns to the ServerSocket's accept() method, waiting for another connection.

Each time a client connection is received, the addClient() method is called. This method creates a PrintWriter that can be used to send data to the client. This PrintWriter is added to the mClients list. Then addClient() sends a welcome message to the client. Finally, an object is created that listens for incoming data from the client. As the data is received, it is sent out to all the clients using the sendToClients() method. If the incoming data is an empty string, the client is disconnecting and is removed from the mClients list.

The last method, sendToClients(), iterates through the mClients list and sends a line of text to each one.

Listener, a Handy Abstract Class

But what about that Listener class we used? Here it is:

import java.io.*;

public abstract class Listener  {
  public abstract void processLine(String line);

  private BufferedReader mIn;
  
  public Listener(InputStream in) {
    mIn = new BufferedReader(new InputStreamReader(in));
    Thread t = new Thread() {
      public void run() {
        try {
          String line;
          while ((line = mIn.readLine()) != null) processLine(line);
        }
        catch (IOException ioe) {}
      }
    };
    t.start();
  }
}

All this class does is listen for incoming data on an InputStream. This work is done in a separate thread so that the rest of the application can carry on. All Listener does is wrap the byte-based InputStream it receives in its constructor in a character-based BufferedReader. Then it simply reads lines of text. Each line is passed to an abstract method, processLine(). In the Server class, a Listener subclass is used to receive lines from each connected client. The processLine() method is defined to send the line back out to all the clients. Later on, in the Client class, you'll see a slightly different use for Listener--each time a line is received from the server, it will be displayed on the screen.

Java makes it easy to put this work in a separate thread. All we do is create a new Thread subclass, define the run() method, and start the thread. When the connection is shut down, we should get an IOException, which allows the Thread to finish its run() method and be harvested by the garbage collector.

The Client

The client application is a little more tricky, but only because it supports a user interface. The network and thread programming is just as simple as in the server application.

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

import javax.swing.*;

public class Client
    extends JPanel {
  public static void main(String[] args) throws IOException {
    String name = args[0];
    String host = args[1];
    int port = Integer.parseInt(args[2]);
    
    final Socket s = new Socket(host, port);
    final Client c = new Client(name, s);
  
    JFrame f = new JFrame("Client : " + name);
    f.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent we) {
        c.shutDown();
        System.exit(0);
      }
    });
    f.setSize(300, 300);
    f.setLocation(100, 100);
    f.setContentPane(c);
    f.setVisible(true);
  }
  
  private String mName;
  private JTextArea mOutputArea;
  private JTextField mInputField;
  private PrintWriter mOut;

  public Client(final String name, Socket s)
      throws IOException {
    mName = name;
    createUI();
    wireNetwork(s);
    wireEvents();
  }
  
  public void shutDown() {
    mOut.println("");
    mOut.close();
  }
  
  protected void createUI() {
    setLayout(new BorderLayout());
    mOutputArea = new JTextArea();
    mOutputArea.setLineWrap(true);
    mOutputArea.setEditable(false);
    add(new JScrollPane(mOutputArea), BorderLayout.CENTER);
    mInputField = new JTextField(20);
    JPanel controls = new JPanel();
    controls.add(mInputField);
    add(controls, BorderLayout.SOUTH);
    mInputField.requestFocus();
  }
    
  protected void wireNetwork(Socket s) throws IOException {
    mOut = new PrintWriter(s.getOutputStream(), true);

    final String eol = System.getProperty("line.separator");    
    new Listener(s.getInputStream()) {
      public void processLine(String line) {
        mOutputArea.append(line + eol);
        mOutputArea.setCaretPosition(
            mOutputArea.getDocument().getLength());
      }
    };
  }
  
  protected void wireEvents() {
    mInputField.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent ae) {
        String line = mInputField.getText();
        if (line.length() == 0) return;
        mOut.println(mName + " : " + line);
        mInputField.setText("");
      }
    });
  }
}

As with Server, Client's main() method interprets the command-line arguments. It extracts a name, the server's address, and the server's port number. It connects to the server by creating a Socket to the server's address and port number. Then it creates a new Client using this Socket. Finally, main() creates a JFrame that will hold the Client on the screen.

Client's constructor performs several important initializations using helper methods. First, it calls createUI() to create text controls for the user. Then wireNetwork() is called to set up the Client's connection to the server. Finally, Client's constructor calls wireEvents() to set up the event handling for the text controls.

The shutDown() method sends an empty string to the server, signaling that this client is signing off. This method is called when the JFrame that contains this Client (created in main()) is closed.

The createUI() method creates two text controls. The first is a JTextArea which shows all the text coming from the chat server. This includes what the user types as well as what any other connected users type. The second text control is a JTextField. The user can type into this field. When the return key is pressed, the contents of the field are sent to the server, prefixed by the user's name.

The wireNetwork() method serves two purposes. First, it sets up a PrintWriter member variable, mOut, that can be used to send data to the server. Second, this method creates a Listener subclass that listens for data coming from the server. When a line of text is received, it is appended to the JTextArea.

The wireEvents() method simply sets up the handler for the input text field. When the return key is pressed in this field, it generates an ActionEvent. If the input field is not empty, the text is sent to the server (with the user's name in front):

        mOut.println(mName + " : " + line);

If That Was Too Easy...

It is startlingly simple to create an Internet chat system in Java. If this column was too easy for you, you might consider these enhancements:
  • If you join a conversation in progress, you have no idea what happened before you got there. It might be nice if the server kept track of the conversation and sent the previous data to clients as they join.
  • It would also be nice if the server notified its clients when new people joined the conversation.
  • Try implementing this system as an applet. Because unsigned applets can only make network connections to the host they came from, you'll have to run the Server class on the same machine that hosts the applet. You'll have to write a JApplet that wraps the Client class somehow. I deliberately subclasses JPanel to make it feasible.
  • Along the same lines, you could develop an applet client that runs in a JDK 1.1 environment. You'd have to dump out the Swing components, but it wouldn't really change the code very much at all. To run in a JDK 1.0 environment, you'd have to make more drastic changes, like reverting to the old AWT event model and throwing out the inner classes.
  • Make a fancier client using a JTextPane. Maybe you could show different parts of the text in different fonts or colors. Wouldn't it be nice if each connected user's text showed up in a different color?
  • Add authentication to the system. Implement a password or signature scheme to authenticate users as they connect to the chat system. This would allow you to restrict access to the chat system to people you trusted.
  • Add confidentiality to the system. Using classes in the Java Cryptography Extension (JCE), you could make the connections between the server and clients encrypted streams. This would prevent network snoops from seeing your conversations.

Download the Source Code.


Jonathan Knudsen is a staff writer for O'Reilly & Associates. He is the author of Java Cryptography and Java 2D Graphics. For superlative technical books, visit O'Reilly's home page at http://www.oreilly.com/.


O'Reilly Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies

© 1998, O'Reilly & Associates, Inc.