Client - Server Connection: A Simple Server

As in our last example, the basic structure for a client program or applet is as follows:

/* Opening up the connection to the server */
try 
{
   Socket           client    = new Socket(host, 7);
   DataOutputStream socketOut = new 
                    DataOutputStream(client.getOutputStream());
   DataInputStream  socketIn  = new 
                    DataInputStream(client.getInputStream());

/* You can now receive information from the server using the 
   socketIn stream, and send information to the server using 
   the socketOut stream. You can also use the System.in, 
   System.out, and System.err streams, if necessary. */
 
/* Now closing the connection to the server */

   socketOut.close(); 
   socketIn.close();
   socket.close();
} 

/* Catching the errors. They can be caught elsewhere also */

catch (UnknownHostException e) 
{ System.err.println(host + ": unknown host."); } 
catch (IOException e) 
{ System.err.println("I/O error with " + host); }

Now let's take a look at the basics of a server program. A server uses the ServerSocket class to handle the connection to a client. Here's the basic code:

try 
{
   server = new ServerSocket(4444);
} 
catch (IOException e) 
{
   System.out.println("Error on port: " + 4444 + ", " + e);
   System.exit(1);
}

A new "ServerSocket" is instantiated and ready to guard port 4444 on the system. Remember, on Unix systems the ports below 1000 require special access priviledges, but all ports above 1000 are free for anyone to use. The ServerSocket throws an exception if it can not tie itself to the specified port - for example, if the port is already in use by another server.

After the ServerSocket successfully instantiates, it can wait for a connection. To do that, it waits for a connection from a client; if a client tries to connect, the server socket  returns a new Socket on a new, local port. That enables the server, in principle, to continue waiting for another client to connect on the same port as before, since the previously accepted connection has been moved to a different port. Here's the code:

Socket client = null;
try 
{
   client = server.accept();
} 
catch (IOException e) 
{
   System.out.println("Did not accept connection: " + 4444 + ", " + e);
   System.exit(1);
}

Note that the accept() method of a ServerSocket is a blocking method. That means that the program will not continue until a client attempts to request a connection. Also, the method throws a potential IOException that needs to be caught again.

Now, since the server has setup communications with the client through a socket, it next needs to tie input and output streams to the socket very similar to the previous client. In other words, the next few lines of a server would look like this:

DataInputStream streamIn = new 
             DataInputStream(new 
             BufferedInputStream(client.getInputStream()));
PrintStream streamOut = new 
             PrintStream(new 
             BufferedOutputStream(client.getOutputStream(), 1024), false);

Note that input and output streams are buffered. That allows them to temporarily store characters and deliver or retrieve them in larger chunks. Also, a PrintStream instead of a DataOutputStream is used. Now the server can talk to the client through the streamIn and streamOut objects, using the appropriate methods to send and receive data. When everything is done, the server would clean up after itself using code such as:

streamOut.close();
streamIn.close();
client.close();
server.close();

Now, to start, let's setup a server that waits for a client to connect, and simply echos everything that the client 'says' to the standard output. The client determines when the connection is over by disconnecting. Note that since the server does not need to speak to the client, there's not need to setup an output stream to the client. Here's the complete code:


TestServer:

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

public class TestServer
{
   public static void main(String args[])
   {
      ServerSocket server = null;
      try 
      {
         server = new ServerSocket(4321);
      } 
      catch (IOException e) 
      {
         System.out.println("Error on port: 4321 " + ", " + e);
         System.exit(1);
      }

      System.out.println("Server setup and waiting for client connection ...");

      Socket client = null;
      try 
      {
         client = server.accept();
      } 
      catch (IOException e) 
      {
         System.out.println("Did not accept connection: " + e);
         System.exit(1);
      }

      System.out.println("Client connection accepted. Moving to local port ...");

      try
      {
         DataInputStream streamIn = new 
                  DataInputStream(new 
                  BufferedInputStream(client.getInputStream()));

         boolean done = false;
         String line;
         while (!done)
         {
            line = streamIn.readLine();
            if (line.equalsIgnoreCase(".bye"))
               done = true;
            else
               System.out.println("Client says: " + line);
         }

         streamIn.close();
         client.close();
         server.close();
      }
      catch(IOException e)
      { System.out.println("IO Error in streams " + e); }
   }
}

TestClient

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

public class TestClient 
{
   public static void main(String[] args)
   {
      String host = "sciris.shu.edu";
      try 
      {
         Socket           client    = new Socket(host, 4321);
         DataOutputStream socketOut = new DataOutputStream(client.getOutputStream());
         DataInputStream  socketIn  = new DataInputStream(client.getInputStream());
         DataInputStream  console   = new DataInputStream(System.in);
         System.out.println("Connected to " + host + ". Enter text:");

         boolean done = false;
         String line;
         while (!done) 
         {
            line = console.readLine();
            if (line.equalsIgnoreCase(".bye"))
               done = true;
            socketOut.writeBytes(line + '\n');
         }

         socketOut.close(); socketIn.close(); client.close();
      } 
      catch (UnknownHostException e) 
      { System.err.println(host + ": unknown host."); } 
      catch (IOException e) 
      { System.err.println("I/O error with " + host); }
   }
}

  

Notice that the code between opening and closing the various sockets and connections is just about the same. That is, of course, because both the client and the server need to know what type of information they are exchanging. Notice also that once the client sends the string ".bye" to the server, both server and clients quit. That means in particular that a client can not connect again to the server without starting the server up again.

Next, we'll refine this basic client - server example to implement a particular protocol.