Topic 4 Networking (1)

Leedehai
Friday, May 19, 2017
Monday, May 22, 2017

Networking: enlisting the services of multiple machines.
Conceptually, multiple machines form a local area network (LAN) via hubs, and multiple LANs form a larger LAN via bridges. Implementations of LAN include Ethernet, Wi-Fi, and ARCNET.
Multiple incompatible LANs form a larger interconnected network, called internet, via routers. The most widespread implementation of internet is the global IP internet, or referred to as the Internet.
World Wide Web, or simply the Web, is the content served up through the Internet.

4.1 Client-server, request-response

4.1.1 "Function calls"

In CS110 we have four types of "function calls", all of which can be framed in a client-server relationship: The client makes a request, and then the server makes a response.

We can see that networking is essentially no different from what we have seen in previous client-server relationships.

┌──────────┐ ┌──────────┐ │ │────request─────▶│ │ │ Client │ │ Server │ │ │◀───response─────│ │ └──────────┘ └──────────┘ main() foo() ..function call user process operating system ..system call proc. 1 on machine A proc. 2 on machine A ..pipe (x2) proc. on machine A proc. on machine B ..socket

Here, a server is a conceptual entity, which is not to be confused with a sever machine.

In the case of networking, the machine on which a (or multiple) network clients or servers is running is referred to as a host.

4.1.2 Web browsing in terminal

An experiment in your terminal, to see how request and response are made via network.

$ telnet www.bing.com 80 [type and return] Trying 204.79.197.200... Connected to a-0001.a-msedge.net. Escape character is '^]'. GET / HTTP/1.1 [type and return] ┐ request Host: www.bing.com [type and return] │ in [return again] ┘ HTTP/1.1 HTTP/1.1 200 OK ┐ Date: Mon, 29 May 2017 22:02:07 GMT │ Cache-Control: private, max-age=0 │ Content-Type: text/html; charset=ISO-8859-1 │ response Set-Cookie: ...blah blah blah │ in P3P: ...blah blah blah │ HTTP/1.1 ... │ <!DOCTYPE html... │ ...blah blah blah │ ...</html> ┘ Connection closed by foreign host. $

In the terminal session above, you made a HTTP request to the website, and a process on that website's machine response with a long text string, which could be interpreted by your web browser and rendered in a pictorial form.

4.1.3 CS110 time server

Actually, the response does not have to conform to HTTP protocol. Run an executable that fires up a server:

$ cd /usr/class/staff/examples/networking $ ./cs110_time_server Server listening on port 12345

In another terminal, we access that server. This time, we make no request, and the server automatically makes a response.

$ telnet myth15.stanford.edu 12345 Trying 171.64.15.56... Connected to myth15.stanford.edu Escape character is '^]'. Mon May 29 00:02:01 2017 <-- the server's response Connection closed by foreign host. $

4.2 Implementing a server

Linux's default network implementation complies with the Internet Protocol (IP).

4.2.1 Operative metaphor

4.2.2 Your first server: time server

Our time server accepts all incoming connection requests and quickly publishes the time, regardless of what the client's request is.

#include ... #include "socket++.h" using namespace std; static const int kServerStartFailure = 2; /* our cusrtom port number, should be larger than 1024 */ static const short kPortNumber = 12345; static void publishTime(int connectedSocket); int main() { /* create a listening socket, with a designated port number */ int listenSocket = createServerSocket(kPortNumber); if (listenSocket == kServerSocketFailure) { cerr << "Error: Could not start server on this port." << endl; cerr << "Aborting... " << endl; return kServerStartFailure; } else { cout << "Server listening on port " << kPortNumber << "." << endl; } while (true) { /* wait until a connection request arrives, then open an * connected socket descriptor, which is readable & writable */ int connectedSocket = accept(listenSocket, NULL, NULL); /* make a response to that connection */ publishTime(connectedSocket); } return 0; } void publishTime(int connectedSocket) { /* The first five lines here produce the full time string that * should be published. * These lines represent more generally the server-side computation * produce a response. It could have been a static HTML page, a * Bing search result, an RSS XML document, an image, or a video. */ time_t rawtime; time(&rawtime); struct tm *ptm = gmtime(&rawtime); /* Greenwich Mean Time Zone */ char timeString[128]; /* should be big enough */ strftime(timeString, sizeof(timeString), "%c\n", ptm); /* now, we write to the connected socket descriptor, * just like writing to a traditional FD */ size_t nBytesWritten = 0, nBytesToWrite = strlen(timeString); while (nBytesWritten < nBytesToWrite) { nBytesWritten += write(clientSocket, timeString + nBytesWritten, nBytesToWrite - nBytesWritten); } close(connectedSocket); /* close the connected socket FD */ /* alternatively, the writing part could be written in a more C++ way: * sockbuf sb(connectedSocket); * iosockstream ss(&sb); * ss << timeString << flush; * connectedSocket is automatically closed when the sockbuf * object is destroyed. */ }

