All about Sockets |
The section shows you how to write the server side of a socket connection through a complete client/server example. The server in the client/server pair serves "Knock Knock" jokes. Knock Knock jokes are favored by young children, 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 KKProtocol. KnockKnockServer contains the
main()
method for the server program and performs all the grunge work of listening to the port, establishing connections and reading from and writing to the socket. KKProtocol 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 implement 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. Typically, the ServerSocket class sits on top of a platform-dependent implementation hiding the details of any particular system from your Java program. Thus, when working with ServerSockets, you should always use the java.net.ServerSocket class and bypass the underlying system-dependent functions. In this way, your Java programs will remain system neutral.try { serverSocket = new ServerSocket(4444); } catch (IOException e) { System.out.println("Could not listen on port: " + 4444 + ", " + e); System.exit(1); }The constructor for ServerSocket will throw and exception if for some reason (the port is already in use) it could not listen on the specified port. In this case, the KnockKnockServer has no choice but to exit.
If the server successfully connected to its port, then the ServerSocket object was 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 connects to the same port (in this case, port 4444) that the server is listening to. When the client requests a connection, theaccept()
method accepts the connection, if nothing goes wrong, and returns a socket. This Socket object is a reference to the socket that the client used to establish the connection with the port. Now, both the server and the client are communicating to each other via the Socket and the ServerSocket is out of the picture for now. There's little bit more about the ServerSocket later.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 and example of on the previous page and will see 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; KKProtocol kkp = new KKProtocol();accept()
method. The next two lines similarly open an output stream on the same socket. The line simply declare and create a couple local strings used to read from and write to the socket. And finally, the last line creates a KKProtocol 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, that is, 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 the first line of text that the server says to the client from the KKProtocol. For this example, the first thing that server says is "Knock Knock!". When we examine the code for the KKProtocol object you'll see how this works.outputLine = kkp.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 sequense of code 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 writes something to its output stream (the server's input stream). When the client does response, the server passes the response to the KKProtocol object and gets a response from the KKProtocol object. The server immediates sends the reponse to the client via the output stream connected to the socket using calls toprintln()
andflush()
. If the server's response generated from the KKProtocol 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 = kkp.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 KKProtocol 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 KKProtocol 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 otherwise the data that passes back and forth would be meaningless. The protocol that your client/server uses 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 hostnameSocket kkSocket = new Socket("taranis", 4444); PrintStream os = new PrintStream(kkSocket.getOutputStream()); DataInputStream is = new DataInputStream(kkSocket.getInputStream()); StringBuffer buf = new StringBuffer(50); int c; String fromServer;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 and sets up some local variables.
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 all of the 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 just as you would run any other Java program using the Java interpreter. Remember to run the server on the machine that you specified the client program 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 may 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 will just hang. 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 will echo what you type and send 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 withServer: Knock Knock! Who's There? Client: Who's There? Server: TurnipAgain, the client will echo what you type and send 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 KKProtocol object will catch that and the server will respond with a message similar to this and start the joke over again:
The KKProtocol object is particular about spelling and punctuation, but not about capital and lower case letters.Server: You're supposed to say "Who's There?"! Try again. Knock Knock!Supporting Multiple Clients
The discussion about creating a ServerSocket to listen to a port and accepting a connection on that port indicated that once a connection had been accepted on the ServerSocket that the ServerSocket was out of the picture. That is only true for this particular example. To keep the KnockKnockServer example simple, we designed it to listen for just one connection request. However, you can use the same ServerSocket to continue to listen for connection requests after the first request has been serviced. Connection requests on a socket are queued. Thus the server processes connection requests sequentially.The basic flow of logic in such a server is this:
while (true) { accept a connection ; read/write to the client as necessary ; 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 that contains the
main()
method that simply creates a KKMultiServerThread and starts it. Run the new Knock Knock server and then run several clients in succession.See also
java.net.ServerSocket
All about Sockets |