Sockets


Overview

Java offers many ways to exchange information between a client and a server object. Some examples that are available throughout this Web site include:

  1. HTML documents can call CGI programs written in Java
  2. Java applets can call CGI programs running on the Web server
  3. Java applets can call procedures contained within remote Java objects through the RMI

Examples 1 and 2, using CGI, represent stateless environments. In other words, the client object opens a connection to the server, executes the server object, accepts any results, and disconnects immediately from the server. If another call to the server is required, the client needs to open the connection to the server again and repeat the process.

Example 3, using Remote Method Invocation, represents a persistent environment. In other words, the client object opens a connection to the server and leaves it open until the client or the server chooses to disconnect. The client can continue exchanging information with the server object without needing to reestablish the connection.

Sockets allow Java objects to communicate with each other persistently, similar to RMI and CORBA/DL. The difference lies, however, in how it does this. Let's consider the distributed architecture of a socket-based application:

Sockets architecture

The Java client object connects to the Java server object through the TCP/IP network using a socket. Once this connection is made, the two can exchange information between themselves.

What exactly is a socket? Consider the following scenario:

Two companies, A and B, are located on separate islands and wish to engage in commerce with each other. There are miles of ocean separating them, so they agree to buy a freight barge and use it to transfer the goods between them. On each island, the companies need to dock the barge at a harbor, so they each build a port.

Company A loads the barge with its product at the port and sends it off to Company B. Company B opens its port to receive the barge and unloads the goods. In return, it loads the barge with its own product for delivery back to Company A.

In this example, the barge serves as the transport vehicle between the two islands, and the port serves as point where the products are loaded and unloaded. Let's extend this example to a computer application:

A Java applet (client) contains a search form, on which the user enters some search criteria. It wishes to send the search criteria to a server application in order to perform a database query and send the results back the applet for display. The client applet represents Company A. The server application represents Company B. The search criteria and query results represent the products exchanged between the client and the server. The TCP/IP network represents the ocean, and the TCP layer is the barge that transports the information.

The server application opens a TCP port, called a server socket, and waits for incoming traffic. The applet opens a network connection, called a client socket, with the server. Once the connection is accepted by the server, the applet sends the search criteria to the server through an outbound TCP stream. The server reads the incoming stream, processes the database query, and sends the results back to the client through the TCP stream. This process continues until either the client or the server closes its respective socket.

Let's start by building a simple socket-based application. As we build the application, we will see how each of the components interacts with the other.


A Simple Socket Server

Our first attempt at building a socket-based application will be a simple one:

  1. A client object will send a string to the server.
  2. The server object will reverse the string and send it back to the client.
  3. The client will display the reversed string on the system console.

Let's start with the server application.

Create the Server Object

