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
    • Once you have the files, you are welcome to copy them elsewhere, such as your course-work repo.
       
  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 your lab10.c C code to CourSys; the file name must be an exact match to what CourSys is expecting, otherwise it won't accept it.

Submissions will be marked for completion. It must be valid C code that runs (however we are unlikely to actually compile and run the code). You do not need to complete any optional steps.