New to Java? We'll help you get started with our revised beginner's tutorial, or our free online textbook.
Get the latest Java books h t t p : / /w w w . j a v a c o f f e e b r e a k . c o m /
Section 10.5
Programming Networked Applications
SOCKETS AND CLIENT/SERVER programming were introduced in the previous section in a mostly theoretical way. This section presents a few complete programming examples. All but one of the examples in this section are standalone programs, so you won't see them running on this Web page. If you want to run these programs, you will have to compile the source code and run them with a Java interpreter.
The first example consists of two programs. One is a simple client and the other is a matching server. The client makes a connection to the server, reads one line of text from the server, and displays that text on the screen. The text sent by the server consists of the current date and time on the computer where the server is running. In order to open a connection, the client must know the computer on which the server is running and the port on which it is listening. The server listens on port number 32007. The port number could be anything between 1025 and 65535, as long the server and the client use the same port. Port numbers between 1 and 1024 are reserved for standard services and should not be used for other servers. The name or IP number of the computer on which the server is running must be specified as a command-line parameter. For example, if the server is running on a computer named math.hws.edu, then you would typically run the client with the command "java DataClient math.hws.edu". (You might need to replace the "java" command with another command if you are using a different Java interpreter.) Here is the complete client program:
import java.net.*; import java.io.*; public class DateClient { static final int LISTENING_PORT = 32007; public static void main(String[] args) { String computer; // Name of the computer to connect to. Socket connection; // A socket for communicating with // that computer. Reader incoming; // Stream for reading data from // the connection. /* Get computer name from command line. */ if (args.length > 0) computer = args[0]; else { // No computer name was given. Print a message and exit. System.out.println("Usage: java DateClient <server>"); return; } /* Make the connection, then read and display a line of text. */ try { connection = new Socket( computer, LISTENING_PORT ); incoming = new InputStreamReader( connection.getInputStream() ); while (true) { int ch = incoming.read(); if (ch == -1 || ch == '\n' || ch == '\r') break; System.out.print( (char)ch ); } System.out.println(); incoming.close(); } catch (IOException e) { TextIO.putln("Error: " + e); } // end main() } // end class DateClientNote that all the communication with the server is done in a try...catch. This will catch the IOExceptions that can be generated when the connection is opened or closed and when characters are read from the stream. The stream that is used for input is a basic Reader, which includes the input operation incoming.read(). This function reads one character from the stream and returns its Unicode code number. If the end-of-stream has been reached, then the value -1 is returned instead. The while loop reads characters and copies them to standard output until an end-of-stream or end-of-line is seen. An end of line is marked by one of the characters '\n' or '\r', depending on the type of computer on which the server is running.
In order for this program to run without error, the server program must be running on the computer to which the client tries to connect. By the way, it's possible to run the client and the server program on the same computer. For example, you can open two command windows, start the server in one window and then run the client in the other window. To make things like this easier, most computers will recognize the IP number 127.0.0.1 as referring to "this computer". That is, the command "java DateClient 127.0.0.1" will tell the DateClient program to connect to a server running on the same computer. Most computers will also recognize the name "localhost" as a name for "this computer".
The server program that corresponds to the DateClient client program is called DateServe. The DateServe program creates a ServerSocket to listen for connection requests on port 32007. After the port is created, the server will enter an infinite loop in which it accepts and processes connections. This will continue until the program is killed in some way -- for example by typing a CONTROL-C in the command window where the server is running. In the previous section, I noted that a server typically opens a separate thread to handle each connection. However, in this simple example, the server just calls a subroutine. In the subroutine, any Exception that occurs is caught, so that it will not crash the server. The subroutine creates a PrintWriter stream for sending data over the connection. It writes the current date and time to this stream and then closes the connection. (The standard class java.util.Date is used to obtain the current time. An object of type Date represents a particular date and time. The default constructor, "new Date()", creates an object that represents the time when the object is created.) The complete server program is as follows:
import java.net.*; import java.io.*; import java.util.Date; public class DateServe { static final int LISTENING_PORT = 32007; public static void main(String[] args) { ServerSocket listener; // Listens for incoming connections. Socket connection; // For communication with the // connecting program. /* Accept and process connections forever, or until some error occurs. (Note that errors that occur while communicating with a connected program are caught and handled in the sendDate() routine, so they will not crash the server.) */ try { listener = new ServerSocket(LISTENING_PORT); TextIO.putln("Listening on port " + LISTENING_PORT); while (true) { connection = listener.accept(); sendDate(connection); } } catch (Exception e) { TextIO.putln("Sorry, the server has shut down."); TextIO.putln("Error: " + e); return; } } // end main() static void sendDate(Socket client) { // The parameter, client, is a socket that is // already connected to another program. Get // an output stream for the connection, send the // current date, and close the connection. try { System.out.println("Connection from " + client.getInetAddress().toString() ); Date now = new Date(); // The current date and time. PrintWriter outgoing; // Stream for sending data. outgoing = new PrintWriter( client.getOutputStream() ); outgoing.println( now.toString() ); outgoing.flush(); // Make sure the data is actually sent! client.close(); } catch (Exception e){ System.out.println("Error: " + e); } } // end sendDate() } //end class DateServeIf you run DateServe in a command-line interface, it will sit and wait for connection requests and report them as they are received. To make the DateServe service permanently available on a computer, the program really should be run as a daemon. A daeman is a program that runs continually on a computer, independently of any user. The computer can be configured to start the daemon automatically as soon as the computer boots up. It then runs in the background, even while the computer is being used for other purposes. For example, a computer that makes pages available on the World Wide Web runs a daemon that listens for requests for pages and responds by transmitting the pages. It's just a souped-up analog of the DateServe program! However, the question of how to set up a program as a daemon is not one I want to go into here. For testing purposes, it's easy enough to start the program by hand, and, in any case, my examples are not really robust enough or full-featured enough to be run as serious servers. (By the way, the word "daemon" is just an alternative spelling of "demon" and is usually pronounced the same way.)
Note that after calling out.println() to send a line of data to the client, the server program calls out.flush(). The flush() method is available in every output stream class. Calling it ensures that data that has been written to the stream is actually sent to its destination. You should call this function every time you use an output stream to send data over a network connection. If you don't do so, it's possible that the stream will collect data until it has a large batch of data to send. This is done for efficiency, but it can impose unacceptable delays when the client is waiting for the transmission. It is even possible that some of the data might remain untransmitted when the socket is closed, so it is especially important to call flush() before closing the connection. This is one of those unfortunate cases where different implementations of Java can behave differently. If you fail to flush your output streams, it is possible that your network application will work on some types of computers but not on others.
In the DateServe example, the server transmits information and the client reads it. It's also possible to have two-way communication between client and server. As a first example, we'll look at a client and server that allow a user on each end of the connection to send messages to the other user. The program works in a command-line interface where the users type in their messages. In this example, the server waits for a connection from a single client and then closes down its listener so that no other clients can connect. After the client and server are connected, both ends of the connection work in much the same way. The user on the client end types a message, and it is transmitted to the server, which displays it to the user on that end. Then the user of the server types a message that is transmitted to the client. Then the client user types another message, and so on. This continues until one user or the other enters "quit" when prompted for a message. When that happens, the connection is closed and both programs terminate. The client program and the server program are very similar. The techniques for opening the connections differ, and the client is programmed to send the first message while the server is programmed to receive the first message. Here is the server program. You can find the client program in the file CLChatClient.java. (The name "CLChat" stands for command-line chat.)
import java.net.*; import java.io.*; public class CLChatServer { static final int DEFAULT_PORT = 1728; // Port to listen on, // if none is specified // on the command line. static final String HANDSHAKE = "CLChat"; // Handshake string. // Each end of the connection sends this string // to the other just after the connection is // opened. This is done to confirm that the // program on the other side of the connection // is a CLChat program. static final char MESSAGE = '0'; // This character is prepended // to every message that is sent. static final char CLOSE = '1'; // This character is sent to // the connected program when // the user quits. public static void main(String[] args) { int port; // The port on which the server listens. ServerSocket listener; // Listens for a connection request. Socket connection; // For communication with the client. TextReader incoming; // Stream for receiving data from client. PrintWriter outgoing; // Stream for sending data to client. String messageOut; // A message to be sent to the client. String messageIn; // A message received from the client. /* First, get the port number from the command line, or use the default port if none is specified. */ if (args.length == 0) port = DEFAULT_PORT; else { try { port = Integer.parseInt(args[0]); if (port < 0 || port > 65535) throw new NumberFormatException(); } catch (NumberFormatException e) { TextIO.putln("Illegal port number, " + args[0]); return; } } /* Wait for a connection request. When it arrives, close down the listener. Create streams for communication and exchange the handshake. */ try { listener = new ServerSocket(port); TextIO.putln("Listening on port " + listener.getLocalPort()); connection = listener.accept(); listener.close(); incoming = new TextReader(connection.getInputStream()); outgoing = new PrintWriter(connection.getOutputStream()); outgoing.println(HANDSHAKE); outgoing.flush(); messageIn = incoming.getln(); if (! messageIn.equals(HANDSHAKE) ) { throw new IOException("Connected program is not CLChat!"); } TextIO.putln("Connected. Waiting for the first message.\n"); } catch (Exception e) { TextIO.putln("An error occurred while opening connection."); TextIO.putln(e.toString()); return; } /* Exchange messages with the other end of the connection until one side or the other closes the connection. This server program waits for the first message from the client. After that, messages alternate strictly back and forth. */ try { while (true) { TextIO.putln("WAITING..."); messageIn = incoming.getln(); if (messageIn.length() > 0) { // The first character of the message is a command. // If the command is CLOSE, then the connection // is closed. Otherwise, remove the command // character from the message and proceed. if (messageIn.charAt(0) == CLOSE) { TextIO.putln("Connection closed at other end."); connection.close(); break; } messageIn = messageIn.substring(1); } TextIO.putln("RECEIVED: " + messageIn); TextIO.put("SEND: "); messageOut = TextIO.getln(); if (messageOut.equalsIgnoreCase("quit")) { // User wants to quit. Inform the other side // of the connection, then close the connection. outgoing.println(CLOSE); outgoing.flush(); // Make sure the data is sent! connection.close(); TextIO.putln("Connection closed."); break; } outgoing.println(MESSAGE + messageOut); outgoing.flush(); // Make sure the data is sent! if (outgoing.checkError()) { throw new IOException( "Error ocurred while reading incoming message."); } } } catch (Exception e) { TextIO.putln("Sorry, an error has occurred. Connection lost."); TextIO.putln(e.toString()); System.exit(1); } } // end main() } //end class CLChatServerThis program is a little more robust than DateServe. For one thing, it uses a handshake to make sure that a client who is trying to connect is really a CLChatClient program. A handshake is simply information sent between client and server as part of setting up the connection. In this case, each side of the connection sends a string to the other side to identify itself. The handshake is part of the protocol that I made up for communication between CLChatClient and CLChatServer. When you design a client/server application, the design of the protocol is an important consideration. Another aspect of the CLChat protocol is that every line of text that is sent over the connection after the handshake begins with a character that acts as a command. If the character is 1, the rest of the line is a message from one user to the other. If the character is 0, the line indicates that a user has entered the "quit" command, and the connection is to be shut down.
Remember that if you want to try out this program on a single computer, you can use two command-line windows. In one, give the command "java CLChatServer" to start the server. Then, in the other, use the command "java CLChatClient 127.0.0.1" to connect to the server that is running on the same machine.
There are several problems with the CLChat programs. For one thing, after a user enters a message, the user must wait for a reply from the other side of the connection. It would be nice if the user could just keep typing lines and see the other user's messages as they arrive. It's not easy to do this in a command-line interface, but it's a natural application for a graphical user interface. My next example is a GUI chat program. The class ConnectionWindow, which you will find in the source code file ConnectionWindow.java, supports two-way chatting between two users over the Internet. The ConnectionWindow class is fairly sophisticated, and I don't want to cover everything that it does, but I will describe some of its functions. You should read the source code if you want to understand it completely.
A ConnectionWindow has a text-input box where the user can type messages that are to be sent to the partner on other side of the network connection. The user can send a message at any time by pressing return in the text-input box. At the same time, the user on the other end of the connection can do the same thing. How are these messages received? Each ConnectionWindow runs a separate thread that reads the messages sent from the other side of the connection. (Both the sent and received messages are displayed to the user in a large TextArea that acts as a transcript of the conversation.) Thus the sending and receiving of messages are handled by different subroutines which are executed by different threads. The two processes do not interfere with each other. The code for sending and receiving individual messages is essentially the same as the code in the CLChat programs.
There remains the question of how a connection can be established between two ConnectionWindows. As the ConnectionWindow class is designed, the connection must be established before the window is opened. The connected Socket is passed as a parameter to the ConnectionWindow constructor. This makes ConnectionWindow into a nicely reusable class that can be used in a variety of programs. The simplest approach to establishing the connection uses a command-line interface, just as is done with the CLChat programs. Once the connection has been established, a ConnectionWindow is opened on each side of the conection, and the actual chatting takes place through the windows instead of the command line. For this example, I've written a main() routine that can act as either the server or the client, depending on the command line argument that it is given. If the first command line argument is "-s", the program will act as a server. Otherwise, it assumes that the first argument specifies the computer where the server is running, and it acts as a client. The code for doing this is:
try { if (args[0].equalsIgnoreCase("-s")) { // Act as a server. Wait for a connection. ServerSocket listener = new ServerSocket(port); System.out.println("Listening on port " + listener.getLocalPort()); connection = listener.accept(); listener.close(); } else { // Act as a client. Request a connection with // a server running on the computer specifed in args[0]. connection = new Socket(args[0],port); } out = new PrintWriter(connection.getOutputStream()); out.println(HANDSHAKE); out.flush(); in = new TextReader(connection.getInputStream()); message = in.getln(); if (! message.equals(HANDSHAKE) ) { throw new IOException( "Connected program is not a ConnectionWindow"); } System.out.println("Connected."); } catch (Exception e) { System.out.println("Error opening connection."); System.out.println(e.toString()); return; } ConnectionWindow w; // The window for this end of the connection. w = new ConnectionWindow("ConnectionWindow", connection);As it happens, I've taken the rather twisty approach of putting this main() routine in the ConnectionWindow class! This means that you can run ConnectionWindow as a standalone program. If you run it with the command "java ConnectionWindow -s", it will run as a server. To run it as a client, use the command "java ConnectionWindow <server>", where <server> is the name or IP number of the computer where the server is running. I say that this approach is twisty because the main() routine could easily be in another class. It has nothing to do with the function of ConnectionWindow objects. (This is an illustration of the separation between the static and the non-static parts of a class -- a discussion that you might remember from earlier in this text.)
There is still a big problem with running ConnectionWindow in the way I've just described. Suppose I want to set up a connection. How do I know who has a ConnectionWindow server running? If I start up the server myself, how will anyone know about it? The CLChat programs have the same problem. What I would like is a program that would show me a list of all the "chatters" who are available, and I would like to be able to add myself to the list so that other people can tell that I am available to receive connections. The problem is, who is going to keep the list and how will my program get a copy of the list?
This is a natural application for a server! We can have a server whose job is to keep a list of available chatters. This server can be run as a daemon on a "well-known computer", so that it is always available at a known location on the Internet. Then, a program anywhere on the Internet can contact the server to get the list of chatters or to register a person on the list. That program acts as a client for the server.
In fact, I've written such a server program. It's called ConnectionBroker, and the source code is available in the file ConnectionBroker.java. The main() routine of this server is similar to the main() routine of the DateServe example that was given at the beginning of this section. That is, it runs in an infinite loop in which it accepts connections and processes them. In this case, however, the processing of each request is much more complicated and can take a long time, so the main() routine sets up a separate thread to process each connection request. Once the connection is open, the server reads a command from the client. It understands three types of commands:
- A REGISTER command that adds a client to the list of available chatters. The server keeps this list in an internal data structure. The connection remains open and the client waits for some other user to request a connection with that client. Once a connection is made, the client is removed from the list -- the server does not support multiple connections to the same chatter.
- A SEND_CLIENT_LIST command requests a copy of the list of available chatters. The server responds by sending the list and closing the connection.
- A CONNECT command requests the server to set up a connection with one of the chatters in the list. The server sets up the connection, and -- if no error occurs -- informs both parties that a connection has been established. The two parties can then start sending messages to each other. (These messages actually continue to pass through the server. The direct network connections are between the server and the two clients. The server relays messages from each client to the other. It's done this way so that a ConnectionBroker will work with applets as clients, as long as the applets are loaded from the computer where the server is running. An applet is not ordinarily allowed to make network connections, except to the computer from which it was loaded.)
To use a ConnectionBroker, you need a program that acts as a client for the ConnectionBroker service. I have an applet that does this. The applet tries to connect to a ConnectionBroker server on the computer from which the applet was loaded. If no such server is running on that computer, you will see an error message at the top of the applet. It might say something like "Connection refused" or "Network not reachable". If you have downloaded this on-line textbook and are reading the copy on your own computer, you should be able to run the ConnectionBroker server on your computer and use the applet connect to it. Here is the applet:
If the applet does find a server, it will display the list of available chatters in the large area in the center of the applet. If no chatters are available on the server, then you'll just see the message "No connections available" at the top of the applet. If you want to establish a connection, click on one of the items in the list of chatters and then click the "Connect" button. A ConnectionWindow will open which you can use to chat with the selected chatter (unless some error occurs). As the applet runs, the list displayed in the applet might become out of date, so a person in the list might no longer be available. To get a new, updated list from the server, click the "Refresh" button.
If you want to add yourself to the list, enter your name or some other information about yourself in the text-input box at the bottom of the applet, and click on the button labeled "Register me with this info:". The text that you entered in the box appears in the list, and a window opens to await the incoming connection. This window contains the message "Waiting for connection". If some user of the BrokeredChat applet requests a connection with you, the window will display the message "Connected" and you can start chatting with that person. You can close the connection window at any time. If you do this before a connection request is received, this will remove you from the list of chatters on the server.
You can enter yourself multiple times in the list, if you want, and you can connect to multiple people on the list. A separate ConnectionWindow will open for each connection. You can even connect with yourself, so you can try out the applet even if you are the only user.
This networked chat application is still very much a demonstration system. That is, it is not robust enough or full-featured enough to be used for serious applications. I tried to keep the interactions among the server, the applet, and the connection windows simple enough to understand with a reasonable effort. If you are interested in pursuing the topic of network programming, I suggest you start by reading the three source code files for this example: the applet BrokeredChat.java, the server ConnectionBroker.java, and the window ConnectionWindow.java.
By the way, the ConnectionBroker server does not require that the clients that connect to it be chat programs. Once a connection relay has been set up between two clients, the server will happily transmit any messages that are sent through it. The only rule that the server enforces is that if either client sends a message that begins with the CLOSE command character, ']', then the connection will be closed. So the ConnectionBroker could easily support, say, a game of checkers between two clients instead of a chat session.
End of Chapter 10
[ Next Chapter | Previous Section | Chapter Index | Main Index ]