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();