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.