Project 1 - Pizza Consumers Union
This assignment can be completed either individually, or in groups with a maximum of 2 members. You can discuss problems and potential solutions with other students, but you cannot share completed programs or significant pieces of completed code. See the honor code in the syllabus for more details.
This project will serve as an introduction to TCP network socket programming in C. In this assignment, you will construct both a client application and a server application that communicate using TCP to jointly determine the most cost-effective pizza purchase.
High-Level Assignment Description
Imagine it is 11pm, you're hungry, and you want pizza. You have 2 choices: Pizzas'R'Us sells a 12-inch pizza for $6.99, and Lots'O'Toppings sells a 14-inch pizza for $8.99. Which is the better deal, as determined by the lowest price per square inch? You calculate that Pizzas'R'Us goes for $0.06/square inch, while Lots'O'Toppings goes for $0.05/square inch. Thus, Round'N'Saucy is the better deal.
Client: The client application interacts with the user. In this assignment, the client should print a welcome message to the user, and prompt the user for the vendor name, diameter and price of the first pizza choice, and then the vendor name, diameter and price of the second pizza choice. The client must send this information to the server, which will do the cost/benefit calculation and return a result. The client then presents this result to the user.
Example client output:
Welcome to the pizza program
Enter the name of pizza parlor 1: Pizzas'R'Us
Enter the diameter of pizza 1 in inches: 12
Enter the price of pizza 1: $6.99
Enter the name of pizza parlor 2: Lots'O'Toppings
Enter the diameter of pizza 2 in inches: 14
Enter the price of pizza 2: $8.99
Connecting the server...
The cost per square inch of pizza 1 is: $0.06
The cost per square inch of pizza 2 is $0.05
Lots'O'Toppings offers the most economical choice. Enjoy!
Server: The server application listens for messages from multiple clients, performs calculations on their behalf, and returns a message indicating the lowest-cost pizza per square inch, along with the exact cost per square inch.
Example server output: None! (The server should be silent during normal operation)
Low-Level Assignment Details
This assignment should be completed in C code that follows the C99 standard. It should compile and run without errors on a modern Linux system using a recent GCC compiler. The result of compilation should be two binaries named client and server that communicate via TCP sockets.
Client: The client should be invoked with the following command, which provides two arguments:
client <server IP> <server port>
Server: The server should be invoked with the following command, which provides the port to listen on as the only argument:
server <server port>
Note: You can invoke the client and server directly from the Eclipse IDE or from the terminal. To run directly from Eclipse, you will need to tell Eclipse what arguments to use. Go to Run->Run Configuration->Your Project Name->Arguments and the command line arguments (to pass to your program) there.
The server listens for messages on a port known to the clients. If the server cannot bind to the port that you specify, a message should be printed on standard error and the program should exit. You shouldn't assume that your server will be running on a particular IP address, or that clients will be coming from a predetermined IP address. Both the client and server should generate an appropriate error message and terminate when given invalid arguments.
The server should be able to queue connections from multiple clients. i.e., it will handle the client connections sequentially, but should be able to accept connections from multiple clients. After servicing the current client to completion, it should proceed to the next. If multiple clients simultaneously try to send messages to the server, the server should process them one at a time (in any order). Thus, in this assignment, you do not need to implement an event-driven or multi-threaded server. The server should execute and wait for clients to connect until it is killed by the user via CTRL-C.
During normal operation, the server should be silent, and not print out any debug information. However, debugging will be valuable to you during program development. Thus, it is recommended that you create debug messages, but provide a convenient way to toggle them on or off. (For instance, you might declare a global constant variable, i.e. const int DEBUG_MODE=1; and set it to 1 while developing your program, and 0 before turning in your assignment.)
The client must convey the following details to the server. (A client application that does the cost calculation itself will receive no credit! This program would be trivial if we weren't learning about networks and sockets at the same time).
- Vendor of pizza 1
- Diameter of pizza 1
- Cost of pizza 1
- Vendor of pizza 2
- Diameter of pizza 2
- Cost of pizza 2
This information must be conveyed from client to server using the following packet header format.
(32 bits wide)
|16 bits - Cost of pizza 1 ("dollars")||16 bits - Cost of pizza 1 ("cents")|
|16 bits - Diameter of pizza 1 (whole number of inches)||16 bits - Diameter of pizza 1 (number of 1/100 inches)|
|16 bits - Cost of pizza 2 ("dollars")||16 bits - Cost of pizza 2 ("cents")|
|16 bits - Diameter of pizza 2 (whole number of inches)||16 bits - Diameter of pizza 2 (number of 1/100 inches)|
|16 bits - Number of characters in pizza vendor 1 name||16 bits - Number of characters in pizza vendor 2 name|
|Variable length - Pizza vendor 1 name (as ASCII string)|
|Variable length - Pizza vendor 2 name (as ASCII string)|
Note 1: The cost and diameter values have been split into both whole number and fractional (n*1/100) value fields, each containing 16-bit integer values. Be sure to use the correct ntoh and hton wrapper function when sending the integer values! Here are some examples to guide you:
- 6.99 dollars - Transmit as 6 and 99 (each in 16 bit wide integer fields)
- 6.09 dollars - Transmit as 6 and 9
- 12.75 inches - Transmit as 12 and 75
- 12.5 inches - Transmit as 12 and 50
- 12.05 inches - Transmit as 12 and 5 (representing 5*1/100 inches)
- 12.005 inches - Transmit as 12 and 0 (or round up to 1 if desired)
Note 2: The vendor names (as ASCII strings) include the NULL character \0 at the end. Further, the count fields that indicate the number of characters in the vendor names do include the null character. the number of characters in the vendor names is its own explicit field in the packet header. While we could have made a different design decision here, by picking a standard, programs should be interoperable between class members.
The server, upon receiving the message from the client and performing its calculations, must send a response with the following details.
- Cost/square-inch of pizza 1
- Cost/square-inch of pizza 2
- Name of most cost-effective vendor
This information must be conveyed from server to client using the following packet header format. Once again, the cost has been split into whole number and fractional value fields.
(32 bits wide)
|16 bits - Cost/sq. in of pizza 1 ("dollars")||16 bits - Cost/sq. in of pizza 1 ("cents")|
|16 bits - Cost/sq. in of pizza 2 ("dollars")||16 bits - Cost/sq. in of pizza 2 ("cents")|
|16 bits - Number of characters in winning vendor name||Variable length - Pizza vendor 1 name (as ASCII string)|
Again, the ASCII string includes the null character at the end.
- You must implement the program in C and use the following GCC options in Eclipse to set the compiler to a very picky mode: -std=c99 -Wall -Wextra -D_POSIX_SOURCE
- -std=c99 (Use the more modern C99 standard for the C language)
- -Wall and -Wextra (Turn on all warnings. By viewing and fixing issues that generate warnings, you will produce better, safer, C code)
- -D_POSIX_SOURCE (Includes the POSIX libraries, which provide essential functions for socket libraries)
- These options are set in: Project Menu-> Properties -> C/C++ Build (expand the category) -> Settings -> Tool Settings -> GCC C Compiler
- Warnings tab: Ensure box for -Wall is checked
- Warnings tab: Ensure box for -Wextra is checked
- Miscellaneous tab: Type in -std=c99 Append this to what is already in the field! The completed line should look like this: -c -fmessage-length=0 -std=c99
- Add a new entry for _POSIX_SOURCE (without the D, because Eclipse provides that for you) in the symbols/defined symbols category.
- If your program produces any warnings during compilation (with these options), 5 points will be deducted.
- If your program doesn't compile, zero points will be awarded.
- All communication must be done using TCP sockets for reliable data communication.
- Your server should not be terminated abruptly when the user enters CTRL-C. Just think of all the temporary data stored in memory that might be lost if that happened. Instead, your server should "capture" the user's keystroke (technically, the SIGINT interrupt triggered by CTRL-C), call a function that properly shuts down and cleans up the server by closing the sockets, and then exit gracefully.
The following restrictions should simplify the program you are writing and eliminate some headaches:
- Assume that calls to send() and recv() are fully successful. That is, if you give send() 512 bytes, it will accept all 512 bytes, and the corresponding call to recv() will yield all 512 bytes. This is a big assumption, and is only true for small messages on idle systems. In reality, you might call send() with 512 bytes, and it only takes 400 bytes! It is up to you to re-send the remaining 112 bytes. Similarly, when you call recv(), you might only get 256 bytes the first time, and have to call it again to get the remaining 256 bytes. But, we won't worry about that. This time... (for a solution, see Beej's guide section 7.3)
- Assume that all Pizza Vendor names are a single word, i.e. "Lots'O'Toppings instead of Lots of Toppings". String handling in C is primitive at best... Now you can just use scanf() to take all input from the user.
Tips and Comments
- Visit Beej's Guide to Network Programming and read it before starting this project.
- You probably want two Eclipse projects - one for the client application, and the other for the server.
- You will need to tell Eclipse what command-line arguments to run when executing your program. Go to Run->Run Configurations to set the arguments. Or, you may prefer to simply run your program at the command lie by opening up two terminal windows or tabs.
- I had difficulty getting Eclipse to run two projects (i.e. the client project and server project) at the same time and maintain separate console windows. Thus, I had to run them from the console during final integration and testing.
- If you are bad at indenting your code properly, Eclipse can do that for you. Simply select all the lines in your source file, and choose Source->Correct Indendation.
- You can run both the client and server application on the same computer. In this case, the hostname to connect to is either localhost or 127.0.0.1. (This IP address is referred to as local loopback, because it allows the machine to communicate with itself). Note how, by using sockets, we have constructed a client/server pair that can either run on the same machine, or on different machines!
- There is no guarantee, however, that I will run both parts of your program on the same machine.
- If your program crashes, you may not be able to re-use the same port immediately. (The OS will think it is still in use.) Just pick another port number, or get a cup of coffee. Remember to avoid the pre-defined ports. It is best to use ephemeral ports for this program (in the range of 49152–65535)
- Google is your friend. (The Internet knows a few things about networking programming...)
- Look up the scanf() function to obtain formatted input data from the user (e.g. a string, integer or float)
- For an example of malloc(), structures, casting, string arrays, etc..., see this short sample program
- Need C programming resources? Visit the class Resources page
The Eclipse IDE can package up your entire project into a Zip-compressed archive file. Inside the archive are all of your source files along with a "Makefile" indicating how your project is to be compiled and executed. This is a common way in which Linux applications are distributed in source code form. I can uncompress, build, and run your project with the following commands
To produce the archive, go to File->Export->General->Archive File. Select your project to include in the archive and provide a filename. Upload the resulting compressed file to Sakai. Depending on how you have your project structured, you may need to upload two files, one for the server, and one for the client application.