Lab Handout 6: Networking


The lab checkoff sheet for all students can be found right here.

Get Started

Before starting, go ahead and clone the lab6 folder, which contains the code for the file-server program discussed in Problem 3.

git clone /usr/class/cs110/repos/lab6/shared lab6

Problem 1: Hello Server Short Answers

Est. 25min.

Consider the following server called hello-server:

static const size_t kNumWorkers = 3;
static pid_t workerPIDs[kNumWorkers];
static void shutdownAllServers(int unused) {
  for (size_t i = 0; i < kNumWorkers; i++) {
    cout << "Shutting down server with pid " << workerPIDs[i] << "." << endl;
    kill(workerPIDs[i], SIGINT);
    waitpid(workerPIDs[i], NULL, 0);
  }
  cout << "Shutting down orchestrator." << endl;
  exit(0);
}

static void handleRequest(int client) {
  sockbuf sb(client);
  iosockstream ss(&sb);
  ss << "Hello from server (pid: " << getpid() << ")" << endl;
}

static void runServer(int server) {
  cout << "Firing up hello server inside process with pid " << getpid() << "." << endl;
  while (true) {
    int client = accept(server, NULL, NULL);
    cout << "Request handled by server (pid " << getpid() << ")." << endl;
    handleRequest(client);
  }
}

int main(int argc, char *argv[]) {
  int server = createServerSocket(33334);
  for (size_t i = 0; i < kNumWorkers; i++) {
    if ((workerPIDs[i] = fork()) == 0) runServer(server);
  }
  signal(SIGINT, shutdownAllServers);
  runServer(server); // let orchestrator process be consumed by same server                                                                                                                                    
  return 0;
}

If we launch the above executable on myth64, hit it 12 times by repeatedly typing telnet myth64 33334 at the prompt from a myth65 shell, and then press ctrl-c on myth64, we get the following:

myth64$ ./hello-server 
Firing up hello server inside process with pid 19941.
Firing up hello server inside process with pid 19940.
Firing up hello server inside process with pid 19942.
Firing up hello server inside process with pid 19943.
Request handled by server (pid 19940).
Request handled by server (pid 19942).
Request handled by server (pid 19941).
Request handled by server (pid 19943).
Request handled by server (pid 19940).
Request handled by server (pid 19942).
Request handled by server (pid 19941).
Request handled by server (pid 19943).
Request handled by server (pid 19940).
Request handled by server (pid 19942).
Request handled by server (pid 19941).
Request handled by server (pid 19943).
^CShutting down server with pid 19941.
Shutting down server with pid 19942.
Shutting down server with pid 19943.
Shutting down orchestrator.

Based on the code and test run you see above, answer each of the following short answer questions.

  1. Note the very first line of the server’s main function creates a server socket that listens to 33334 for incoming network activity. Why, after the for loop within main, are all of the child processes also listening to port 33334 through the same server socket descriptor?
  2. One way to describe port numbers is as "virtual process IDs". What might be meant by that, and why are virtual process IDs needed with networked applications?
  3. What happens if the call to exit(0) is removed from the implementation of shutdownServers and we send a SIGINT to the orchestrator process in the suite of 4 hello-server servers?
  4. If the call to the signal function is moved to reside above the for loop in main instead of below it, does that impact our ability to close down the full suite of 4 hello-server servers?
  5. The above program doesn’t close the server sockets in shutdownServers. Describe a simple way to ensure a server socket is properly closed with close() before the surrounding server executable exits.

Problem 2: Networking, Client/Server, Request/Response

(Est. 15min)

Problem 3: File Server

(Est. 40min)

In class, we built a server that used our subprocess function to run an external program. Using most of the same code (minus the subprocess, timing, caching, and JSON functionality), we can write a file server that sends files from our file system to a client, and also produces directory listings if the client requests a directory. Many World Wide Web servers have this ability built-in, and it is a quick way to access files on your web server. If you cloned the repository, you have all of the code for the file server.

In order to request a file, the client requests the server name and port number, followed by the file path, which has its root wherever the server is running. e.g. http://myth57:13133/filename.

The code for getFilename is shown below:

static string getFilename(iosockstream& ss) {
    string method, path, protocol;
    ss >> method >> path >> protocol;
    string rest;
    getline(ss, rest);
    cout << "\tPath requested: " << path << endl;
    if (path == "/") {
        // serve current directory
        return (".");
    }
    size_t pos = path.find("/");
    return pos == string::npos ? path : path.substr(pos + 1);
}

The three functions shown below determine whether the file requested by the client is a file or a directory, and format the fileContents appropriately. If the name requested is a directory, the opendir and readdir functions are used to populate an html listing, complete with hyperlinks. For actual files, the file is sent in plain text without any extra formatting.

/** 
 * Function: listDir
 * ------------------------------
 *  Populates fileContents with a directory listing in HTML
 */
static void listDir(string& fileContents, const string& directoryPath, bool& isHTML) {
    isHTML = true;

    /* Create a list of all entries in this directory, in (filename, path) pairs
     * e.g. (myfile.txt, samples/mydir/files/myfile.txt).
     */
    vector<pair<string, string>> containedFiles;

    DIR *dir = opendir(directoryPath.c_str());
    if (dir != NULL) {
        struct dirent *ent = readdir(dir);

        // Loop through every entry in this directory
        while (ent != NULL) {
            string entryFileName = string(ent->d_name);
            string pathToFile;

            if (entryFileName == ".") {
                pathToFile = ".";
            } else if (entryFileName == "..") {
                // remove up to the final slash 
                size_t lastSlashIndex = directoryPath.rfind("/");
                if (lastSlashIndex != string::npos) {
                    pathToFile = directoryPath.substr(0, lastSlashIndex);
                } else {
                    pathToFile = "";
                }
            } else {
                pathToFile = directoryPath + "/" + entryFileName;
            }

            // Add a new (filename, path) pair
            containedFiles.push_back(make_pair(entryFileName, pathToFile));

            ent = readdir(dir);
        }

        closedir(dir);

        // Fill fileContents with HTML for these pairs (omitted)
        makeHTMLForDirectoryContents(fileContents, directoryPath, containedFiles);
    } else {
        /* could not open directory */
        fileContents = "<h1>Could not open directory.</h1>\r\n";
    }

}

/** 
 * Function: showFile
 * ------------------------------
 *  Populates fileContents with the file contents 
 */
static void showFile(string& fileContents, const string& fileName, bool& isHtml) {
    ifstream t(fileName);
    stringstream buffer;
    buffer << t.rdbuf();
    fileContents = buffer.str();
    isHtml = false;
}

/**
 * Function: getFileContents
 * ------------------------------
 *  If fileName is a file, populate fileContents with
 *  the file's data. If fileName is a directory,
 *  populate fileContents with a directory listing
 */
static void getFileContents(string& fileContents, const string& fileName, bool& isHTML) {
    struct stat s;
    if (stat(fileName.c_str(), &s) == 0) {
        if (s.st_mode & S_IFDIR) {
            //it's a directory
            listDir(fileContents, fileName, isHTML);
        }
        else if (s.st_mode & S_IFREG) {
            //it's a file
            showFile(fileContents, fileName, isHTML);
        } else {
            fileContents = "<h1>Requested name was not a file or directory.</h1>\r\n";
            isHTML = true;
            return;
        }
    } else {
        fileContents = "<h1>File \"" + fileName + "\" was not found.</h1>\r\n";
        isHTML = true;
        return;
    }
}

The sendResponse function is shown below:

static void sendResponse(iosockstream& ss, const string& payload, bool isHTML) {
    ss << "HTTP/1.1 200 OK\r\n";
    if (isHTML) {
        ss << "Content-Type: text/html; charset=UTF-8\r\n";
    } else {
        ss << "Content-Type: text/plain; charset=UTF-8\r\n";
    }
    ss << "Content-Length: " << payload.size() << "\r\n";
    ss << "\r\n";
    ss << payload << flush;
}

You can try out the server by running it and then requesting files using your web browser (if you aren't on the Stanford campus, you should use the Stanford VPN so you can access the myth machines with your browser). There are two directories (subdir and anotherdir) in samples with files in them to test going up and down directories.


Website design based on a design by Chris Piech
Icons by Piotr Kwiatkowski