Internet Programming in Java

( TCP Sockets)

Joseph Bergin
Pace University
jbergin@pace.edu

 

This is a short tutorial on internet programming in Java. It will show the basic techniques used in servers and in clients. I will use an example adapted from Peter van der Linden's Just Java (Prentice-Hall, 1998). A few years ago Peter Steiner had a cartoon in the New Yorker. A dog was typing at a keyboard and talking to another dog over its shoulder. The caption was: "On the internet, no one knows you're a dog." The running example here is of a server that attempts to determine if its client is a dog.

The server begins an exchange by asking the client a question. The client responds. The server then uses the response to determine if the client is a dog. The question is: "If you met me, would you shake my hand or sniff it."

For internet programming in Java you have a number of alternatives. Here we will only explore a middle level protocol called sockets. A TCP (tramission control protocol) socket is a point to point two way communication stream on which the client and server can exchange information of any type. We will just use strings. (A more sophisticated mechanism in Java is RMI-Remote Method Invocation, in which the distinction between client and server is erased and the two appear to be one application running over the net.)

Communication over sockets is exactly like reading to and writing from files. If a server writes into a socket, the client can read it and conversely.

There are two kinds of sockets: server sockets and ordinary sockets. To establish a link, a server must create a server socket on a port and then listen on that port by executing the accept method. A port is a numbered logical connection point to the operating system. Typically port numbers below 1024 are reserved for sytem use. For example port 80 is usually the HTTP port and the FTP server usually listens on port 21. Only one server can be installed on a given port.

The Server

Here is a simple dog server, adapted from Just Java. We will use port 4444, hoping that it isn't currently in use on our server machine. If it is we will get an error when we run it and will need to pick another port number. Clients will need to know this port number also, as well as the IP address of the server or an equivalent domain name.

import java.io.*;
import java.net.*;
/**
 *  a network server that detects presence of dogs on the Internet
 *
 *  @version   1.1 Jan 21 1996
 *  @author    Peter van der Linden
 *  @author    From the book "Just Java"
 * Adapted by J. Bergin
 *
 */
public class DogServer 
{	 public static void main(String a[]) throws IOException 
	{ 	int port = 4444;
		Socket client = null;
		// Next we create the server socket that we will listen to. 
		ServerSocket servsock = new ServerSocket(port);

		String query = "If you met me would you shake my hand, or sniff it?";

		while (true) 
		{ 	// Wait for the next client connection
			client = servsock.accept();
			// Create the input and output streams for our communication. 
			PrintStream out = new PrintStream( client.getOutputStream() );
			BufferedReader in  = new BufferedReader(new InputStreamReader( client.getInputStream()));
			// Now you can just write to and read from these streams. 

			// Send our query
			out.println(query); out.flush();

			// get the reply
			String reply = in.readLine();
			if (reply.indexOf("sniff") > -1) 
			{	System.out.println("On the Internet I know this is a DOG!");
				out.println("You're a dog.");
			}
			else 
			{	System.out.println("Probably a person or an AI experiment");
				out.println("You're a person or something.");
			}
			out.flush();
				
			// All done. Close this connection 
			client.close();
		 }
	}
}

Once we have established the connection (when accept returns), the resulting socket will give us its input and output streams which we can wrap in any convenient wrappers. Here we used a PrintStream and a BufferedReader since all we want to write are strings. Note that since this was intended as a demonstration, the server prints a trace of its results on System.out. This isn't very realistic in a real server, however.

This program should be run in the background (and may need to be run by the system administrator), so that it continues to run after you exit. On a UNIX system you would run it with:

java DogServer &

On Windows95/98/NT you could run it in one DOS window, and run clients in another. You could also use the start command.

This server will only handle one client at a time but it will queue up to 50 (the default) clients waiting to connect. We will show how to do better below.

A Simple Client

Once you have a server, you need a client. Actually you can use a telnet program to connect to port 4444 on the server and carry on the conversation directly. As soon as you connect, the server will send you its question and await your answer. As soon as you answer, it will send its reply and then break the connection. Note that a different server could carry on a very long and involved transaction with the client. This is distinct from what occurs with a CGI where the client initiates the conversation and the server responds once and then always breaks the connection.