4.2.3 Writing to SD in a more C++ way

Alternatively, the writing part below...

size_t nBytesWritten = 0, nBytesToWrite = strlen(timeString); while (nBytesWritten < nBytesToWrite) { nBytesWritten += write(clientSocket, timeString + nBytesWritten, nBytesToWrite - nBytesWritten); } close(connectedSocket);

...could be written in a more C++ way:

sockbuf sb(connectedSocket); iosockstream ss(&sb); ss << timeString << flush;

Note that the SD connectedSocket is automatically closed when the sockbuf object is destroyed. You should not close the SD manually if a sockbuf object owns it.

Also note that a iosockstream object is both readable and writable, as long as the SD, which is owned by the underlying sockbuf object, is readable and writable.

Classes sockbuf and iosockstream are not provided by C++ STL, but rather by a third-party open source library named "sock++", whose development seems to be terminated.

4.2.4 Listening SD and connected SD

4.2.5 Block & wait for connection requests : system call accept()

System call accept() accepts a connection request on a listening socket. Its prototype is

int accept(int listenfd, struct sockaddr *addr, socklen_t *addrlen);

A nice illustration:

INSIDE THE SERVER MACHINE: ┌───────────────────────┐ │ (no connection []listening SD │ ▼ time reqeust) │ (accept() blocks) │ │ │ a server process │ │ │ │ └───────────────────────┘ ┌───────────────────────┐ ╸╸╸connection╸╸▶[]listening SD │ request │ (accept() returns │ │ a connected SD) │ a server process │ │ │ []connected SD ◀┘ │ └───────────────────────┘ ┌───────────────────────┐ []listening SD │ │ │ │ │ a server process requests ─▶ │ (readable & writable) │ ◀════socket════▶[]connected SD ◀─▶ R/W │ ◀─ responses └───────────────────────┘

4.3 Server-side multithreading

Networking and multithreading are a natural match.

4.3.1 Re-implement the time server

#include ... #include "socket++.h" using namespace std; static const int kServerStartFailure = 2; /* our cusrtom port number, should be larger than 1024 */ static const short kPortNumber = 12345; static void publishTime(int connectedSocket); int main() { /* create a listening socket, with a designated port number */ int listenSocket = createServerSocket(kPortNumber); if listenSocket == kServerSocketFailure) { cerr << "Error: Could not start server on this port." << endl; cerr << "Aborting... " << endl; return kServerStartFailure; } else { cout << "Server listening on port " << kPortNumber << "." << endl; } /* ThreadPool: already done in our assignment 6 */ ThreadPool pool(4); /* spawn 4 child threads as workers */ while (true) { /* blocks & wait */ int connectedSocket = accept(listenSocket, NULL, NULL); /* schedule, let the Threadpool object handle multithreading */ pool.schedule([connectedSocket] { publishTime(clientSocket); }); } return 0; } void publishTime(int connectedSocket) { /* Similar to 4.2.2, but need to be thread-safe */ time_t rawtime; time(&rawtime); struct tm tm; gmtime_r(&rawtime, &tm); char timeString[128]; strftime(timeString, sizeof(timeString), "%c\n", ptm); sockbuf sb(connectedSocket); iosockstream ss(&sb); ss << timeString << endl << flush; } /* sockbuf's destructor will close connectedSocket */

Note that with multithreading, your code needs to be thread-safe, i.e. avoid race conditions and deadlocks. Reiterated, your code of reading and writing against a SD needs to be thread-safe, so it is not merely copy-and-paste the mono-thread version.

4.4 Implementing a client: time_client

The description:

4.4.1 The code

#include ... using namespace std; int main() { /* send the server a connection request, return an SD upon sucess */ int clientSocket = createClientSocket("myth7.stanford.edu", 12345); if (clientSocket == kClientSocketError) { cerr << "Time server could not be reached" << endl; cerr << "Aborting" << endl; return 1; } /* read from the SD */ sockbuf sb(clientSocket); iosockstream ss(&sb); string timeline; getline(ss, timeline); cout << timeline << endl; return 0; } /* clientSocket will be closed by sockbuf's destructor */
EOF