A Simple TCP Client

When you run the Java virtual machine, you specify a public class name on the command line. Java starts execution by calling a static method in this class called main. We will place all of the code for the client in this one method, and we will call the name of the containing class, Client. The class has a simple structure: it contains no member variable and only a single static method.

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

public final class Client
{
   public static void main(String argv[]) throws Exception
   {
      . . .
   }
}

The import command tells Java which package to search when resolving external class references. In our program, we need to include references to two of the Java core packages, because these packages include classes that we use in this program. The package java.io contains the classes for the input and output streams that we use in this program, and the package java.net contains the class files for networking functionality.

The first action we take is to retrieve two command line parameters and store them in an appropriate format.

// Get the port number and IP address of the server from the command line.
int port = Integer.parseInt(argv[0]);
InetAddress IPAddress = InetAddress.getByName(argv[1]);

The port is the port number on which the server is listening for a TCP connection request. We need to convert the string representation of the port number to an integer representation. We do that with the parseInt method of the Integer class.

The variable IPAddress represents the IP address of the server. Because we will need to use this address in the form of an InetAddress object, we make a static method call to getByName(), which takes the host name and converts it into a 32-bit IP address by contacting the system's data name server (DNS).

After obtaining the host name and port number, we create a new socket with the following command:

// Establish a TCP connection to the server.
Socket socket = new Socket( ?, ? );

The new operator creates an instance of the Socket class by calling its constructor with the supplied parameters. The constructor will open a socket and attempt to establish a TCP connection with the server that is specified by the constructor's two arguments. If it fails to form a TCP connection with the remote host, the constructor will throw an UnknownHostException, which will terminate program execution with an error message. On the other hand, if the constructor is successful, new will return a reference to the newly created socket. You need to replace each "?" with a missing detail. You can search the documentation of the Java API to determine what to pass as arguments to the Socket constructor, or you can simply make an educated guess.

Up to this point, communication with the server has occurred in the transport layer. But now that a TCP connection has been established, we can begin communicating with the server on the application layer. We are now going to: (1) send a text string to the server, (2) get a text string from the server, and (3) close the socket. Both text strings must be terminated by a new line character.

Communication through the socket is done by writing to the socket's OutputStream and reading from the socket's InputStream. References to both of these streams are obtained from the socket object by calling the appropriate methods:

// Get a reference to the socket's InputStream and OutputStream.
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();

Before we start to read from the input stream and write to the output stream, we attach a series of helpful filters to these streams. By doing so, we will be able to simplify the reading and writing operations.

// Setup input stream filters.
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader( ? );

// Setup output stream filters.
OutputStreamWriter osw = new OutputStreamWriter(os);
BufferedWriter bw = new BufferedWriter( ? );

Now we are ready for application-level communication with the server. According to our trivial protocol, the first action we take is to send a message to the server, which is accomplished by writing into the output stream.

// Send message to server.
bw.write("send me something\n");
bw.flush();

Notice that we flush() the output stream. The reason for this is that we added a filter to buffer writes into the stream, so that only when the buffer is full are the bytes actually sent into the output stream of the socket. If we didn't flush the stream at this point, the server would remain blocked waiting for the client's message, and the client would proceed to it's next step, which is to read the server's message from the input stream, and so would also block. The result is a deadlock.

After writing our message to output stream, we know that the server will send with a response message. So our next step is to read from the input stream and print out the message that was sent from the server.

// Get message from server.
String messageFromServer = br.readLine();

// Display message from server.
System.out.println(messageFromServer);

When readLine() encounters a new line character, it will return a string that it has read from the input stream.

The final task is to terminate gracefully by first closing the streams and the socket.

// Close the streams.
bw.close();
br.? ;

// Close the socket.
socket.close();