We will write a client in Java also, however. A very simple, text based client follows. This illustrates only the simplest case of all.

Here we assume that the client runs on the same machine as the server. If not we need to supply a URL or an IP address for the server machine.

// On the Internet no one knows you're a dog...
// unless you tell them.

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

class Dog 
{	public static void main(String a[]) throws IOException 
	{	// Specify your server with a url or an IP address. 
		// if you try this on your system, insert your system name 
		//  such as "sol.pace.edu" in the server string instead of loopback.
		String loopback = "127.0.0.1"; // "localhost" will also work. 
		String server = loopback;
		// Open our connection to the server, at port 4444
		Socket sock = new Socket(server,4444);
    
		// Get I/O streams from the socket
		BufferedReader dis = new BufferedReader(new InputStreamReader( sock.getInputStream() ));
		PrintStream dat =  new PrintStream(sock.getOutputStream() );
 		
		// Now we can just read from and write to the streams. 
		// Start with reading for this server. 
		String fromServer = dis.readLine();  
		System.out.println("Got this from server: " + fromServer);
		
		String myReply = null;
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		myReply = in.readLine();
		dat.println(myReply);
 	
		fromServer = dis.readLine();
		System.out.println("More from Server: "+ fromServer);
 	
		// All done. Close the connection.
		sock.close();
	}  
}

A Threaded Server

Following is a more sophisticated server. It creates a new thread for each connection and handles the transaction in that thread. The thread set up is fairly quick, so a waiting client won't need to wait very long before it gets service, unlike the first version where a newly arrived client would need to wait until the entire previous transaction completed.

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

/**
 *  A network server that detects presence of dogs on the Internet
 *	This version of dogserver is multithreaded, spawning one new thread for each client.
 *  @version   1.2 March 26 2000
 *  @author    Joseph Bergin
 *  @author    Adapted from the book "Just Java" by Peter van der Linden
 * Adapted by J. Bergin
 *
 */
public class ThreadedDogServer 
{	 public static void main(String a[]) throws IOException 
	{ 	int port = 4444;
		Socket sock = null;
		ServerSocket servsock = new ServerSocket(port);

		while (true) 
		{ 	// wait for the next client connection
			sock=servsock.accept();
			// Create the new thread and hand it the socket. Then start it. 
			new ClientHandler(sock).start();
		 }
	}
	
	private static class ClientHandler extends Thread
	{	ClientHandler(Socket client)
		{	this.client = client;
		}
		
		public void run()
		{	// Get I/O streams from the client
			try
			{	PrintStream out = new PrintStream( client.getOutputStream() );
				BufferedReader in  = new BufferedReader(new InputStreamReader( client.getInputStream()));
				String query = "If you met me would you shake my hand, or sniff it?";

				// Send our query
				out.println(query); out.flush();

				// get the reply
				String reply = in.readLine();
				if (reply.indexOf("sniff") > -1) 
				{	System.out.println("On the Internet I know this is a DOG!");
					out.println("You're a dog.");
				}
				else 
				{	System.out.println("Probably a person or an AI experiment");
					out.println("You're a person or something.");
				}
				out.flush();
					
				// Close this connection 
				client.close();
			} 
			catch(IOException e)
			{	System.out.println("Missed client.");
				e.printStackTrace();
			}
		}
		
		private Socket client = null;
	}
}

Here we use an inner class to define the thread. It is static since we need to call its constructor from a static method (main), but in general it need not be either static or inner.

There are some problems with this design, however, in the case of a high volume server. First, you wouldn't want to let the server create an unlimited number of threads. This would make each run too slowly and the system would be unusable for all. Second, you wouldn't want to wait for even the creation of the new threads, so you would modify this so that old threads were saved (perhaps in a queue) for reuse by future clients. This server also prints its trace to System.out, which is also unrealistic.

An Applet Client

Finally, here is an applet that can be used as a client. It isn't very sophisticated, but you can see how the various key parts used above appear within applet code. Since the server will disconnect after it makes a determination, we need to open a new socket each time we want to get the question and answer it. In general any server establishes a protocol for communication and the client will need to use this protocol.

