8.1 Updates:
- 2023-10-07 Minor clarification on No Secondary Server message.
Multi-process, Multi-threaded Joke Server and Client
Copyright (c) 2023 By Dr. Clark Elliott with all rights reserved.
Overview:
Joke-checklist <-- Download and complete the JokeServer checklist. Submit to D2L along with your
Java source code.
In this assignment you will build a pair of multi-threaded servers that accept input from
multiple clients, and return appropriate output. In addition to the basic client-server model,
you will also implement a secondary administration channel to your servers, and manually
maintain the state of all conversations within your distributed application.
Each aspect of the specifications requires you to solve a particular client-server problem
while maintaining a conversation within a stateless protocol. The assignment is
designed to give you general-purpose exposure to writing code for a client-server
multi-processing environment, and working with threads, neither of which is trivial.
Your finished server is just for fun, but with minor changes it can serve as the basis for a
real, viable, client-server application handling thousands of client conversations
simultaneously. Although we will only be testing with from 1 to 5 clients at a time, your
program should be designed to run thousands of worker threads at once.
Note that we will not make these servers thread-safe, which is beyond the scope of this
assignment. However, you are free to do so if you have the expertise and wish to do so.
Administration:
- Supplemental links:
- Submission files for JokeServer ( precise names are rquired ):
Joke-checklist.html
JokeServer.java.txt ← Copied from JokeServer.java. Contains the code for three separately
running processes: JokeServer, JokeClient, and JokeClientAdmin. Also included will be your JokeLog.txt and
your Postings files as Java comments at the bottom You will likely have to copy your file from the command
line. (On Windows, use copy. On Mac/unix, use cp).
Executable files generated: JokeServer.class, JokeClient.class and JokeClientAdmin.class
- Additonally, you will create a JokeLog.txt file, then insert it as Java comments at the bottom
of your JokeServer.java (and thus JokeServer.java.txt) file.
- Complete the ColorServer first. You are free to use that code as a basis to start this
assignment. Note: you may not copy any other code, including InetServer code, or you will be
guilty of plagiarism. WRITE YOUR OWN CODE.
- Update your Joke-checklist as you make progress on the JokeServer assignment.
- There is a high priority on our being able to download your JokeServer.java.txt file,
compile JokeServer.java from it, and run your three java applications (JokeServer, JokeClient,
JokeClientAdmin) without any complications.
- Make sure that your program will compile when we issue "javac *.java" (twice) at the
command line prompt from within the directory where the java code resides. For this class, for
grading purposes, NEVER use java packages. That is, the package statement should not
appear anywhere in your source code.
- Submit to D2L well before the deadline.
- Be sure to include the required java header file as comments at the top of of your java file.
- Allow yourself enough time to get a basic version of the JokeServer running. Debugging
TCP/IP on your machine, having difficulties wtih your firewall, spawning runaway processes,
etc. comes with the territory, and you should not underestimate the amount of time this will
take you during the initial phases of this project.
- JokeLog.txt (Will later be inserted at the bottom of JokeServer.java as Java
comments.) Capture the console output from your running programs so that the output—showing
your working processes in each of the three terminal windows—can be placed in your JokeLog.txt
file. Manual Copy and Paste from the console is probably the easiest scheme, and
exactly what I suggest. I do NOT recommend writing to a file, because it takes a lot more work
and you'll have to address having three output streams from at least three running processes in
three files that later have to get concatenated together. Don't waste time making the
output fancy.
- Note that you have to maintain a theoretically unlimited number of arrays, or other
data structures, to keep track of client state—one for each conversation—for a
theoretically unlimited number of Joke Clients; you have to connect from two different kinds of
clients (Jokeclient and JokeClientAdmin); and so on. Each of these steps may take some thought,
so leave plenty of time to get this assignment finished.
- Allow yourself enough time to prepare the D2L submisison of your work exactly as
specified. This may take some thought and some experimentation the first time you do this. For
example, your programs must compile and run from the command line. You should NOT assume that
you can get this done at the last minute. And, you must get a TII report.
- I strongly recommend that you complete this assignment incrementally: Make
small additions to the code at a time, while always maintaining a running program.
- Read this whole assignment page before you get started so you know where all the hints, links
and explanations are.
Suggested Development Order:
- You can develop as you wish, but following is the partial, incremental development
order I recommend. After each step, clean up the interface code as necessary to meet the
console input and output specifications. You can use the checklist as a guide, and should
ypdate your checklist as you complete steps. Comment your code during development .
- Starting with your running ColorServer code: (a) rename the client and server classes
to JokeServer and JokeClient, (b) remove the ColorServer application code that selects
and sends colors (but leave the serializing and network code in place, leave in the basic
client and server loops, and the network connection and serialization architecture. Finally,
(c) modify the code to return a single joke from the your server as soon as the client
connects.
- Copy your JokeClient class code to a JokeClientAdmin class. Change the port
at which it is connecting to the server. Modify your JokeServer to run a second thread in a
server loop listening for JokeClientAdmin connections (see example code below). Connect from
the admin client and automatically toggle the server mode between joke and proverb on each
connection. Display on both the server console and the admin client console what the mode is
each time you toggle. If you are not clear on how to change the server mode in one method (and
thread—e.g. the Admin Worker) and read it in another (e.g. the Joke Worker) you can refer
to this Mode Changer Method as one way you might want to do this.
- You can see how to generate three executable, named .class files fron one Java source
file from this three executables from one Java file code.
- Return either a single joke or a single proverb to the JokeClient depending
on the server mode.
- At this point you should be presenting console output in each of your three
terminal windows, with regular announcements about what your three process are doing. Later,
using Copy and Paste, you will copy this output to your JokeLog.txt file.
- Return four jokes in order: JA, JB, JC, JD, then repeat over and over, and do the same
for proverbs PA, PB, PC, PD, etc.
- At the end of each joke cycle and proverb cycle, announce on the console "JOKE CYCLE
COMPLETED" or "PROVERB CYCLE COMPLETED."
- Connect from multiple Joke Clients and verify that the previous step works for each client,
independently without interference from other clients. THIS IS IMPORTANT. You will need to
have a separate data structure saving each conversation—one for each Joke Client. In
theory you should be able to support 10,000 independent conversations with your code.
- Return interleaved jokes and proverbs, depending on server mode at the time of any given
request, without losing track of where you are in the JA,JB,JC,JD cycle of jokes, and the
PA,PB,PC,PD cycle of proverbs for one client, verifying that there is no intereference in the
two respective cycle orders for each of jokes and proverbs. So, (without randomization) you
might have JA, PA, PB, JB, JC, JD, PC, PD, JA, JB, PA...
- Return interleaved jokes and proverbs without losing track of where you are in the
JA,JB,JC,JD cycle of jokes, and the PA,PB,PC,PD cycle of proverbs for multiple clients,
verifying that there is no interference from switching server mode from joke to proverb, and no
interference between any one of your multiple clients and another.
- Parse the user name in the JokeClient before entering the request loop. Insert
the user's name right after the "JA", "JB", "PA", "PB", etc. designation, and before the joke
or proverb. For novice programmers this string processing can be hard. Do this toward the
end. It is not a particularly critical part of the assignment. Utility code tips and questions
on string manipulation in java on the class D2L forums is encouraged. Example: "JA Joseph: Why
did the chicken cross the road? To get to the other side!"
- Re-randomize the jokes, and proverbs for each client conversation at the start of each
four-item joke and proverb cycle. (The J and P labels stay with the jokes.) Veryify that the
randomization of jokes and proverbs for one client conversation still does not interfere with any
other client conversation. Because randomizing makes development confusing, do this toward the end.
- Go through your entire program to verify that ALL specifications are met regarding file
naming, comments, program logic, console input, and output.
- Make sure your JokeClient and JokeClientAdmin processes will accept a first argument with a
different domain name or IP address. Run your clients and server on different machines if it is
practical for you to do so. Take some care that your firewall does not interfere with you
running your programs across the network. [To find the IP address of your machine under
Windows, type "ipconfig" at a command prompt; use "ifconfig" under unix/Mac.]
- Once your default JokeServer is fully working, proceed with the following steps. Make
SURE that when no arguments are passed to your client java programs they run at the default
locations, as given in the specifications.
- Change your JokeServer to accept an argument (see below) indicating that the JokeServer
should run at the secondary server port number, rather than at the default server port
number. The intent is that this will only happen after the first, default, JokeServer is
already running at the default port.
- Modify your JokeClient and JokeClientAdmin programs so that when they are given two
arguments, the first argument will point to the domain name or IP address of the server, and
the second argument, if present will be used as IP address of a second server.
Joke-postings:
You are required to make at least two scholarly postings in the D2L discussion forums
JokeServer thread. A scholarly post can be any of, e.g., a discussion thread you start on design aspects
of the JokeServer or clients and servers in general, bragging rights for some interesting extra feature you
plan on implementing, a well-formed question about how to implement or design the JokeServer,
bigger-picture ideas about scale-up design considerations, or data-protection
considerations. You can include annotated URLs as part of your discussion. Favorite
topics might have to do with discussions about maintaining conversation state. Or, you can
reply to any of the above that others have posted. Answers to coding or design questions are
always a big hit. Postings that initiate or take part in discussions are always more
highly prized. Submit your two (or more) postings with your Java code—at the bottom of
JokeServer.java as Java comments.
Multi-threaded Joke and Proverb Server:
JokeServer Conventions
JokeServer Detailed Specifications:
- Make sure your JokeClient asks for the user's name [to be used later] before
it enters the request loop. We should never have to enter the user name more than once per process.
- Inside of the loop, when the user presses <enter> this signals a connection to the
server. When your JokeClient connects to your JokeServer this initiates an algorithmic process
such that, ultimately, the next joke or proverb is displayed on the console of the
JokeClient. Whether it is a joke or a proverb depends on the current mode of the server.
- Build a template of exactly four jokes and four proverbs for use in your client/server
system. Put "JA " at the beginning of the first joke, "JB " at the beginning of the second, "PC
" at the beginning of the third proverb, and so on to help with debugging, and grading [this is
required]. The letters always stay with the same joke or proverb through the randomizing step.
- For each conversation with a client, complete sets of four jokes, [JA, JB, JC, JD]
and, independently, four proverbs [PA, PB, PC, PD] are returned to the client one at a
time. When all members of the jokes set or of the proverbs set have been returned, then print a
console message that the joke or proverb cycle is complete, and start over at the beginning of
the set. Multiple clients [theoretcially, many thousands of clients] run simultaneously, and
each client conversation is completely independent of all other client conversations.
- Your JokeClientAdmin connects to the server and toggles it to Proverb Mode , or
Joke Mode for ALL subsequent client connections within the respective
conversations.
- The username must appear (as specified above) as part of each joke or proverb JokeClient
console message. Additionally, the username must appear on the server console as well, each time a
client request is made (e.g., "New client request from user Joseph").
- Once your jokes and proverbs are being returned correctly, without interference
between client conversations, then at the beginning of each joke or proverb cycle, randomize
the jokes or proverbs before starting the cycle again. However, the rule still stands that no
joke or proverb can be repeated as part of a client conversation until ALL the jokes or
proverbs in that cycle have been seen by the client, at which point the cycle (and
randomization) is started all over again.
This radomizing is a small part of the assignment, but think about how this might be done. Did
you implement randomness efficiently? Does your solution scale up for 1,000 jokes? 1,000,000
jokes? Why might a set implementation, possibly with a linked data structure, be useful here,
rather than random access to an array? Note: efficiency in your implementation is desirable,
but will not affect your JokeServer grade. Comments in your code?
- Each time a client connects, select the next joke or proverb to
be sent as part of that conversation, and then insert the user's name
into the joke or proverb after the "JA " indication, and
before the body of the joke.
Asssuming the user's name is Joseph, you might start with the template
for our first joke:
JA <name-holder>: Why did the chicken cross the road? To get to the other side!
The server modifies the template for this client to produce:
JA Joseph: why did the chicken cross the road? To get to the other side!
- Proverbs are the same:
PD Joseph: The early bird gets the worm.
- Modify the server to accept a connection at port 5050 from an administration client (and,
later at 5051 for the secondary server). This will take some thought because you will have to
make an asynchronous thread call in your server, initiating a separate thread, to
start a second server listener waiting for JokeAdminClient connections. See the file joke-threads.html for some hints, and the file joke-pseudo.html for pseudo code.
- Write a second client, JokeClientAdmin that connects at the administration port and
toggles the server between Joke Mode and Proverb Mode.
State maintenance:
- How you maintain the state of client conversations is up to you, as long as you
always break the connection after each joke, or proverb is returned (because we are writing
explicitly for a connectionless protocol). But this will require some thought before you begin
development. I recommend that you write out your design with pencil and paper before beginning
to write computer code.
The general problem to be solved is that having established a client/server conversation (i.e.,
a history of your previous interactions), then broken the TCP/IP connection between client and
server, how do you retrieve that same conversation next time you connect? (Remember that there
may be 2,000 simultaneous conversations going on.)
- You have many ways that you can implement state maintenance. For
example:
- MINIMAL COOKIE ON THE CLIENT; FULL STATE ON THE SERVER: You can maintain the state of the
conversation by sending a unique "cookie" (e.g., a UUID would be one way to implement this, or
a large random number unlikely to have a collision) from the client when it first
connects. Pass the "cookie" from the client each time it connects, and use it on the server to
look up the state of the unique client conversation which has been stored there (e.g., in an
array of conversation-state objects). Use this mechanism to guarantee that the client never
gets a joke, or proverb, repeated until ALL of the jokes and proverbs in each respective
four-item cycle have been sent. Then, cycle through all the jokes or proverbs again, after
re-randomizing.
- FULL STATE ON THE CLIENT; NOTHING ON THE SERVER: You can send the entire state
back to the client after updating by the server, and maintain nothing at all on the server
side. That is, read and update the jokes-sent/proverbs-sent "checklist" (sent by the client) at
the server with the new joke or proverb marked as having been sent, and then send the whole
joke/proverb checklist back to the client. Each time the client connects, it sends the entire
state (contained in the checklist, plus the user name) again.
Or, because the only thing you actually need from the server, is what mode it is in, you can
simply send the username to the server (to display on the server console), and get back what
mode the server is in, then do EVERYthing else on the client.
- MAINTAIN PART OF THE STATE AT EACH END: You can implement anything in between the above
two schemes, such as keeping the cookie and the joke-state on the client, and the proverb-state
on the server; the cookie and the user name on the client, but the rest of the state on the
server; etc.
- NOTHING ON THE CLIENT: It is not possible to implement the JokeServer
this way. Why not?
- See joke-state.html for some
additional pointers on maintaining the state of the client.
Some further notes on state maintenance:
- There are other design elements to consider: For example, suppose you want to store
everything, including the jokes and proverbs on each of your 50,000 clients. What happens if
you hear from your legal department that one of your jokes is now considered libelous and must
be replaced? With this model you have 50,000 jokes to retrieve and replace, including on the
5,000 clients that will not be connected anytime soon, right? Ouch!
- MUST CONNECT TO SERVER: Even if you maintain the entire state of the client
conversation on the JokeClient, and update it there, you cannot determine joke-mode or
proverb-mode without connecting to the server. You can, however, download your four jokes and
four proverbs when you first connect, then, as noted above, just ask the server what mode it is
in each time you re-connect. When the jokes or proverbs are exhausted, you can reconnect to get
them randomized and downloaded again, or just randomize them on the client, depending on your
design and how often you envision the jokes and proverbs being updated.
- COOKIE VARIATION: Optionally you could initially send an empty cookie/state structure from
the client at startup time, and have the server assign a value (such as an incremental array
index) when it notices that the cookie or state structure has not been initialized. This allows
the use of a simple counter maintained by the server to identify conversations of various
clients. Thereafter, the client just sends the number assigned, as the cookie.
- FOR THE TWO-SERVER MODEL: When extending our client / server system to use multiple
servers (see below), we have additional design concerns. Each client will now have two
conversations going—one with each server. If you use the state-maintenance model of
storing everything on the server, and only a UUID/cookie on the client, then you only need to
pass the same UUID to either server, and you'll get put back in the right conversation from
each respective server. If, by contrast, you store everything on the client, you'll have to
maintain two conversation-objects on your client, and send the right one to the right
server each time.
- ALL client conversations are entirely indpendent, except that the corpus of joke templates
and proverb templates is the same, and all client coversations use the current server mode to
determine whether a joke or proverb is to be returned to the client.
- QUESTIONS: For a large-scale production system, when is it appropriate to send just a
cookie back to the client? When should the whole state be sent to the client? Suppose that in
place of jokes you were sending 4 GByte database entries? Suppose that instead of a static
joke, you were computing, in real time, some highly cpu-intensive output based on input data
assessed at the server at the time of the request? Which design would you use for each? How
would your solution scale?
Extending to two servers, primary and secondary:
Execution and JokeLog.txt:
- Run your sever with multiple clients active at the same
time. Interleave requests from different clients. Capture eight output responses from one
client in joke mode, and eight in proverb mode, showing that the jokes and proverbs are
returned randomly, but not repeated until all four in each set have been sent back. Annotate
this in your JokeOutput.txt log file.
- Repeat the process, but this time use your adminstration client to interleave
Joke Mode and Proverb Mode, showing that state is correctly maintained across changes between
modes.
- Put the output from your running sessions in a text log file and ANNOTATE the
output—highlighting the randomness of the jokes, the interleaving of modes, etc. NEVER
alter any of the output data, but you can add some white space and some annotation headings to
make it easier for us to read if you like.
- Format the presentation and submission of your work exactly as specified.
Bragging Rights:
These modifications are not required, but if you complete them, let us know about it in the
checklist comments section, and post your interesting work on the forums! Be SURE to print a
comment about the extra features to the console screens at startup time, and give clear
prompting. Your JokeServer and clients MUST still run in the default way. Pass an additional
argument to your programs to turn on the extra features.
- Any cool thing you feel like doing, and telling us about.
- It is not required that your JokeClientAdmin have the capability to shut down your server,
but it would be nice. Send the token "shutdown" from your JokeClientAdmin to shut down your
server[s]. There is a catch, however. Your request will be processed by an AdminWorker
thread. By the time you get to your JokeWorker, your JokeServer main listening thread will be
blocked waiting, and your JokeAdminServer main listening thread will also likely be blocked
waiting. Neither will automatically wake up so that they can be shut down. Even if you change a
loop-control variable for each loop to false, these servers will not notice it until you
wake them up with another request. To see a way of gracefully shutting down your admin server
loop with a single connection, within the worker dialog and "reaching back" to kill off the
parent that called the admin worker thread see the "HostServer" assignment. Now, how can you
kill off the main Joke listening thread?
- Make your client and server somewhat fault-tolerant by writing the state to disk after
each request at both the client side and the server side, so that if either crashes, on restart
they read the state back in from disk before resuming operation. You will need to identify your
user to the JokeClient in this case, and the user will need to have a unique user name, or be
verified by a unique ID (email address?)
- Hard: If it is available see the associated extra credit project for creating an
asynchronous JokeServer Client, possibly that also connects to multiple JokeServers.