Principles of Computer Systems
Fall 2019
Stanford University
Computer Science Department
Instructors: Chris Gregg and Philip Levis
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
(you'll be implementing one for Assignment 6) 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.