Here the protocol is that the client connects, the server responds with a string and then accepts a string. Finally the server responds to the client string with a string of its own and then disconnects. The client may also disconnect in the middle of a transaction.

import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.net.*;
import java.io.*;

public class DogApplet extends Applet 
{	public void init()
	{	super.init();
		setLayout(null);
		resize(544,409);
		
		answer = new Button("Answer Server");
		AnswerListener answerlistener =  new AnswerListener();
		answer.addActionListener(answerlistener);
		answer.setBounds(173,195,106,22);
		add(answer);
		
		reset = new Button("Reset");
		reset.addActionListener(new ResetListener());
		reset.setBounds(406,371,60,23);
		add(reset);
		
		label1 = new Label("From Server");
		label1.setBounds(30,65,115,24);
		add(label1);
		
		label2 = new Label("Your Reply");
		label2.setBounds(30,157,103,25);
		add(label2);
		
		label3 = new Label("Server's Judgement");
		label3.setBounds(30,268,133,23);
		add(label3);
		
		label4 = new Label("Are you a dog? Answer the following question.");
		label4.setBounds(56,12,407,24);
		add(label4);
		
		fromServer = new TextField();
		fromServer.setEditable(false);
		fromServer.setBounds(172,67,343,36);
		add(fromServer);	
		fromServer.setEnabled(false);
		
		judgementFromServer = new TextField();
		judgementFromServer.setEditable(false);
		judgementFromServer.setBounds(172,261,343,36);
		add(judgementFromServer);		
		judgementFromServer.setEnabled(false);
		
		userReply = new TextField();
		userReply.addActionListener(answerlistener);
		userReply.setBounds(172,150,343,36);
		add(userReply);
		
		setVisible(true);

		// Get the first question from the server.
		firstQuestion();
	}

	void firstQuestion()
	{	try
		{	if(sock != null)  sock.close();
        	getServerQuestion();
		}
		catch(IOException e)
 		{	fromServer.setText("Sorry, IO Error: " + e);
		}
	}
	
	void getServerQuestion() throws IOException
	{  	sock = new Socket(host, port);
	
		// Get I/O streams fromServer the socket
		dis = new BufferedReader(new InputStreamReader( sock.getInputStream() ));
		dat = new PrintStream( sock.getOutputStream() );
        
 		judgementFromServer.setText("");
		String questionFromServer = dis.readLine(); 
		fromServer.setText(questionFromServer);
	}
	
	private class AnswerListener implements ActionListener
	{	public void actionPerformed(ActionEvent e)
		{	String user = userReply.getText();
			dat.println(user);
			try
			{	String judgement = dis.readLine();
				fromServer.setText("");
	 			judgementFromServer.setText(judgement);
				sock.close();
	 		}
	 		catch(IOException exc)
	 		{	fromServer.setText("Sorry, IO Error: " + e);
	 		}
	        	sock = null;
		}
	}
	
	private class ResetListener implements ActionListener
	{	public void actionPerformed(ActionEvent e)
		{	try
			{	if(sock == null){ getServerQuestion();} 
			}
			catch(IOException exc)
		 	{	fromServer.setText("Sorry, IO Error: " + e);
			}
		}
	}
	
	private class WindowCloser extends WindowAdapter
	{	public void windowClosing(WindowEvent e)
		{	System.exit(0);
		}
	}
	
	private String host = "localhost";
	private int port = 4444;
	
	private Button answer;
	private Button reset;
	private Label label1;
	private Label label2;
	private Label label3;
	private Label label4;
	private TextField fromServer;
	private TextField judgementFromServer;
	private TextField userReply;	

	private Socket sock = null;
	private BufferedReader dis;
	private PrintStream dat;
}
 

A Chat Server and Client

Somewhat more sophisticated is a Chat server and client. This is also quite simple but shows what you need to do when the threads in your server need to interact. In this case they need to communicate with each other so that the text typed by one user can be sent to all the users. This example uses a shared buffer for this.


Note. The book Just Java from which this was developed has nice chapters on network programming and on graphical programming, especially the concept of smooth animation using double buffering. Other books in the same series are also excellent for examples of Java techniques.

Last Updated: March 27, 2000