Lab 10 - Socket Programming: Multiple Clients

Goals for this lab


  • Apply your understanding of multi-threading and synchronization to client-server sockets.
  • Understand a parallel flow of information to a server.
  • Able to stop a thread.


Prerequisite Skills for This Lab


  • Linux socket programming.
  • C Skills
    • Able to use structs, arrays, pointers
    • Able to compile, run, and debug C programs using CMake.


Task 1: Download & Compile


  1. Clone the Lab 10 starter project
     
  2. Compile and run the program. Use multiple terminals:
    • One for nvim: I suggest opening all .c files at once (nvim *.c). You can switch between them using the commands :b1 and :b2.
    • One for running the server: use build_and_run_server.sh.
    • One for running multiple clients: use build_and_run_clients.sh.
    • You only need to run cmake once to generate the makefiles in /build, assuming you don't create additional .c files.
    • Each time you edit server.c or client.c, re-run the appropriate build_and_run_xxxxxx.sh script.
       
  3. Copy-and-paste the following questions into the top of the provided server.c, and then answer them. You can run the code and read the code to discover the answers.
    /*
    Questions to answer at top of server.c:
    (You should not need to change client.c)
    Understanding the Client:
    1. How is the client sending data to the server? What protocol?
    2. What data is the client sending to the server?
    Understanding the Server:
    1. Explain the argument that the `run_acceptor` thread is passed as an argument.
    2. How are received messages stored?
    3. What does `main()` do with the received messages?
    4. How are threads used in this sample code?
    */


Task 2: Server Implementation


Complete the implementation for the server (server.c) so that..

  1. In server.c's main() function, find the // TODO for waiting until enough messages are received.
    • Add code to wait until the number of messages received is at least MAX_CLIENTS * NUM_MSG_PER_CLIENT.
    • OK to use a busy wait here if you like (loop that continuously re-checks it).
    • Be careful about critical sections. You'll need to use a mutex to safely read the number of messages.
       
  2. In server.c's run_acceptor(), find the // TODO for creating the threads.
    • Add new code to launch a new thread for each connected client.
    • Hint: All necessary parameters to pthread_create() are created and ready to pass in.
    • Remember to count the number of clients connected (num_clients).
    • When you accept a new connection, print "Client connected!\n" to the screen.
       
  3. In server.c's run_client(), find the // TODO.
    • Call add_to_list(), passing in the correct arguments to add the newly created node to the list.
    • Hint: All parameters are readily available to just be passed in.
    • Ensure you are writing thread-safe code. You'll need to lock something before calling add_to_list().
       
  4. Have your program cleanly shut-down threads:
    • Find the TODOs in run_acceptor().
    • To set the stop flag, you are looking for the run field in the clients.
    • You'll need to cleanup the thread and the socket.
    • Hint: use pthread_join() and close() respectively.
       
  5. Answer this question in the top of your server.c:
    • Explain the use of non-blocking sockets in this lab.
      • How are sockets made non-blocking?
      • What sockets are made non-blocking?
      • Why are these sockets made non-blocking? What purpose does it serve?
         
  6. Output Sample
    • Server side with 3 clients connecting:
      Client connected!
      Client connected!
      Client connected!
      Client connected!
      Not accepting any more clients!
      Collected: Hello
      Collected: Apple
      Collected: Car
      Collected: Green
      Collected: Dog
      Collected: Hello
      Collected: Hello
      Collected: Hello
      Collected: Apple
      Collected: Apple
      Collected: Apple
      Collected: Car
      Collected: Car
      Collected: Car
      Collected: Green
      Collected: Green
      Collected: Green
      Collected: Dog
      Collected: Dog
      Collected: Dog
      Collected: 20
      All messages were collected!

       

    • Running 3 clients at once.
      labs/lab10❯ ./build_and_run_clients.sh
      [ 33%] Built target server
      [ 66%] Built target client
      [100%] Built target server_sol
      Sent: Hello
      Sent: Hello
      Sent: Hello
      Sent: Hello
      Sent: Apple
      Sent: Apple
      Sent: Apple
      Sent: Apple
      Sent: Car
      Sent: Car
      Sent: Car
      Sent: Car
      Sent: Green
      Sent: Green
      Sent: Green
      Sent: Green
      Sent: Dog
      Sent: Dog
      Sent: Dog
      Sent: Dog

3. [Optional] Challenges


  1. Optional: Lots of clients
    • Launch many many clients to try and connect to the server. How good is performance as you scale the number of clients? How can you measure this automatically?
  2. Optional: Switch to using an atomic integer (#include <stdatomic.h>) for the count of number of messages received.
  3. Optional: Switch to using a condition variable to track number of messages received.

Submission


Create a new file name lab10.c which contains the code from:

  1. client.c
  2. followed by the code from server.c.

Submit just your lab10.c C code to CourSys by Sunday midnight. The file name must be an exact match to what CourSys is expecting, otherwise it won't accept it. Remember you can submit up to 3 days late with no penalty if needed (but please plan to get it done on time to stay up to date on the course!)

Submissions will be marked for completion. You do not need to complete any optional steps.