CS110: Principles of Computer Systems
Winter 2021-2022
Stanford University
Instructors: Nick Troccoli and Jerry Cain
Introduction to Networking
Servers / HTTP
Clients, Servers and APIs
Networking System Calls
assign6: implement an HTTP Proxy that sits between a client device and a web server to monitor, block or modify web traffic.
Your computer performs DNS lookups frequently on your behalf - e.g. when you want to visit a website in your browser.
For fun, we can view DNS servers using the dig command:
where are the edu nameservers? "dig -t NS +noall +answer edu"
the stanford.edu nameservers? "dig -t NS +noall +answer stanford.edu"
where is web.stanford.edu? "dig -t A +noall +answer web.stanford.edu"
// Opens a connection to a server (returns kClientSocketError on error)
int createClientSocket(const string& host, unsigned short port);
New helper function to connect to a server:
(Later on, we will learn how to implement createClientSocket!)
I am running a server on myth64.stanford.edu, port 12345 that can tell you the current time. Whenever a client connects to it, the server sends back the time as text.
int main(int argc, char *argv[]) {
// Open a connection to the server
int socketDescriptor = createClientSocket("myth64.stanford.edu", 12345);
// Read in the data from the server (assumed to be at most 1024 byte string)
char buf[1024];
size_t bytes_read = 0;
while (true) {
size_t read_this_time = read(socketDescriptor, buf + bytes_read, sizeof(buf) - bytes_read);
if (read_this_time == 0) break;
bytes_read += read_this_time;
}
buf[bytes_read] = '\0';
close(socketDescriptor);
// print the data from the server
cout << buf << flush;
return 0;
}
Client sockets work similarly to regular file descriptors - we open one, read from/write to it, and close it.
int main(int argc, char *argv[]) {
// Open a connection to the server
int socketDescriptor = createClientSocket("myth64.stanford.edu", 12345);
// Read in the data from the server (assumed to be at most 1024 byte string)
char buf[1024];
size_t bytes_read = 0;
while (true) {
size_t read_this_time = read(socketDescriptor, buf + bytes_read, sizeof(buf) - bytes_read);
if (read_this_time == 0) break;
bytes_read += read_this_time;
}
buf[bytes_read] = '\0';
close(socketDescriptor);
// print the data from the server
cout << buf << flush;
return 0;
}
Using read/write is cumbersome with socket descriptors. The socket++ library provides a type iosockstream that let us wrap a socket descriptor in a stream (so that we can read/write like we do with cout):
static string readLineFromSocket(int socketDescriptor) {
sockbuf socketBuffer(socketDescriptor);
iosockstream socketStream(&socketBuffer);
string timeline;
getline(socketStream, timeline);
return timeline;
} // sockbuf destructor closes client
Here is a version of the same client program using sockbuf and iosockstream instead of read:
int main(int argc, char *argv[]) {
// Open a connection to the server
int socketDescriptor = createClientSocket("myth64.stanford.edu", 12345);
// Read in the data from the server (sockbuf descructor closes descriptor)
sockbuf socketBuffer(socketDescriptor);
iosockstream socketStream(&socketBuffer);
string timeline;
getline(socketStream, timeline);
// Print the data from the server
cout << timeline << endl;
return 0;
}
Next time: more about servers, data formats and protocols