Project 0: Environment Setup, Sockets
Due on Sunday, January 19th at 11:59 pm
#Quick Links
#Overview
Welcome to CS 118! We hope you have a fun experience working on our projects this quarter. To make things easier, we’ve designed a local environment that replicates the Gradescope autograder. In other classes, you might have used the autograder and submitted hundreds of times to perfect your solution. Since you can run the exact environment on your machine, you’ll have a much better (and faster) time making improvements to your solution.
To get started, please review the Environment Setup and Using the Local Autograder pages.
This project is meant to introduce you to socket programming basics. It is one of three in this class. They will all build on top of each other. It’s important that you finish this project in order to work on the next one. (If you’re not able to finish by the deadline, we do plan on releasing linkable shared binaries.)
#Notices
- If you’re using GitHub or a similar tool to track your work, please make sure that your repository is set to private.
- As per the syllabus, the use of AI assisted tools is not allowed. Again, we can’t prove that such tools were used, but we do reserve the right to ask a couple questions about your solution and/or add project related questions to the exam.
- In the provided autograding Docker container, there are reference binaries. Please do not reverse engineer the binaries and submit that code—which would be obvious and clear academic dishonesty. Remember, we do manually inspect your code.
- This project must be done individually.
#Motivation
You may be familiar with the program netcat
. Essentially, this program takes input from standard input and sends it to a network host of your choosing. It will also listen for input from said network host and output it on standard output.
Here’s an example of two hosts communicating with netcat
:
Host 1 (Client) | Host 2 (Server) |
---|---|
host1:~/project0 $ nc -4u localhost 8080 > Message 1 Message 2 > Message 3 > Message 4 > George Varghese Lixia Zhang Songwu Lu | host2:~/project0 $ nc -4ul 8080 Message 1 > Message 2 Message 3 Message 4 George Varghese > Lixia Zhang > Songwu Lu |
(Lines starting with > represent input into standard input (what you type). The extra lines between the messages are to show which messages happen in what sequence. These both don’t actually show in the real prompt.)
Here’s a diagram that (hopefully) makes it a bit more clear as to what these programs are doing.
Your goal is to create two programs (aptly named client and server) that do the exact same thing as the example above:
Host 1 (Client) | Host 2 (Server) |
---|---|
host1:~/project0 $ ./client localhost 8080 > Message 1 Message 2 | host2:~/project0 $ ./server 8080 Message 1 > Message 2 |
#Specification
We’re now ready to start your first 118 project! If you haven’t already, please clone the Starter Code.
Create two programs written in C/C++ (up to version 17). Both programs will use BSD sockets using IPv4 and UDP to listen/connect. While you may make both programs multi-threaded, it’s not recommended. Use non-blocking file descriptors to help.
#Libraries
Our Socket Programming Tips will show you how to use the
socket APIs in the standard way. However, for this project, you’ll be using our
very own libsocket
. It works just like the regular socket APIs-the only
changes
you’ll need to make are to replace the socket
, bind
, recvfrom
, sendto
calls with ones prepended with s_
. See libsocket for
more info. Note: libsocket
is only available in the Docker container.
No other third party libraries are allowed.
#Server
The first program, server
, takes one argument: the port number. The usage of server
is as follows:
./server <port>
server
will accept UDP datagrams on the given port. We know that the client is ready to receive data as soon as it sends over its first datagram. The server does not have to accept datagrams from any other client from this point forward. Now, the server will:
- start reading from the socket and output its contents to standard output.
- At the same time, it will start reading from standard input and forwarding its contents to the client.
Even though server
is doing two things at the same time, it’s possible to keep it single threaded. All you have to do is make both standard input and the socket non-blocking.
Here’s some example pseudocode for server
:
Set up socket
Make socket non-blocking
Make standard input non-blocking
Infinite loop:
recvfrom client
if no data has been received and client has not sent over a datagram yet:
continue
// Now, we know "who" the client is and can start sending data over
Set client connected to true
if data has been received:
write to standard out
read from standard in
if data available from standard in:
while no error sending:
sendto client
#Client
The second program, client
, takes two arguments: the hostname and the port number. The usage of client
is as follows:
./client <hostname> <port>
Note: While we ask you to accept a hostname, we’ll only test you on localhost
. In short, your logic can literally check if the second argument is “localhost
” and use the IP “127.0.0.1
”—otherwise, pass in the second argument verbatim to inet_addr
.
client
will send datagrams to the server over UDP. The client can always assume that the server has already started and is immediately ready for sending/receiving. We will always test it such that the server has started much before the client. Immediately, it will:
- start reading from the socket and output its contents to standard output.
- At the same time, it will start reading from standard input and forwarding its contents to the server.
Just like server
, even though client
is doing two things at the same time, it’s possible to keep it single threaded. All you have to do is make both standard input and the socket non-blocking.
Here’s some example pseudocode for client
:
Set up socket
Make socket non-blocking
Make standard input non-blocking
Infinite loop:
recvfrom server
if data has been received:
write to standard out
read from standard in
if data available from standard in:
while no error sending:
sendto server
Note that the code for the client is very similar to the server, but it doesn’t worry about whether or not the server has already sent over data.
#Potential Improvements
To make your life easier before you attempt Project 1, try to find a way to abstract your “listen loop” code into another file. After all, both the client and server use very similar code.
#Common Problems (list will be frequently updated)
- Don’t ever use
printf
. Just write directly to standard output. - Don’t ever use
fgets
. Just read directly from standard input. - Don’t ever use
strlen
. We’re working with raw bytes, not C strings. There’s no null terminator. - If you want to write out debugging messages, use
fprintf
tostderr
. - Test on ports >=1024. Under that requires root access.
- You must use a buffer size of 1024 bytes. We won’t send nor accept datagrams any larger than that.
- If you’re getting segmentation faults, try adding
-g -fsanitize=address
to yourMakefile
’sCPPFLAGS
variable. Follow Professor Smallberg’s Guide to Interpreting Sanitizer Messages. - If the autograder sees no data being sent/received, make sure you’re using
libsocket
’ss_recvfrom
and not regularrecvfrom
(along with the other 3 calls). Run this command to check if you’re using any non-libsocket
calls.
grep -nE '\b=?(socket|recvfrom|sendto|bind)\b' **/*.c
(This will show comments with these calls, so feel free to ignore those lines.) - The
Makefile
is designed for running using thetester
script. Running this directly outside of thetester
is unsupported, and may get errors likeld: library 'socket' not found
. Usetester interactive
instead to manually test your code.
#Autograder Test Cases
Note: Gradescope is the ultimate source of authority for your score. While the local autograder tries its best to replicate Gradescope’s environment, it doesn’t always match up (computer performance, kernel configuration, etc).
#0. Compilation
This test case passes if:
- Your code compiles,
- yields two executables named
server
andclient
, - and has no files that aren’t source code.
#1. Data Transport
-
Data Transport (Your Client <-> Your Server): Small, ASCII only file (10 KB)
test_self_ascii
(10 points)
This test case runs your server executable then your client executable. It will then input 10 KB of random data (ASCII only) in both programs’ stdin and check if the output on the respective other side matches. -
Data Transport (Your Client <-> Your Server): Small file (10 KB)
test_self
(10 points)
Same as 1, but could be any sort of data (just completely random bytes). -
Data Transport (Your Client <-> Reference Server): Small file (10 KB)
test_client_normal
(20 points)
Same as 2, but uses the reference server instead. -
Data Transport (Your Client <-> Reference Server): Small file (10 KB)
test_client_only
(20 points)
Same as 3, but inputs only 1 byte in the reference server. -
Data Transport (Reference Client <-> Your Server): Small file (10 KB)
test_server_normal
(20 points)
Same as 2, but uses the reference client instead. -
Data Transport (Reference Client <-> Your Server): Small file (10 KB)
test_server_only
(20 points)
Same as 5, but inputs only 1 byte in the reference client.
#Submission Requirements
Submit a ZIP file, GitHub repo, or Bitbucket repo to Gradescope by the posted deadline. You have unlimited submissions; we’ll choose your best scoring one. Make sure that you choose the best scoring one as your active submission. As per the syllabus, remember that all code is subject to manual inspection.
In your ZIP file, include all your source code + a Makefile that generates two
executables named server
and client
. Do not include any other files; the
autograder will reject submissions with those. If you’re using the starter
repository, the helper
tool has a zip
method that automatically creates a compatible ZIP file.