Principles of Computer Systems
Winter 2020
Stanford University
Computer Science Department
Instructors: Chris Gregg and
Nick Troccoli
Introduction to Networking
API Servers
Network System Calls
static void writeToSocket(int socketDescriptor) {
sockbuf sb(socketDescriptor);
iosockstream ss(&sb);
ss << [CONTENT HERE] << endl;
} // sockbuf destructor closes client"client" and "server" are the two main program "roles" in networking.
int main(int argc, char *argv[]) {
int clientSocket = createClientSocket("myserver.com", 12345);
sockbuf sb(clientSocket);
iosockstream ss(&sb);
string responseData;
getline(ss, responseData);
cout << responseData << endl;
return 0;
}
But what is createClientSocket really doing?
int createClientSocket(const string& host, unsigned short port);
int createClientSocket(const string& host, unsigned short port);
int createClientSocket(const string& host, unsigned short port);
sizeof(struct in_addr) and the third the AF_INET constant.
struct hostent with host's info (or NULL if error)
Idea: let's use gethostbyname() to look up this host and see if it's valid (non-NULL).
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const char *addr, int len, int type);
// represents an IP Address
struct in_addr {
unsigned int s_addr // stored in network byte order (big endian)
};
// represents a host's info
struct hostent {
// official name of host
char *h_name;
// NULL-terminated list of aliases
char **h_aliases;
// host address type (typically AF_INET for IPv4)
int h_addrtype;
// address length (typically 4, or sizeof(struct in_addr) for IPv4)
int h_length;
// NULL-terminated list of IP addresses
// This is really a struct in_addr ** when hostent contains IPv4 addresses
char **h_addr_list;
};
gethostbyname()Note: h_addr_list is typed to be a char * array, but for IPv4 records it's really struct in_addr **, so we cast it to that in our code.
Why the confusion?
int createClientSocket(const string& host, unsigned short port) {
struct hostent *he = gethostbyname(host.c_str());
if (he == NULL) return -1;
...
int socket(int domain, int type, int protocol);
int createClientSocket(const string& host, unsigned short port) {
...
int s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) return -1;
...
int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen);
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)
};
The sockaddr_in is used to model IPv4 address/port pairs.
sin_family field should always be initialized to be AF_INET, which is a constant used to be clear that IPv4 addresses are being used. If it feels redundant that a record dedicated to IPv4 needs to store a constant saying everything is IPv4, then stay tuned.sin_port field stores a port number in network byte (i.e. big endian) order.sin_addr field stores an IPv4 address as a packed, big endian int, as you saw with gethostbynameand the struct hostent.sin_zero field is generally ignored (though it's often set to store all zeroes). It exists primarily to pad the record up to 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;
};
sockaddr_in6 is used to model IPv6 address/port pairs.
sin6_family field should always be set to AF_INET6. As with the sin_family field, sin6_family field occupies the first two bytes of surrounding record.sin6_port field holds a two-byte, network-byte-ordered port number, just like sin_port does.struct in6_addr is also wedged in there to manage a 128-bit IPv6 address.sin6_flowinfo and sin6_scope_id are beyond the scope of what we need, so we'll ignore them.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;
};
struct sockaddr is the best C can do to emulate an abstract base class.
struct sockaddr, but many system calls will accept parameters of type struct sockaddr *.struct sockaddr_in or a struct sockaddr_in6. The system call relies on the value within the first two bytes—the sa_family field—to determine what the true record type is.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;
};
int createClientSocket(const string& host, unsigned short port) {
...
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;
...
createClientSocket: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;
}
address is declared to be of type struct sockaddr_in, since that's the data type specifically set up to model IPv4 addresses. Had we been dealing with IPv6 addresses, we'd have declared a struct sockaddr_in6 instead.AF_INET within the sin_family field, since those two bytes are examined by system calls to determine the type of socket address structure.sin_port field is, not surprisingly, designed to hold the port of interest. htons—that's an abbreviation for host-to-network-short—is there to ensure the port is stored in network byte order (which is big endian order). On big endian machines, htons is implemented to return the provided short without modification. On little endian machines (like the myths), htons returns a figure constructed by exchanging the two bytes of the incoming short. In addition to htons, Linux also provided htonl for four-byte longs, and it also provides ntohs and ntohl to restore host byte order from network byte ordered figures.connect associates the descriptor s with the host/IP address pair modeled by the supplied struct sockaddr_in *. The second argument is downcast to a struct sockaddr *, since connect needs accept a pointer to any type within the entire struct sockaddr family, not just struct sockaddr_ins.
int createServerSocket(unsigned short port, int backlog = kDefaultBacklog);
createServerSocket: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;
}
createServerSocket worth calling out:
socket is precisely the same here as it was in createClientSocket. It allocates a descriptor and configures it to be a socket descriptor within the AF_INET namespace.struct sockaddr_in here is configured in much the same way it was in createClientSocket, except that the sin_addr.s_addr field should be set to a local IP address, not a remote one. The constant INADDR_ANY is used to state that address should represent all local addresses.bind call simply assigns the set of local IP addresses represented by address to the provided socket s. Because we embedded INADDR_ANY within address, bind associates the supplied socket with all local IP addresses. That means once createServerSocket has done its job, clients can connect to any of the machine's IP addresses via the specified port.listen call is what converts the socket to be one that's willing to accept connections via accept. The second argument is a queue size limit, which states how many pending connection requests can accumulate and wait their turn to be accepted. If the number of outstanding requests is at the limit, additional requests are simply refused.ThreadPool to your program, but first, write a sequential version.GET http://www.cornell.edu/research/ HTTP/1.1
GET /research/ HTTP/1.1
HTTPRequest class, although you will have to update the operator<< function at a later stage.
Next Time: Overview of MapReduce