CIT5950 Project 3c: Event-driven Server Solved

50.00 $

Description

5/5 - (2 votes)

1             Project 3c: Event-driven Server

Overview

In Part B, you had implemented a multi-threaded TCP server that supports the 3-way handshake protocol. In this part, you are required to write a single-threaded server (async-tcpserver) that monitors multiple sockets for new connections using an event-driven approach, and perform the same 3-way handshake with many concurrent clients.

Instead of using threads, you will use the select API to monitor multiple sockets and an array to maintain state information for different clients. Along with the manual pages, read Beej’s guide to network programming (http://beej.us/guide/bgnet/) on how to use the select function. We will also provide some sample code on select for you.

1.1            Requirements

1.1.1           Source files and arguments

All of the requirements from Part A remain the same, with the following changes:

  • You MUST implement the server in async-tcpserver.c instead of tcpserver.c
  • The makefile MUST create the executable async-tcpserver instead of tcpserver

1.1.2           Handshake Protocol

All of the requirements from Part A apply to Part C.

1.1.3           End of Line characters

All of the requirements from Part A apply to Part C.

1.1.4           Miscellaneous

  • You MAY use the epoll family of functions to handle the asynchronous portion of the assignment instead of select.
  • You SHOULD read the manual pages in detail.
  • You SHOULD read the manual pages in detail, again.
  • The server MUST handle two different events for each client: Step 2 (three-way handshake first message HELLO X) and Step 4 (three-way handshake second message HELLO Z). This requirement refers to the Handshake Protocol above.
  • You MUST place the code for event handling logic into two functions named handle_first_shake and handle_second_shake.
  • You MAY assume that no more than 100 concurrent client connections will attempt to connect to the server.
  • You SHOULD reuse the client from Part A. You do not need to make any changes to the client for this part (unless there are still bugs).
  • All of the requirements from Part A apply to Part C.

1.2            Suggested Approach

This is a suggested approach. You are free to implement the solution in any manner you want.

  1. Start by copying your Part A code into the new source file.
  2. Instead of a single helper function to do the entire handshake protocol, refactor your code such that each individual handshake is handled by a separate event handler function. Your server should still work as a sequential server at this point.
  3. Create two file descriptor sets: one to track all of the file descriptors you are interested in, another to track those that are ready to be read. Be sure to initalise these correctly (by reading the manual pages).
  4. Create some kind of struct to track the state of a single client (e.g. client_state). Because the server will handle each individual handshake asynchronously, the client_state needs to remember what has already happened in order to perform the correct next step. Be sure to initialise it to a known default state.
  5. Since there are potentially multiple clients communicating with the server, you’ll need to keep track of multiple clients, possibly in some kind of array (e.g. a client_state_array). Setup this data structure and properly initialise it as well.
  6. Now for the asynchronous implementation. You’ll have to do some prep work before calling select:
    • Note the arguments to select (from the manual pages). The first argument is the highest file descriptor you are interested in (plus one). Since there are no clients connected right now, the only file descriptor that might have data to read is the listening socket. Be sure to add that socket to the “all” file descriptor set. Additionally, the highest file descriptor may change during the execution of the program (e.g. when you accept a new client), so you’ll need to track this value.
    • The second argument is a set of file descriptors that you want to read from. You could use the “all” file descriptor set, but unfortunately select modifies the set in-place; select essentially filters out file descriptors that aren’t ready to be read. This is why you have two file descriptor sets. The “all” set keep a record of all file descriptors, while the “read” set can safely be filtered.
    • Clear out whatever happens to be in the “read” set, then copy the “all” set to the “read” set. Now when select filters out non-ready file descriptors, the “read” set only has file descriptors that are ready to be read, and the “all” set still has a record of all the file descriptors of interest.
    • To make everything properly asynchronous, you need to disable blocking for the listening socket. Use fcntl to do this.
  7. Call select in a loop after all this setup is done. If everything is done correctly, you should be able to see your program exit the select call without error. If you check the contents of the “read”, you should see that the listening socket is still there. This is easier to do in gdb than with print statements (you should be using gdb anyway).
  8. Now you can start handling file descriptors. After select returns, scan through all the possible file descriptors and check if it is set in the “read” set. If so, then there is an additional check: is it the listening socket, or is it a client socket?
  9. If it is the listening socket, accept the incomming connection and store the new socket file descriptor into an unused client_state. Don’t forget to set this socket to non-blocking, add it to the “all” set, and update the highest file descriptor number you have seen so far. Now you can return to the start of the loop and select will filter-in this socket.
  10. If it is not the listening socket, then it must be one of the clients. Find the client_state for this socket in your client state data structure, and check which handshake to process, calling the appropriate handling function.
  11. Be sure to cleanup and reset everything after the second handshake.
  12. Test your asynchronous server by running a client. You should see all the handshakes print as expected.
  13. Further test your asynchronous server by running several clients back to back, sequentially. You should see each set of clients print the appropriate messages, in order.
  14. Stress test your asynchronous server by running several clients concurrently. The messages may print out of order due to interleaving. See the Testing Interleaving section below.

1.3            Submission

  • Your solution MUST compile when running make clean followed by make. Please do check this before submitting. Submissions that fail to compile will receive a 0.
  • You MUST submit all source and header files, and the makefile, in a tarball (.tar.gz) to the Gradescope assignment CIT 5950 Project 3c.
  • Your tarball SHOULD NOT contain compiled binaries or demo code files. • You have unlimited submissions until the due date specified by the syllabus.

1.4            Grading

Project 3c will be marked on five rubric items. The descriptions for these tests are in the The Autograder section.

There is no peer review for this assignment. You will not be marked on code style.

The score at the due date is final. Do note that they do not sync with Canvas immediately; the course staff must manually release them.

  1. Static Analysis : 0 points
  2. Proxy Test : 20 points
  3. Load Test : 30 points
  4. Interleaving Clients Test : 30 points
  5. Server Error Checking Test : 20 points

Total : 100 points

2             Testing Interleaving

One of the challenges with 3b and 3c is that your code needs to handle client interleaving. For example, the server may get the first message from ClientA, followed by ClientB and then ClientC. However, the second message from ClientB might arrive at the server before ClientA or ClientC. The autograder will create this interleaving by using a custom tcpclient. The information below will show you how to set up your client and server code to test this interleaving before you submit to the autograder.

  1. Run our concurrent request Python code

A python script has been provided with the sample code. To use this script, start your server, then run:

python3 ./concurrent-requests.py portNumber

where portNumber is the port used for the server.

This script will run your client program 100 times. The script should complete without errors and the server should still be running. If either program crashes, then there is likely an error in with thread handling (3b) or file descriptor management (3c). While this script will highlight major issues, it does not confirm that the server printed out all of the handshakes correctly. You can add counters for each handshake and print out the result. If both numbers are 100, your code is basically working and then you can test the interleaving.

  1. Introduce delays to your TCP client code to test your implementation

To create the interleaving, you need to add code to your TCP client. The example code provided below will result in a random delay (up to 100 msec) before the second message is sent from the client. You can do something similar to add a random delay between the connect and first message. The details of the code are in the comments. This delay will result in the server receiving the second message in a different client order than the first message. The script should exit without errors, the server should still be running, and the number of first and second handshakes should be 100. If you have questions about this testing, post on the course discussion forum or attend an office hour.

When you are ready to submit your code to the autograder, remove any extra print statements (such as printing out the number of handshake calls).

  1. Client Delay Code

Add the following include statement at the top of your code with the other include statements:

// Need to get timestamp to seed random number

#include <sys/time.h>

Add the following code just before the client sends the second message to the server:

// Extra code to create client interleaving;

// timestamp is only used to seed srand() so that each client sleeps

// a different time struct timeval timestamp; gettimeofday(&timestamp, NULL); // get current timestamp

// use usec part of timestamp to seed random numbers;

// cannot use seconds because clients spawn too quickly to cause

// a variation in the time

srand((int)timestamp.tv_usec);

// get a random number between 0 and 99 int ranSleep_ms = (rand() % 100);

// usleep takes number of usec, mult by 1000 to get ms usleep(1000 * ranSleep_ms);

// Code to send the second message to the server should be here…

 

 

  • 3c-6rxfzx.zip