Cliser Example: A Knock-Knock Service Using TCP and Java |
To illustrate the construction of a non-standard TCP/IP service in Java, this example builds a server that delivers "knock-knock" jokes, and a client to access the server, both using the TCP protocol.
Our "knock-knock" server will listen for client requests on port 1234. For those who are unaware or who have managed to block out their memories of this genre of "lame" jokes, the "knock-knock protocol" goes something like this:
Server: "Knock knock" Client: "Who's there?" Server: "Wendy" Client: "Wendy who?" Server: "Wendy wind blows de cradle will rock"
A knock-knock server thus waits for a connection, to which it replies with the string Knock knock. It waits for the client to respond with Who's there?, to which it responds with some first-part, usually a name. It waits for the client to respond with first-part who?, to which it responds with a last-part, which involves some semi-humorous word-play on first-part.
To satisfy its side of the "knock-knock protocol", a knock-knock client begins by connecting to the server, and then waiting for the server to send it the string Knock knock. I then displays that string, displays and replies with Who's there?, waits for and displays the server's response, displays and replies with the server's response followed by who?, and lastly gets and displays the server's final response.
To build a client and server for the daytime service, we follow the steps described previously:
$ java cliser.GUIThis displays the Cliser graphical user-interface, as shown below:
private static Random myGenerator = new Random(); private final static String [] FIRST_PART = { "Hatch", "Boo", "Cows go", "Doris", "Dwayne", "Max", "Police", "Wendy", "Wild Tom of the One-Eyed Left-Handed Monkey Boys" }; private final static String [] LAST_PART = {"Gesundheit!", "Don't cry, it's only a joke.", "No, cows go 'moo', owls go 'who'!", "Doris locked, that's why I had to knock!", "Dwayne the bathtub, I'm dwowning!", "Max no difference -- open ze door.", "Police open the door -- it's cold out here!", "Wendy wind blows, de cradle will rock!", "Well how many 'Wild Tom of the One-Eyed Left-Handed Monkey Boys' do you know?" };The first attribute is a variable to generate random integers. The second attribute is a constant array of strings, whose elements are the first parts of knock-knock jokes. The final attribute is a constant array of strings whose elements are the last parts of knock-knock jokes. Since we are using a multi-threaded server, we define these attributes as class variables and constants of class KnockKnockThread, so that each thread can share the same attributes.
Given these attributes, we can replace the comments in method interactWithClient() with the following statements:
public void interactWithClient() { int randomIndex = myGenerator.nextInt(FIRST_PART.length); this.send("Knock knock"); String reply1 = this.receive(); if ( reply1.equalsIgnoreCase("Who's there?") ) { this.send(FIRST_PART[randomIndex]); String reply2 = this.receive(); if ( reply2.equalsIgnoreCase(FIRST_PART[randomIndex] + " who?") ) this.send(LAST_PART[randomIndex]); else this.send("***'" + FIRST_PART[randomInt] + " who?' expected"); } else this.send("'Who's there?' expected"); }That's it! While not quite trivial, our server customization is this simple because the Cliser system architecture provides the easy-to-use, protocol-independent send() and receive() communication primitives.
Note that a Cliser server or thread using TCP can immediately interact with its client, because a connection has already have been accepted prior to the execution of interactWithClient().
To use Java's Random class, we must add the line
import java.util.Random;to the other import statement at the beginning of the file. The result is the file KnockKnockConcurrentTCPServer.java.
public void interactWithServer() { String received1 = this.receive(); if ( received1.equalsIgnoreCase("Knock knock") ) { System.out.println("Server: " + received1); String sent1 = "Who's there?"; System.out.println("Client: " + sent1); this.send(sent1); String received2 = this.receive(); System.out.println("Server: " + received2); String sent2 = received2 + " who?"; System.out.println("Client: " + sent2); this.send(sent2); String received3 = this.receive(); System.out.println("Server: " + received3); } else { System.err.println("KnockKnockTCPClient.interactWithServer(): " + "'Knock knock' expected, " + received1 + "' received"); System.exit(1); } }The result is the file KnockKnockTCPClient.java. Once again, our customization is quite easy thanks to Cliser's simple send() and receive() communication primitives.
$ javac -deprecation KnockKnockTCPClient.java $ javac -deprecation KnockKnockConcurrentTCPPServer.javaThese commands create the files KnockKnockTCPClient.class, and KnockKnockThread.class and KnockKnockConcurrentTCPServer.class, respectively. Because Cliser-generated clients and servers are derived from classes in the Cliser class library you must have this library installed on your machine for such clients and servers to compile and run correctly.
$ java KnockKnockConcurrentTCPServerThis will start our server running, listening for connections on port 1234. The server can also be run on a different port by specifying that port as a command-line argument.
$ java KnockKnockTCPClient myMachine.myDomainHere is a sample run of our client:
Server: Knock knock Client: Who's there? Server: Dwayne Client: Dwayne who? Server: Dwayne the bathtub, I'm dwowning!