Principles of Computer Systems
Winter 2021
Stanford University
Computer Science Department
Instructors: Chris Gregg and
Nick Troccoli
netstat
command:cgregg@myth59: $ netstat -plnt
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:587 0.0.0.0:* LISTEN -
tcp 0 0 127.0.1.1:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 ::1:631 :::* LISTEN -
cgregg@myth59: $ netstat -plnt
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:587 0.0.0.0:* LISTEN -
tcp 0 0 127.0.1.1:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 ::1:631 :::* LISTEN -
int main(int argc, char *argv[]) {
int server = createServerSocket(12345);
while (true) {
int client = accept(server, NULL, NULL); // the two NULLs could instead be used to
// surface the IP address of the client
publishTime(client);
}
return 0;
}
accept
(found in sys/socket.h
) returns a descriptor that can be written to and read from. Whatever's written is sent to the client, and whatever the client sends back is readable here.
publishTime
function is straightforward:static void publishTime(int client) {
time_t rawtime;
time(&rawtime);
struct tm *ptm = gmtime(&rawtime);
char timestr[128]; // more than big enough
/* size_t len = */ strftime(timestr, sizeof(timestr), "%c\n", ptm);
size_t numBytesWritten = 0, numBytesToWrite = strlen(timestr);
while (numBytesWritten < numBytesToWrite) {
numBytesWritten += write(client,
timestr + numBytesWritten,
numBytesToWrite - numBytesWritten);
}
close(client);
}
while
loop for writing bytes is a bit more important now that we are networking: we are more likely to need to write multiple times on a network.
write
's return value could very well be less than what was supplied by the third argument.FILE *
) or C++ streams (e.g. the iostream
class hierarchy) to layer over data buffers and manage the while
loop around exposed write
calls for us.socket++
that provides exactly this.
socket++
provides iostream subclasses that respond to operator<<
, operator>>
, getline
, endl
, and so forth, just like cin
, cout
, and file streams do.publishTime
.publishTime
:static void publishTime(int client) {
time_t rawtime;
time(&rawtime);
struct tm *ptm = gmtime(&rawtime);
char timestr[128]; // more than big enough
/* size_t len = */ strftime(timestr, sizeof(timestr), "%c", ptm);
sockbuf sb(client);
iosockstream ss(&sb);
ss << timestr << endl;
} // sockbuf destructor closes client
iosockstream
that itself layers over the client socket.sockbuf
class takes ownership of the socket and closes it when its destructor is called.aggregate
) where multithreading can significantly improve the performance of networked applications.accept
returns a socket descriptor, spawn a child thread—or reuse an existing one within a ThreadPool
—to get any intense, time consuming computation off of the main thread. The child thread can make use of a second processor or a second core, and the main thread can quickly move on to its next accept
call.ThreadPool
to get the computation off the main thread.int main(int argc, char *argv[]) {
int server = createServerSocket(12345);
ThreadPool pool(4);
while (true) {
int client = accept(server, NULL, NULL); // the two NULLs could instead be used
// to surface the IP address of the client
pool.schedule([client] { publishTime(client); });
}
return 0;
}
publishTime
needs to change just a little if it's to be thread safe.gmtime
.gmtime
returns a pointer to a single, statically allocated global that's used by all calls.mutex
to ensure that a thread can call gmtime
without competition and subsequently extract the data from the global into local copy.gmtime_r
. This second, reentrant version just requires that space for a dedicated return value be passed in.gmtime
_r itself is, since it doesn't depend on any shared resources.publishTime
is presented on the next slide.publishTime
:static void publishTime(int client) {
time_t rawtime;
time(&rawtime);
struct tm tm;
gmtime_r(&rawtime, &tm);
char timestr[128]; // more than big enough
/* size_t len = */ strftime(timestr, sizeof(timestr), "%c", &tm);
sockbuf sb(client); // destructor closes socket
iosockstream ss(&sb);
ss << timestr << endl;
}
createClientSocket
. For now, view it as a built-in that sets up a bidirectional pipe between a client and a server running on the specified host (e.g. myth64
) and bound to the specified port number (e.g. 12345).int main(int argc, char *argv[]) {
int clientSocket = createClientSocket("myth64.stanford.edu", 12345);
assert(clientSocket >= 0);
sockbuf sb(clientSocket);
iosockstream ss(&sb);
string timeline;
getline(ss, timeline);
cout << timeline << endl;
return 0;
}
wget
is a command line utility that, given its URL, downloads a single document (HTML document, image, video, etc.) and saves a copy of it to the current working directory.wget
's most basic functionality.main
and parseUrl
functions.parseUrl
dissects the supplied URL to surface the host and pathname components.static const string kProtocolPrefix = "http://";
static const string kDefaultPath = "/";
static pair<string, string> parseURL(string url) {
if (startsWith(url, kProtocolPrefix))
url = url.substr(kProtocolPrefix.size());
size_t found = url.find('/');
if (found == string::npos)
return make_pair(url, kDefaultPath);
string host = url.substr(0, found);
string path = url.substr(found);
return make_pair(host, path);
}
int main(int argc, char *argv[]) {
pullContent(parseURL(argv[1]));
return 0;
}
wget
(continued)
pullContent
, of course, needs to manage everything, including the networking.static const unsigned short kDefaultHTTPPort = 80;
static void pullContent(const pair<string, string>& components) {
int client = createClientSocket(components.first, kDefaultHTTPPort);
if (client == kClientSocketError) {
cerr << "Could not connect to host named \"" << components.first << "\"." << endl;
return;
}
sockbuf sb(client);
iosockstream ss(&sb);
issueRequest(ss, components.first, components.second);
skipHeader(ss);
savePayload(ss, getFileName(components.second));
}
createClientSocket
function for our time-client
. This time, we're connecting to real but arbitrary web servers that speak HTTP.issueRequest
, skipHeader
, and savePayload
subdivide the client-server conversation into manageable chunks.Emulation of wget
(continued)
issueRequest
, which generates the smallest legitimate HTTP request possible and sends it over to the server.
static void issueRequest(iosockstream& ss, const string& host, const string& path) {
ss << "GET " << path << " HTTP/1.0\r\n";
ss << "Host: " << host << "\r\n";
ss << "\r\n";
ss.flush();
}
'\r'
following by '\n'
.flush
is necessary to ensure all character data is pressed over the wire and consumable at the other end.flush
, the client transitions from supply to ingest mode. Remember, the iosockstream
is read/write, because the socket descriptor backing it is bidirectional.skipHeader
reads through and discards all of the HTTP response header lines until it encounters either a blank line or one that contains nothing other than a '\r'.
The blank line is, indeed, supposed to be "\r\n"
, but some servers—often hand-rolled ones—are sloppy, so we treat the '\r'
as optional. Recall that getline chews up the '\n'
, but it won't chew up the '\r'
.static void skipHeader(iosockstream& ss) {
string line;
do {
getline(ss, line);
} while (!line.empty() && line != "\r");
}
wget
we're imitating—would ingest all of the lines of the response header into a data structure and allow it to influence how it treats payload.static string getFileName(const string& path) {
if (path.empty() || path[path.size() - 1] == '/') return "index.html";
size_t found = path.rfind('/');
return path.substr(found + 1);
}
static void savePayload(iosockstream& ss, const string& filename) {
ofstream output(filename, ios::binary); // don't assume it's text
size_t totalBytes = 0;
while (!ss.fail()) {
char buffer[2014] = {'\0'};
ss.read(buffer, sizeof(buffer));
totalBytes += ss.gcount();
output.write(buffer, ss.gcount());
}
cout << "Total number of bytes fetched: " << totalBytes << endl;
}
EOF
, and we write everything we read.