Principles of Computer Systems

Winter 2020

Stanford University

Computer Science Department

Lecturers: Chris Gregg and

                        Nick Troccoli

PDF of this presentation

Slide 1: Lecture 15: Networking, Clients

Slide 2: Lecture 15: API Servers, Threads, Processes

cgregg@myth61:$ ./scrabble-word-finder lexical
ace
// many lines omitted for brevity
lei
lex
lexica
lexical
li
lice
lie
lilac
xi
cgregg@myth61:$
cgregg@myth61:$ ./scrabble-word-finder network
en
// many lines omitted for brevity
wonk
wont
wore
work
worn
wort
wot
wren
wrote
cgregg@myth61:$

Slide 3: Lecture 15: API Servers, Threads, Processes

  • I want to implement an API service using HTTP to replicate what scrabble-wordfinder is capable of.
  • {
      time: 0.223399,
      cached: false,
      possibilities: [
        'ace',
        // several words omitted
        'lei',
        'lex',
        'lexica',
        'lexical',
        'li',
        'lice',
        'lie',
        'lilac',
        'xi'
      ]
    }
    {
      time: 0.223399,
      cached: false,
      possibilities: [
        'en',
        // several words omitted
        'wonk',
        'wont',
        'wore',
        'work',
        'worn',
        'wort',
        'wot',
        'wren',
        'wrote'
      ]
    }

    Slide 4: Lecture 15: API Servers, Threads, Processes

    struct subprocess_t {
        pid_t pid;
        int supplyfd;
        int ingestfd;
    };
    
    subprocess_t subprocess(char *argv[], 
            bool supplyChildInput, bool ingestChildOutput) throw (SubprocessException);

    Slide 5: Lecture 15: API Servers, Threads, Processes

  • Here is the core of the main function implementing our server:
  • int main(int argc, char *argv[]) {
        unsigned short port = extractPort(argv[1]);
        int server = createServerSocket(port);
        cout << "Server listening on port " << port << "." << endl;
        ThreadPool pool(16);
        map<string, vector<string>> cache;
        mutex cacheLock;
        while (true) {
            struct sockaddr_in address;
            // used to surface IP address of client
            socklen_t size = sizeof(address); // also used to surface client IP address
            bzero(&address, size);
            int client = accept(server, (struct sockaddr *) &address, &size);
            char str[INET_ADDRSTRLEN];
            cout << "Received a connection request from "
                << inet_ntop(AF_INET, &address.sin_addr, str, INET_ADDRSTRLEN) << "." << endl;
            pool.schedule([client, &cache, &cacheLock] {
                    publishScrabbleWords(client, cache, cacheLock);
                    });
        }
        return 0;
    }

    Slide 6: Lecture 15: API Servers, Threads, Processes

    Slide 7: Lecture 15: API Servers, Threads, Processes

    static void publishScrabbleWords(int client, map<string, vector<string>>& cache, 
                                     mutex& cacheLock) {
        sockbuf sb(client);
        iosockstream ss(&sb);
        string letters = getLetters(ss);
        sort(letters.begin(), letters.end());
        skipHeaders(ss);
        struct timeval start;
        gettimeofday(&start, NULL); // start the clock
        cacheLock.lock();
        auto found = cache.find(letters);
        cacheLock.unlock(); // release lock immediately, iterator won't be invalidated by competing find calls
        bool cached = found != cache.end();
        vector<string> formableWords;
        if (cached) {
            formableWords = found->second;
        } else {
            const char *command[] = {"./scrabble-word-finder", letters.c_str(), NULL};
            subprocess_t sp = subprocess(const_cast<char **>(command), false, true);
            pullFormableWords(formableWords, sp.ingestfd);
            waitpid(sp.pid, NULL, 0);
            lock_guard<mutex> lg(cacheLock);
            cache[letters] = formableWords;
        }
        struct timeval end, duration;
        gettimeofday(&end, NULL); // stop the clock, server-computation of formableWords is complete
        timersub(&end, &start, &duration);
        double time = duration.tv_sec + duration.tv_usec/1000000.0;
        ostringstream payload;
        constructPayload(formableWords, cached, time, payload);
        sendResponse(ss, payload.str());
    }

    Slide 8: Lecture 15: API Servers, Threads, Processes

    static void pullFormableWords(vector<string>& formableWords, int ingestfd) {
        stdio_filebuf<char> inbuf(ingestfd, ios::in);
        istream is(&inbuf);
        while (true) {
            string word;
            getline(is, word);
            if (is.fail()) break;
            formableWords.push_back(word);
        }
    }
    
    static void sendResponse(iosockstream& ss, const string& payload) {
        ss << "HTTP/1.1 200 OK\r\n";
        ss << "Content-Type: text/javascript; charset=UTF-8\r\n";
        ss << "Content-Length: " << payload.size() << "\r\n";
        ss << "\r\n";
        ss << payload << flush;
    }

    Slide 9: Lecture 15: API Servers, Threads, Processes

    static string getLetters(iosockstream& ss) {
        string method, path, protocol;
        ss >> method >> path >> protocol;
        string rest;
        getline(ss, rest);
        size_t pos = path.rfind("/");
        return pos == string::npos ? path : path.substr(pos + 1);
    }
    static void constructPayload(const vector<string>& formableWords, bool cached, 
                                 double time, ostringstream& payload) {
        payload << "{" << endl;
        payload << " time: " << time << "," << endl;
        payload << " cached: " << boolalpha << cached << "," << endl;
        payload << " possibilities: " << constructJSONArray(formableWords, 2) << endl;
        payload << "}" << endl;
    }
  • Our scrabble-word-finder-server provided a single API call that resembles the types of API calls afforded by Google, Twitter, or Facebook to access search, tweet, or friend-graph data.
  • Slide 10: Lecture 15: Network System Calls, Library Functions

    struct hostent *gethostbyname(const char *name);
    struct hostent *gethostbyaddr(const char *addr, int len, int type);

    Slide 11: Lecture 15: Network System Calls, Library Functions

    struct in_addr {
        unsigned int s_addr // four bytes, stored in network byte order (big endian)
    };
    struct hostent {
        char *h_name;
        // official name of host
        char **h_aliases;
        // NULL-terminated list of aliases
        int h_addrtype;
        // host address type (typically AF_INET for IPv4)
        int h_length;
        // address length (typically 4, or sizeof(struct in_addr) for IPv4)
        char **h_addr_list; // NULL-terminated list of IP addresses
    }; // h_addr_list is really a struct in_addr ** when hostent contains IPv4 addresses

    Slide 12: Lecture 15: Network System Calls, Library Functions

    static void publishIPAddressInfo(const string& host) {
        struct hostent *he = gethostbyname(host.c_str());
        if (he == NULL) { // NULL return value means resolution attempt failed
            cout << host << " could not be resolved to an address. Did you mistype it?" << endl;
            return;
        }
    
        cout << "Official name is \"" << he->h_name << "\"" << endl;
        cout << "IP Addresses: " << endl;
        struct in_addr **addressList = (struct in_addr **) he->h_addr_list;
        while (*addressList != NULL) {
            char str[INET_ADDRSTRLEN];
            cout << "+ " << inet_ntop(AF_INET, *addressList, str, INET_ADDRSTRLEN) << endl;
            addressList++;
        }
    }

    Slide 13: Lecture 15: Network System Calls, Library Functions

    static void publishIPAddressInfo(const string& host) {
        struct hostent *he = gethostbyname(host.c_str());
        if (he == NULL) { // NULL return value means resolution attempt failed
            cout << host << " could not be resolved to an address. Did you mistype it?" << endl;
            return;
        }
    
        cout << "Official name is \"" << he->h_name << "\"" << endl;
        cout << "IP Addresses: " << endl;
        struct in_addr **addressList = (struct in_addr **) he->h_addr_list;
        while (*addressList != NULL) {
            char str[INET_ADDRSTRLEN];
            cout << "+ " << inet_ntop(AF_INET, *addressList, str, INET_ADDRSTRLEN) << endl;
            addressList++;
        }
    }

    Slide 14: Lecture 15: Network System Calls, Library Functions

    Hostname Resolution: IPv4

    myth61$ ./resolve-hostname
    Welcome to the IP address resolver!
    Enter a host name: www.google.com
    Official name is "www.google.com"
    IP Addresses:
    + 216.58.192.4
    Enter a host name: www.coinbase.com
    Official name is "www.coinbase.com"
    IP Addresses:
    + 104.16.9.251
    + 104.16.8.251
    Enter a host name: www.yale.edu
    Official name is "www.yale.edu.cdn.cloudflare.net"
    IP Addresses:
    + 104.16.140.133
    + 104.16.141.133
    Enter a host name: www.okcupid.com
    Official name is "www.okcupid.com"
    IP Addresses:
    + 198.41.209.132
    + 198.41.209.133
    + 198.41.208.132
    + 198.41.209.131
    + 198.41.208.133
    Enter a host name: www.wikipedia.org
    Official name is "www.wikipedia.org"
    IP Addresses:
    + 198.35.26.96
    Enter a host name:
    All done!
    myth61$

    Slide 15: Lecture 15: Network System Calls, Library Functions

    Hostname Resolution: IPv6

    A more generic version of gethostbyname—inventively named gethostbyname2—can be used to extract IPv6 address information about a hostname.

    struct hostent *gethostbyname2(const char *name, int af);

    Slide 16: Lecture 15: Network System Calls, Library Functions

    struct in6_addr {
        u_int8_t s6_addr[16]; // 16 bytes (128 bits), stored in network byte order
    };

    Slide 17: Lecture 15: Network System Calls, Library Functions

    static void publishIPv6AddressInfo(const string& host) {
        struct hostent *he = gethostbyname2(host.c_str(), AF_INET6);
        if (he == NULL) { // NULL return value means resolution attempt failed
            cout << host << " could not be resolved to an address. Did you mistype it?" << endl;
            return;
        }
    
        cout << "Official name is \"" << he->h_name << "\"" << endl;
        cout << "IPv6 Addresses: " << endl;
        struct in6_addr **addressList = (struct in6_addr **) he->h_addr_list;
        while (*addressList != NULL) {
            char str[INET6_ADDRSTRLEN];
            cout << "+ " << inet_ntop(AF_INET6, *addressList, str, INET6_ADDRSTRLEN) << endl;
            addressList++;
        }
    }

    Slide 18: Lecture 15: Network System Calls, Library Functions

    myth61$ ./resolve-hostname6
    Welcome to the IPv6 address resolver!
    Enter a host name: www.facebook.com
    Official name is "star-mini.c10r.facebook.com"
    IPv6 Addresses:
    + 2a03:2880:f131:83:face:b00c:0:25de
    Enter a host name: www.microsoft.com
    Official name is "e13678.dspb.akamaiedge.net"
    IPv6 Addresses:
    + 2600:1406:1a:386::356e
    + 2600:1406:1a:397::356e
    Enter a host name: www.google.com
    Official name is "www.google.com"
    IPv6 Addresses:
    + 2607:f8b0:4005:801::2004
    Enter a host name: www.berkeley.edu
    Official name is "www-production-1113102805.us-west-2.elb.amazonaws.com"
    IPv6 Addresses:
    + 2600:1f14:436:7800:4598:b474:29c4:6bc0
    + 2600:1f14:436:7801:15f8:d879:9a03:eec0
    Enter a host name: www.harvard.edu
    www.harvard.edu could not be resolved to an address. Did you mistype it?
    Enter a host name:
    All done!
    myth61$

    Slide 19: Lecture 15: Network System Calls, Library Functions

    struct sockaddr { // generic socket
        unsigned short sa_family; // protocol family for socket
        char sa_data[14];
        // address data (and defines full size to be 16 bytes)
    };
    struct sockaddr_in { // IPv4 socket address record
        unsigned short sin_family;
        unsigned short sin_port;
        struct in_addr sin_addr;
        unsigned char sin_zero[8];
    };
    struct sockaddr_in6 { // IPv6 socket address record
        unsigned short sin6_family;
        unsigned short sin6_port;
        unsigned int sin6_flowinfo;;
        struct in6_addr sin6_addr;
        unsigned int sin6_scope_id;
    };

    Slide 20: Lecture 15: Network System Calls, Library Functions

    struct sockaddr { // generic socket
        unsigned short sa_family; // protocol family for socket
        char sa_data[14];
        // address data (and defines full size to be 16 bytes)
    };
    struct sockaddr_in { // IPv4 socket address record
        unsigned short sin_family;
        unsigned short sin_port;
        struct in_addr sin_addr;
        unsigned char sin_zero[8];
    };
    struct sockaddr_in6 { // IPv6 socket address record
        unsigned short sin6_family;
        unsigned short sin6_port;
        unsigned int sin6_flowinfo;;
        struct in6_addr sin6_addr;
        unsigned int sin6_scope_id;
    };

    Slide 21: Lecture 15: Network System Calls, Library Functions

    struct sockaddr { // generic socket
        unsigned short sa_family; // protocol family for socket
        char sa_data[14];
        // address data (and defines full size to be 16 bytes)
    };
    struct sockaddr_in { // IPv4 socket address record
        unsigned short sin_family;
        unsigned short sin_port;
        struct in_addr sin_addr;
        unsigned char sin_zero[8];
    };
    struct sockaddr_in6 { // IPv6 socket address record
        unsigned short sin6_family;
        unsigned short sin6_port;
        unsigned int sin6_flowinfo;;
        struct in6_addr sin6_addr;
        unsigned int sin6_scope_id;
    };

    Slide 22: Lecture 15: Network System Calls, Library Functions

    Slide 23: Lecture 15: Network System Calls, Library Functions

    int createClientSocket(const string& host, unsigned short port) {
        struct hostent *he = gethostbyname(host.c_str());
        if (he == NULL) return -1;
        int s = socket(AF_INET, SOCK_STREAM, 0);
        if (s < 0) return -1;
        struct sockaddr_in address;
        memset(&address, 0, sizeof(address));
        address.sin_family = AF_INET;
        address.sin_port = htons(port);
    
        // h_addr is #define for h_addr_list[0]
        address.sin_addr = *((struct in_addr *)he->h_addr); 
        if (connect(s, (struct sockaddr *) &address, sizeof(address)) == 0) return s;
    
        close(s);
        return -1;
    }

    Slide 24: Lecture 15: Network System Calls, Library Functions

    Here are a few details about my implementation of createClientSocket worth calling out:

    Slide 25: Lecture 15: Network System Calls, Library Functions

    Slide 26: Lecture 15: Network System Calls, Library Functions

    int createServerSocket(unsigned short port, int backlog) {
        int s = socket(AF_INET, SOCK_STREAM, 0);
        if (s < 0) return -1;
        struct sockaddr_in address;
        memset(&address, 0, sizeof(address));
        address.sin_family = AF_INET;
        address.sin_addr.s_addr = htonl(INADDR_ANY);
        address.sin_port = htons(port);
        if (bind(s, (struct sockaddr *)&address, sizeof(address)) == 0 &&
                listen(s, backlog) == 0) return s;
    
        close(s);
        return -1;
    }

    Slide 27: Lecture 15: Network System Calls, Library Functions