Lab 9 - Socket Programming


Expand for the usual lab directions
  • Submit your completed lab online to CourSys (not Canvas, not GitHub).
    • Labs are marked on completion, not correctness, so complete each part to the best of your ability and learn.
    • Lab is due Sunday and may be submitted up to 3 days late. Further extensions possible only in exceptional cases.
  • It is recommended that students attend in-person lab sections for lots of support!
    • You are invited to come to any (or more than one) lab section.
    • There is no need to attend the section you are enrolled in.
    • There is no need to attend any lab sections: there is no attendance taken.
    • You can complete the lab on your own time or own computer if you like.
    • Time suggestions are given here to guide students who are working on the lab during lab times. However, the entire lab must be completed by everyone for marks.
  • While completing these labs, you are encouraged to help your classmates and receive as much help as you like. Assignments, however, are individual work and you must not work with another person on assignments.
    • I recommend coming to the lab and working with someone.
    • Failing that, I recommend getting together with someone in the class outside of lab time to work through the lab together.
    • Failing that, you are welcome to complete the lab exercise without any collaboration.
    • Each student must submit their lab to get marks.
    • Academic honesty expectations for labs are that each student types their own lab solution. It is OK to share ideas and help each others with specific coding issues and design. It is not permitted to submit another student's file for credit.
  • If using CSIL lab PC:
    • [Only CSIL PC] If in Windows, reboot to Linux; while booting, select Ubuntu from boot menu.
    • [Only CSIL PC] Don't setup GitHub tokens in the cmpt201 container because it may be shared with other users.
    • [Only CSIL PC] Delete any possible previous docker container before starting the lab:
      docker rm cmpt201
    • [Only CSIL PC] When done your lab, copy your solution out of the container, then execute the above docker command to delete your docker container to leave it clean for the next user.
  • You do not need to use record.


Goals for this lab


  • Understand and able to apply socket code.
  • Understand the lifecycle of a socket (when it opens / closes).
  • Able to run and debug a network application using sockets.


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 9 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 server.
    • One or more for running client.
    • 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, just re-run make in /build.
       
  3. Copy-and-paste the following 4 questions into the top of the provided client.c, and then answer them. You can run the code and read the code to discover the answers.
    /*
    Questions to answer at top of client.c:
    (You should not need to change the code in client.c)
    1. What is the address of the server it is trying to connect to (IP address and port number).
    2. Is it UDP or TCP? How do you know?
    3. The client is going to send some data to the server. Where does it get this data from? How can you tell in the code?
    4. How does the client program end? How can you tell that in the code?
    */


Task 2: Server thread per client


Complete the implementation for the server (server.c) so that it creates a new thread for each client.

  1. Implement server.c::main() and server.c::handle_client() so that they:
    • Spawn a new thread for each new client connection.
      • Hint - Thread function A template for this thread function is provided. You need to complete it.
    • When a new client is created, print a message to the screen with its client ID (a number you assign from client_id_counter).
    • Each time a message is received, increment total_message_count.
    • Each time a message is received, print out:
      • the client id for the client who sent the message,
      • the total number of messages received (total_message_count), and
      • the contents of the message received (a string).
         
    • Hints for main()
      • Hint - Accept Have main() loop forever calling accept().
      • Hint - New socket Remember that accept() returns a new socket file descriptor for the socket connecting to this client. Save this in a new variable because you'll need this new socket descriptor to receive messages from the client.
      • Hint - Client ID Each time a new socket is opened, increment the client_id_counter variable.
      • Hint - Thread Each time accept() gives you a new socket file descriptor, you'll need to spawn a new thread to handle the messages that client is sending to the server.
      • Hint - Thread Arguments The provided handle_client() thread function is expecting you to pass in two things inside of a struct: the socket file descriptor for the client = cft, and the client ID = client_id_counter. Have main() dynamically allocate an instance of the client_info struct and pass that pointer to the thread function. Have the thread function extract the values as needed and free the memory when no longer needed.
      • Hint - Thread Safe Use the provided mutexes to synchronize access to the global variables. Technically, if only one thread will ever access a variable, then we don't need the mutex; however, some of our global variables will be used in multiple threads, so let's just be safe!
      • Hint - Detach We don't want the server to wait for threads to finish (no pthread_join()), so after you create the thread for the new client socket, call pthread_detach() on it to have the memory be cleaned up automatically.

         

    • Hints for handle_client()
      • Hint - Args Start by extracting the client's socket file descriptor and client id and printing them just as a debug check to ensure you have the right values.
      • Hint - read Setup a loop that repeatedly calls read() to read data from the socket file descriptor. Each time you receive a message increment total_message_count (in a mutex), then print an update to the screen.
      • Hint - Buffer You'll need to setup a buffer to read data into. OK to assume that no message will be longer than the given buffer size (including line-feed).
      • Hint - Exit loop The read() call is blocking. When the client closes the socket, the read() call will exit with an error code. Use this to exit the loop.
      • Hint - Close socket When the client closes the socket you should then close the server's socket for that client (close()) and print a message to the screen saying the client is done. Then just exit the thread function!
    • The server keeps running forever; we have no way to terminate it (other than killing it with ctrl-c or the like).
       
  2. Output Samples
    • One client connecting:
      $ ./server
      New client created! ID 1 on socket FD 4
      Msg #   1; Client ID 1: Hello!
      Msg #   2; Client ID 1: This
      Msg #   3; Client ID 1: is
      Msg #   4; Client ID 1: another
      Msg #   5; Client ID 1: message!
      Msg #   6; Client ID 1: Last message.
      Ending thread for client 1

       

    • Two clients connecting:
      $ ./server
      New client created! ID 1 on socket FD 4
      Msg #   1; Client ID 1: hello world
      Msg #   2; Client ID 1: how are you?
      New client created! ID 2 on socket FD 5
      Msg #   3; Client ID 2: HELLO!
      Msg #   4; Client ID 2: I AM HERE NOW!
      Msg #   5; Client ID 2: YES!
      Msg #   6; Client ID 1: so am i
      Msg #   7; Client ID 2: I'M DONE! GOING AWAY NOW!
      Ending thread for client 2
      Msg #   8; Client ID 1: i am still here.
      Msg #   9; Client ID 1: but, now i am done too.
      Ending thread for client 1

3. [Optional] Challenges


  1. Optional: Lots of clients
    • Make a new file called many_clients.c which launches 100 concurrent clients, each sending 10 random messages to the server before exiting.
    • See if the server handles the messages OK (is the count corrupted?).
    • Play with some delays (such as a 100ms sleep between messages).
       
  2. Optional: Multithreaded Many Clients
    • Make your many_clients.c multithreaded so it spawns a new thread per client.
    • Remove the mutexes from your server and see if you can get it to incorrectly count packets.


Submission


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

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

Submit just your lab9.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.