All About Sockets |
This section shows you how to write the server side of a socket connection, with a complete client/server example. The server in the client/server pair serves "Knock Knock" jokes. Knock Knock jokes are favored by young children and are usually vehicles for bad puns. They go like this:Server: "Knock knock!"
Client: "Who's there?"
Server: "Dexter."
Client: "Dexter who?"
Server: "Dexter halls with boughs of holly."
Client: "Groan."The example consists of two independently running Java programs: the client program and the server program. The client program is implemented by a single class, KnockKnockClient, and is based on the EchoTest example from the previous page. The server program is implemented by two classes: KnockKnockServer and KKState. KnockKnockServer contains the
main()
method for the server program and performs all the grunt work of listening to the port, establishing connections, and reading from and writing to the socket. KKState serves up the jokes: it keeps track of the current joke, the current state (sent knock knock, sent clue, and so on) and serves up the various text pieces of the joke depending on the current state. This page will look in detail at each class in these two programs and finally show you how to run them.The Knock Knock Server
This section walks through the code that implements the Knock Knock server program. Here's the complete source for the KnockKnockServer class. The server program begins by creating a new ServerSocket object to listen on a specific port. When writing a server, you should choose a port that is not already dedicated to some other service. KnockKnockServer listens on port 4444 because 4 happens to be my favorite number and port 4444 is not being used for anything else in my environment:ServerSocket is a java.net class that provides a system-independent implementation of the server side of a client/server socket connection. The constructor for ServerSocket throws an exception if for some reason (such as the port is already being used) it can't listen on the specified port. In this case, the KnockKnockServer has no choice but to exit.try { serverSocket = new ServerSocket(4444); } catch (IOException e) { System.out.println("Could not listen on port: " + 4444 + ", " + e); System.exit(1); }If the server successfully connects to its port, then the ServerSocket object is successfully created and the server continues to the next step, which is to accept a connection from a client.
TheSocket clientSocket = null; try { clientSocket = serverSocket.accept(); } catch (IOException e) { System.out.println("Accept failed: " + 4444 + ", " + e); System.exit(1); }accept()
method blocks (waits) until a client starts up and requests a connection on the port (in this case, port 4444) that the server is listening to. When theaccept()
method successfully establishes a connection with the client, it returns a new Socket object, which is bound to a new, local port. The server can communicate with the client over this new Socket on a port that is different from the one that it was originally listening to for connections. So the server can continue to listen for client connection requests on the original port throught the ServerSocket. This version of the example program doesn't listen for more client connection requests. However, a modified version of the program, provided later, does.The code within the next
try
block implements the server side of the communication with the client. This section of the server is remarkably similar to the client side (which you saw an example of on the previous page and will see again later when we walk through the KnockKnockClient class):Let's start with the first 6 lines:
- Open an input and output stream to the socket.
- Read from and write to the socket.
The first two lines of the code snippet open an input stream on the socket returned by theDataInputStream is = new DataInputStream( new BufferedInputStream(clientSocket.getInputStream())); PrintStream os = new PrintStream( new BufferedOutputStream(clientSocket.getOutputStream(), 1024), false); String inputLine, outputLine; KKState kks = new KKState();accept()
method. The next two lines similarly open an output stream on the same socket. The next line simply declares and creates a couple of local strings used to read from and write to the socket. And finally, the last line creates a KKState object. This is the object that keeps track of the current joke, the current state within the joke, and so on. This object implements the protocol--the language that the client and server have agreed to use to communicate.The server is the first to speak, with these lines of code:
The first line of code gets from the KKState object the first line that the server says to the client. For this example, the first thing that server says is "Knock! Knock!".outputLine = kks.processInput(null); os.println(outputLine); os.flush();The next two lines write to the output stream connected to the client socket and then flush the output stream. This code sequence initiates the conversation between the client and the server.
The next section of code is a loop that reads from and writes to the socket thereby sending messages back and forth between the client and the server while they still have something to say to each other. Since the server initiated the conversation with a "Knock! Knock!", the server must now wait for a response from the client. Thus the
while
loop iterates on a read from the input stream. ThereadLine()
method waits until the client responds by writing something to its output stream (the server's input stream). When the client responds, the server passes the client's response to the KKState object and asks the KKState object for a suitable reply. The server immediately sends the reply to the client via the output stream connected to the socket, using calls toprintln()
andflush()
. If the server's response generated from the KKState object is "Bye.", this indicates that the client said it didn't want anymore jokes and the loop quits.The KnockKnockServer class is a well-behaved server, so the last several lines of this section of KnockKnockServer clean up by closing all the input and output streams, the client socket, and the server socket.while ((inputLine = is.readLine()) != null) { outputLine = kks.processInput(inputLine); os.println(outputLine); os.flush(); if (outputLine.equals("Bye.")) break; }os.close(); is.close(); clientSocket.close(); serverSocket.close();The Knock Knock Protocol
The KKState class implements the protocol that the client and server use to communicate. This class keeps track of where the client and the server are in their conversation and serves up the server's response to the client's statements. The KKState object contains the text of all the jokes and makes sure that the client gives the proper response to the server's statements. It wouldn't do to have the client say "Dexter who?" when the server says "Knock! Knock!".All client/server pairs must have some protocol with which they speak to each other, or the data that passes back and forth would be meaningless. The protocol that your own clients and servers use is entirely dependent on the communication required by them to accomplish the task.
The Knock Knock Client
The KnockKnockClient class implements the client program that speaks to the KnockKnockServer. KnockKnockClient is based on the EchoTest program in the previous section and should be somewhat familiar to you. But let's go over the program anyway and look at what's happening in the client, while keeping in mind what's going on in the server.When you start the client program, the server should already be running and listening to the port waiting for a client to request a connection.
Thus the first thing that the client program does is open a socket on the port that the server is listening to on the machine that the server is running on. The KnockKnockClient example program opens the socket on port number 4444 which is the same port that KnockKnockServer is listenting to. KnockKnockClient uses the hostnamekkSocket = new Socket("taranis", 4444); os = new PrintStream(kkSocket.getOutputStream()); is = new DataInputStream(kkSocket.getInputStream());taranis
, which is the name of a (hypothetical) machine on our local network. When you type in and run this program on your machine, you should change this to the name of a machine on your network. This is the machine that you will run the KnockKnockServer on.Then the client opens an input and output stream on the socket.
Next comes the loop that implements the communication between the client and the server. The server speaks first, so the client must listen first, which it does by reading from the input stream attached to the socket. When the server does speak, if it says "Bye.", the client exits the loop. Otherwise the client displays the text to the standard output, and then reads the response from the user, who types into the standard input. After the user types a carriage return, the client sends the text to the server through the output stream attached to the socket.
The communication ends when the server asks if the client wishes to hear another joke, the user says no, and the server says "Bye."while ((fromServer = is.readLine()) != null) { System.out.println("Server: " + fromServer); if (fromServer.equals("Bye.")) break; while ((c = System.in.read()) != '\n') { buf.append((char)c); } System.out.println("Client: " + buf); os.println(buf.toString()); os.flush(); buf.setLength(0); }In the interest of good housekeeping, the client closes its input and output streams and the socket:
os.close(); is.close(); kkSocket.close();Run the Programs
You must start the server program first. To do this run the server program, using the Java interpreter, just as you would any other Java program. Remember to run the server on the machine that the client program specifies when creating the socket.Next run the client program. Note that you can run the client on any machine on your network; it does not have to run on the same machine as the server.
If you are too quick, you might start the client before the server has a chance to initialize itself and begin listening on the port. If this happens you will see the following error message when you try to start the client:
If this happens just try to start the client again.Exception: java.net.SocketException: Connection refusedYou will see the following error message if you forget to change the hostname in the source code for the KnockKnockClient program.
Modify the KnockKnockClient program and provide a valid hostname for your network. Recompile the client program and try again.Trying to connect to unknown host: java.net.UnknownHostException: taranisIf you try to start a second client while the first client is connected to the server, the second client just hangs. The next section talks about supporting multiple clients.
When you successfully get a connection between the client and server, you will see this displayed to your screen:
Now, you must respond with:Server: Knock! Knock!The client echos what you type and sends the text to the server. The server reponds with the first line of one of the many Knock Knock jokes in its repertoire. Now your screen should contain this (the text you typed is in bold):Who's there?Now, you should respond with:Server: Knock! Knock! Who's there? Client: Who's there? Server: TurnipAgain, the client echos what you type and sends the text to the server. The server responds with the punch line. Now your screen should contain this (the text you typed is in bold):Turnip who?"If you want to hear another joke type "y", if not type "n". If you type "y", the server begins again with "Knock! Knock!". If you type "n", the server says "Bye.", causing both the client and the server to exit.Server: Knock! Knock! Who's there? Client: Who's there? Server: Turnip Turnip who? Client: Turnip who? Server: Turnip the heat, it's cold in here! Want another? (y/n)If at any point you make a typing error, the KKState object catches it, the server responds with a message similar to this, and starts the joke over again:
The KKState object is particular about spelling and punctuation, but not about upper and lower case letters.Server: You're supposed to say "Who's there?"! Try again. Knock! Knock!Supporting Multiple Clients
To keep the KnockKnockServer example simple, we designed it to listen for and handle a single connection request. However, multiple client requests can come into the same port and consequently, into the same ServerSocket. Client connections requests are queued at the port, so the server must accept the connections sequentially. However, it can service them simultaneously through the use of threads--one thread to process each client connection.The basic flow of logic in such a server is this:
The thread reads from and writes to the client connection as necessary.while (true) { accept a connection ; create a thread to deal with the client ; end whileTry this: Modify the KnockKnockServer so that it can service multiple clients at the same time. Here's our solution, which is comprised of two classes: KKMultiServer and KKMultiServerThread (notes). KKMultiServer loops forever listening for client connection requests on a ServerSocket. When a request comes in KKMultiServer accepts the connection, creates a new KKMultiServerThread object to process it, hands it the socket returned from
accept()
, and starts the thread. Then the server goes back to listening for connection requests. The KKMultiServerThread object communicates to the client by reading from and writing to the socket. Run the new Knock Knock server and then run several clients in succession.See also
java.net.ServerSocket
All About Sockets |