The first step in creating the server object is to import the necessary packages. The java.net package will be used to access the socket classes. The java.io package will be used to send messages between the client and server applications.

	// Simple Socket Server

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

	public class SimpleSocketServer {

Declare the Socket Objects

As we communicate using sockets, we need to store a reference to the server socket and the client connection.

// define the socket objects
static ServerSocket serverSocket = null; // server
static Socket       clientSocket = null; // client

Open the Network Connection

The application needs to offer itself as a server through a TCP socket. This is done creating the ServerSocket class, specifying the TCP port number that will be used for communication. (This port number must be one that is not already in use on the server. We will arbitrarily use port 9000.) The attempt to create a server socket may throw an IOException, so we shall trap it and exit the program if this occurs.

public static void main(String args[]) {

    // open up a server socket on port 9000
    try {
         serverSocket = new ServerSocket(9000);
         System.err.println("Server socket listening on port 9000");
    } catch (IOException e) {
         System.err.println("Cannot open socket on port: " + 9000);
         System.err.println(e.getMessage());
         System.exit(1);
    }

Accept a Client Connection

Next, we wait for a client to request a connection with the server on this socket. This is done by calling the server socket's accept() method, which returns a Socket object. This new socket will be used to communicate with the client. The accept() method may throw an IOException, so we shall trap it and exit the program if this occurs.

	// listen for client socket request
	try {
     	clientSocket = serverSocket.accept();
	} catch (IOException e) {
     	System.err.println("Error accepting client socket request.");
     	System.err.println(e.getMessage());
     	System.exit(1);
	}

Open the Socket Streams

Now that we have started a connection with the client, we need to open the input and output streams in order to exchange messages with it. We will use object streams in our example. (This technique is known as object serialization.) The attempts to open these streams may throw an IOException, so we shall trap it and exit the program if this occurs.

// Use object streams as the TCP socket transport
ObjectInputStream  is = null;
ObjectOutputStream os = null;

// open the message streams
try {
    is = new ObjectInputStream(clientSocket.getInputStream());
    os = new ObjectOutputStream(clientSocket.getOutputStream());
} catch (IOException e) {
    System.err.println("Error opening object streams.");
    System.err.println(e.getMessage());
    System.exit(1);
}

Process the Incoming Message

Recall that our client will send us any string, and we will return to it the same string with its characters in reverse order. Through the socket's input stream, let's read the incoming String object using the object input stream. We'll reverse the string and send the result back using the object output stream.

The read attempt may throw an IOException or ClassNotFoundException, and the write attempt may throw an IOException. Let's trap both exceptions through the generic Exception class.

// String areas for client and server messages
String inmsg  = null;
String outmsg = null;
// process the message streams
try {
    StringBuffer sb;

    // read the incoming string object
    inmsg = (String)is.readObject();

    // reverse the string
    sb = new StringBuffer(inmsg);
    outmsg = new String(sb.reverse());

    // send the new string back to the client
    os.writeObject(outmsg);
    os.flush();
} catch (IOException e) {
    System.err.println("Error streaming the message.");
    System.err.println(e.getMessage());
    System.exit(1);
}

Close the Sockets

We've completed our server process. Let's close the client and server sockets and terminate the program.

    // close the sockets when done
    try {
        clientSocket.close();
        serverSocket.close();
    } catch (IOException e) {
        System.err.println("Could not close sockets.");
        System.err.println(e.getMessage());
    }
}

Compile and Run the Server Application

Our server application can be compiled like any other Java class. To run it, open up a shell or window and execute the server application in the background:

javac SimpleSocketServer.java

Windows 95/NT:

start java SimpleSocketServer

Solaris:

java SimpleSocketServer &


A Simple Socket Client

Now that we have build the simple socket server, let's build the associated client. Recall that our socket-based application will do the following:

  1. A client object will send a string to the server.
  2. The server object will reverse the string and send it back to the client.
  3. The client will display the reversed string on the system console.

Create the Client Object

The first step in creating the client object is to import the necessary packages. The java.net package will be used to access the socket classes. The java.io package will be used to send messages between the client and server applications.

// Simple Socket Server

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

public class SimpleSocketClient {

Declare the Socket Objects

The client application requires a socket connection object that will be used to connect to the server.As we communicate using sockets, we need to store a reference to the server socket and the client connection. We will declare the main() routine to throw any exceptions that are generated within the application.

public static void main(String args[]) throws Exception {

    // client socket connection
    Socket socket = null;

Declare the Socket Streams

Similar to the server application, we will use object streams to exchange messages with the server. Let's declare them now, along with the String objects representing the incoming and outgoing messages.

// socket streams
ObjectInputStream is = null;
ObjectOutputStream os = null;

// message strings
String inmsg = null;
String outmsg = null;

// initialize an outgoing message
outmsg = "The quick, brown fox jumps over the lazy dog's back.";

Open the Network Connection

The client will attempt to connect to the server socket now by creating a Socket object, passing the server application's host name and port number. In our example, we will use localhost as the host name, and 9000 as the port number.(If the connection attempt fails, an UnknownHostException or IOException may occur.)

// open socket connection with server
socket = new Socket("localhost", 9000);

Open the Socket Streams

Now that we have connected to the server, we need to open the input and output streams in order to exchange messages with it. We will use object streams that we declared above. (The attempts to open these streams may throw an IOException.)

// open socket streams
os = new ObjectOutputStream(socket.getOutputStream());
is = new ObjectInputStream(socket.getInputStream());

Exchange Messages with the Server

Recall that our client will send a string to the server and receive it back in reverse order. Let's do so now using the object streams. The final result will be printed on the default system display. (The read and write attempts may throw an IOException or ClassNotFoundException,.)

// send outbound message to server and flush the buffer
os.writeObject(outmsg);
os.flush();

// accept inbound message from server
inmsg = (String)is.readObject();
System.out.println(inmsg);

Close the Sockets

We've completed our client process. Let's close the streams and socket connection and terminate the program.

    // close streams and socket connection
    os.close();
    is.close();
    socket.close();
    }
}

Compile and Run the Client

Our client application can be compiled and executed like any other Java application:

javac SimpleSocketServer.java

java SimpleSocketClient

Response:

.kcab s'god yzal eht revo spmuj xof nworb ,kciuq ehT


Distributed Computing Using the JDBC and Sockets

Traditional client-server models illustrate a client application accessing a relational database running on a remote server. Two-tier designs place the database access within the client. Three- and n-tier designs distribute the database access to one or more middle layers running on another machine. The Common Gateway Interface provides one method for delivering Java applications across the network.

The RMI and CORBA/IDL sections of this Web site demonstrate how the traditional client-server model can be extended to a distributed object architecture. The RMI operates in a pure Java environment, whereas the IDL supports a heterogeneous environment using CORBA standards.

We can use TCP sockets to build a distributed object-based application and will do so in the following pages. First, let's compare the use of sockets to some of the other distributed computing models:

Similarities:

Differences:

In order to illustrate a distributed computing model using sockets, as well as its ability to provide persistency in Internet computing, we will build a simple application using the JDBC. If you are new to the JDBC, then refer to the Java Database Connection section of this site for a quick overview. (This same application is shown in the RMI and CORBA/IDL sections for comparison purposes.)

In our example, the applet will appear and function as follows*:

1, Nancy Davolio, Sales Representative
2, Andrew Fuller, Vice President, Sales
3, Janet Leverling, Sales Representative
4, Margaret Peacock, Sales Representative
5, Steven Buchanan, Sales Manager
6, Michael Suyama, Sales Representative
7, Robert King, Sales Representative
8, Laura Callahan, Inside Sales Coordinator
9, Anne Dodsworth, Sales Representative 

*Note: This applet simulates the process that will be used to provide JDBC access through sockets. It is included here only to demonstrate the concepts and does not contain the full implementation.

Upon initial load, the applet will connect to the Microsoft Access 7.0 sample database, Northwind. When the "Begin Search" button is pressed, it will perform a search that returns and displays all employee names and titles from the Employee table. When the "Clear Data" button is pressed, it will clear the search results from the applet. The database connection remains open until the applet is closed and terminated (i.e. shut down).

Key success factors of the process include:

To build our socket-based system, we will create three components:

Similar to the RMI and CORBA/IDL examples, a socket connection allows an applet to communicate with a remote server object. Because we need to provide multiuser database support, we will incorporate threads into the server object as well. We will refer to this as our Server Thread.

Let's begin building our socket process by starting with the Server Thread.


Creating a JDBC/Socket Thread

The first step we will take in building our socket application is in the creation of a threaded object for multiuser access. (This is similar to the JDBC/RMI Connection Object contained within the RMI section of this site.)

Before we begin developing the socket thread, let's define how we wish to exchange messages between the client and the server:

  1. The client will send a command string to the server, indicating a function to be performed.
  2. The server will accept the command and execute the associated function.
  3. Once the function completes, the server will return a result back to the client.

The following table illustrates the various commands and return values that we can have:

Client Command Action Taken by the Server Return Values
1. null (occurs when the client connection is closed) Close the client socket None
2. open Open the database connection ok
error: Exception
3. srch Perform a database search (query) ok
error: Exception
4. fetch Fetch a row from the database (following a query) Query results
null (if no more rows found)
error: Exception
5. close Close the database connection ok
error: Exception
6. All other commands Generate an error (invalid command) Invalid command

Let's call our socket thread JS, for Java Socket.

Create the Thread Class

We begin by importing the appropriate Java packages:

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

public class JS extends Thread {

Define the Variables and Constructor

We will pass a client socket object to the thread's constructor. The socket connection will be established prior to creating the thread. Note that we plan to use object streams as we exchange messages between the client and server.

private Socket clientSocket;    // Client socket object
private Connection con;         // Database connection object
private Statement stmt;         // SQL statement object
private ResultSet rs;           // SQL query results
private ObjectInputStream is;   // Input stream
private ObjectOutputStream os;  // Output stream

public JS (Socket clientSocket) {
    this.clientSocket = clientSocket;
}

Create the Input/Output Methods

The following input/output methods will be used to exchange messages between the client and the server. First, readCommand() will accept and process a command string from the client. Next, send() will return a message back to the client. Finally, sendError() will send a pre-formatted error message to the client.

/** Receive and process incoming command from client socket */
public boolean readCommand() {
    String s = null;

    try {
        s = (String)is.readObject();
    } catch (Exception e) {
        s = null;
    }

    if (s == null) {
        closeSocket();
        return false;
    }

    // invoke the appropriate function based on the command
    if (s.equals("open")) { openDatabase(); }
    else if (s.equals("srch")) { performSearch(); }
    else if (s.equals("fetch")) { getNextRow(); }
    else if (s.equals("close")) { closeDatabase(true); }
    else { sendError("Invalid command -> " + s); }

    return true;
}

/** Send a message back through the client socket */
public void send(Object o) {

    try {
        os.writeObject(o);
        os.flush();
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

/** Send a pre-formatted error message to the client */
public void sendError(String msg) {
    send("error:" + msg);
}

Create the Database Access Methods

Let's build the data access methods that are referenced by our incoming command strings. We'll trap all possible exceptions within these methods and send messages back to the client when they are caught.

/** Open a database connection */
public void openDatabase() {
    try {
        // Load the JDBC-ODBC bridge driver
        Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");

        // Connect to the database
        con = DriverManager.getConnection("jdbc:odbc:Northwind", "admin", "");
        send("ok");
    } catch (Exception ex) {
        sendError(ex.toString());
    }
}

/** Issue a SQL query */
public void performSearch() {

    String query;           // SQL select string

    // build the query command
    query = "SELECT EmployeeID, LastName, FirstName, Title "
          + "FROM Employees ";

    try {
        stmt = con.createStatement();
        rs = stmt.executeQuery(query);
        send("ok");
    } catch (Exception ex) {
        sendError(ex.toString());
    }
}

/** Fetch the next row in the result set, returning null when done */
public void getNextRow() {
    String s = null;

    try {
        if (rs.next()) {
            s = rs.getInt("EmployeeId") + ", "
              + rs.getString("FirstName") + " "
              + rs.getString("LastName") + ", "
              + rs.getString("Title");
        }
        else {
            rs.close();   rs = null;
            stmt.close(); stmt = null;
            s = null;
        }

        send(s);
    } catch (Exception ex) {
        sendError(ex.toString());
    }
}

/** Close the database connection
    ack = true if we want to send an acknowledgement
          back to the client
*/
public void closeDatabase(boolean ack) {
    try {
        if (rs != null) rs.close();
        if (stmt != null) stmt.close();
        if (con != null) con.close();

        rs = null;
        stmt = null;
        con = null;
    } catch (Exception ex) {
        sendError(ex.toString());
    }
}

Thread Execution Methods

Finally, the thread will contain two more methods: run() and closeSocket(). The run() method is where the thread's activities execute once the thread is created. When it completes, the thread stops processing. The closeSocket() method will close the necessary sessions when the client disconnects itself from the server.

/** Thread execution method */
public void run() {

    // initialize the database objects
    rs = null; stmt = null; con = null;

    try {
        // open the socket streams
        is = new ObjectInputStream(clientSocket.getInputStream());
        os = new ObjectOutputStream(clientSocket.getOutputStream());

        // continue processing until no more commands
        while (readCommand()) { }

    } catch (IOException e) {
        e.printStackTrace();
    }
}

/** Close the client socket */
public void closeSocket() {
    try {
        // close all sessions and streams
        closeDatabase(false);
        os.close();
        is.close();
        clientSocket.close();
    } catch (Exception ex) {
        System.err.println(ex.toString());
    }
}

Compile the Socket Thread

javac JS.java


Creating a JDBC/Socket Server Application

Now that we have created our Socket Thread, let's build the server application that will use it. The server application will perform the simple task of opening a socket connection with the client, after which it launches a thread that will do the rest of the work. It is this application that allows multiuser access to the database.

Create the Server

The name of our server application will be JSStart. The comments placed throughout the source code will explain each step in the application. (We'll call our server JSStart.)

// import the necessary packages
import java.net.*;
import java.io.*;

public class JSStart {

    public static void main(String args[]) {

        // create a server socket on port 9000
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(9000);
            System.out.println("JSStart listening on port 9000");
        } catch (IOException e) {
            System.out.println("Cannot listen on port: " + 9000 + ", " + e);
            System.exit(1);
        }

        // endless loop to process client connections
        while (true) {

            // wait for a client request
            Socket clientSocket = null;
            try {
                clientSocket = serverSocket.accept();
            } catch (IOException e) {
                System.out.println("Accept failed: " + 9000 + ", " + e);
                break;
            }

            // create a new thread for the client
            JS js = new JS(clientSocket);

            // can't open a thread, so return an error to the client
            if (js == null) {
                try {
                    ObjectOutputStream os =
                        new ObjectOutputStream(clientSocket.getOutputStream());
                    os.writeObject("error: Cannot open socket thread");
                    os.flush();
                    os.close();
                } catch (Exception ex) {
                    System.out.println("Cannot send error back to client:  9000, " + ex);
                }
            }
            else {
                // transfer control over to the thread now
                js.start();
            }
        }

        // we should never get this far because of the while loop above
        try {
            System.out.println("Closing server socket.");
            serverSocket.close();
        } catch (IOException e) {
            System.err.println("Could not close server socket." + e.getMessage());
        }
    }
}

Compile the Server Application

javac JSStart.java


Creating a JDBC/Socket Client Applet

The server socket application that we just created will be executed as a background process running on the Web server. It represents the "server" half of a "client-server" process. The "client" half can be built as a Java application or as an applet. Although we will illustrate this using an applet, let's quickly explore the security aspects required by socket clients.

Socket Security

Standard Java security measures apply to socket-based applications and applets. For example, an applet may only connect to a server socket on the host from which it was originally loaded. An application may connect to any other server socket on the network. (This is unlike the RMI client, which requires an explicit security manager for client applications.)

Create the Socket Client Applet

The sample code below will only contain the logic necessary for the applet to use sockets. The complete set of source code for the applet is available in The Complete JDBC/Socket Example later on.

The applet begins by importing the appropriate packages and creating an reference to the socket streams. Next, it opens the socket connection with the server and begins processing. Follow each of the methods contained in the source below, referring to the inline comments for further explanation. Pay particular attention to how the client communicates with the server, using the send() and receive() methods.

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

public class JSApplet extends Applet
                      implements ActionListener {

    Socket socket = null;
    ObjectOutputStream os = null;
    ObjectInputStream is = null;


    /** Initialize the applet and connect to the server */
    public void init() {
        ...   // apple display goes here
        if (connectToServer()) {
            openDatabase();
        }
        else {
            showStatus("Cannot open socket connection.");
        }
    }

    /** Open a socket connection with the server */
    public boolean connectToServer() {
        try {
            socket = new Socket(getCodeBase().getHost(), 9000);

            // open the input and output object streams
            os = new ObjectOutputStream(socket.getOutputStream());
            is = new ObjectInputStream(socket.getInputStream());
        } catch (Exception ex) {
            showStatus(ex.toString());
            return false;  // error connecting to server
        }

        return true;  // successful connection
    }

    /** Open the database connection through the socket */
    public void openDatabase() {
        String ack;
        send("open");
        ack = (String)receive();
        if (ack != null)
            showStatus(ack);
    }

    /** Send a message to the server */
    public void send(Object o) {
        try {
            os.writeObject(o);
            os.flush();
        } catch (Exception ex) {
            showStatus(ex.toString());
        }
    }

    /** Receive a message from the server */
    public Object receive() {
        Object o = null;
        try {
            o = is.readObject();
        } catch (Exception ex) {
            showStatus(ex.toString());
        }
        return o;
    }

    /** Close the sockets when the applet is destroyed */
    public void finalize() {
        try {
            send("close");
            os.close();
            is.close();
            socket.close();
        } catch (Exception ex) {}
        super.destroy();
    }

    /** Issue a SQL query */
    public void performSearch() throws Exception {
        Object result;          // Row retrieved from query
        send("srch");
        result = receive();
        if (result.equals("ok")) {
            processResults();
        }
        else {
            showStatus((String)result);
        }
    }

    /** Process the query results */
    public void processResults() {
        Object result;
        send("fetch");
        result = receive();
        if (result == null) {
            showStatus("No rows found using specified search criteria.");
            return;
        }

        // keep fetching until end of result set
        while (result != null) {
            ...  // applet display goes here
            send("fetch");
            result = receive();
        }
    }
}

Compile JSApplet

javac JSApplet.java


Running the JDBC/Socket System

At this point we have created the following classes:

  1. JS: threaded object with database access methods using the JDBC
  2. JSStart: socket server application
  3. JSApplet: socket client applet

To run the applet, simply load it from within an HTML document using a browser or the appletviewer. Since we are using some JDK 1.1 features within our applet, we'll must use a browser that supports this release of the JDK. Let's use the appletviewer.

Start the Server

The socket server application must run as a background process on the Web server. (If you specified a socket port number that is less than 1024, you may also need special administrative privileges to run the application.)

Windows 95/NT:

start /min java JSStart

Solaris:

java JSStart &

When the server starts, you should see the following message displayed on the screen:

JSStart listening on port 9000

Run the Applet

Create an HTML document that loads our applet:

<APPLET CODE="JSApplet.class" HEIGHT=200 WIDTH=600>
</APPLET>

Run this document using the appletviewer (the HTML document name is jsapplet.htm):

appletviewer jsapplet.htm

Congratulations! You are now running a multithreaded JDBC application using Java sockets.


The Complete JDBC/Socket Example

The complete source code for each of the objects created in our JDBC/Socket example is available through the links listed below. You may save each of these objects or copy/paste the code into files on your hard disk and experiment with them. Good luck with your own socket-based applications!

JS.java

The socket thread.

JSStart.java

The socket application.

JSApplet.java

The socket client.