Post

Lecture 22: Network Programming: Part II

Computer Systems: A Programmer's Perspective (CS:APP)

Sockets Interface


Sockets Helper: open_clientfd()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int open_clientfd(char *hostname, char *port) {
    struct addrinfo hints, *listp, *p;
    int clientfd, errcode;

    /* Get a list of potential server addresses */
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;  /* Open a connection */
    hints.ai_flags = AI_NUMERICSERV;  /* Numeric port number */
    hints.ai_flags |= AI_ADDRCONFIG;  /* Recommended for connections */
    if ((errcode = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo() failed (%s:%s): %s\n",
                hostname, port, gai_strerror(errcode));
        return -2;
    }
    /* Walk the list for one that we can successfully connect to */
    for (p = listp; p; p = p->ai_next) {
        if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
            continue;
        if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1)
            break;
        if (close(clientfd) < 0) {
            fprintf(stderr, "open_clientfd(): close() failed: %s\n", strerror(errno));
            return -1;
        }
    }
    Freeaddrinfo(listp);
    if (!p)
        return -1;
    return clientfd;
}

Sockets Helper: open_listenfd()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int open_listenfd(char *port) {
    struct addrinfo hints, *listp, *p;
    int listenfd, errcode, optval = 1;

    /* Get a list of potential server addresses */
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;                /* Accept connections */
    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;    /* ... on any IP address */
    hints.ai_flags |= AI_NUMERICSERV;               /* ... using port number */
    if ((errcode = getaddrinfo(NULL, port, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo failed (port %s): %s\n",
                port, gai_strerror(errcode));
        return -2;
    }
    /* Walk the list for one that we can bind to */
    for (p = listp; p; p = p->ai_next) {
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
            continue;
        /* Eliminates "Address already in use" error from bind */
        Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
                   (const void *)&optval , sizeof(int));
        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
            break;
        if (close(listenfd) < 0) {
            fprintf(stderr, "open_listenfd(): close() failed: %s\n", strerror(errno));
            return -1;
        }
    }
    Freeaddrinfo(listp);
    if (!p)
        return -1;
    /* Make it a listening socket ready to accept connection requests */
    if (listen(listenfd, LISTENQ) < 0) {
        Close(listenfd);
        return -1;
    }
    return listenfd;
}

Echo Client: Main Routine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main(int argc, char *argv[]) {
    int clientfd;
    char *host, *port, buf[MAXLINE];
    rio_t rio;

    if (argc != 3) {
        fprintf(stderr, "Usage: %s <host> <port>\n", argv[0]);
        return 2;
    }
    host = argv[1];
    port = argv[2];
    clientfd = Open_clientfd(host, port);
    Rio_readinitb(&rio, clientfd);
    while (Fgets(buf, MAXLINE, stdin) != NULL) {
        Rio_writen(clientfd, buf, strlen(buf));
        Rio_readlineb(&rio, buf, MAXLINE);
        Fputs(buf, stdout);
    }
    Close(clientfd);
    return 0;
}

Iterative Echo Server: Main Routine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(int argc, char *argv[]) {
    int listenfd, connfd;
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;  /* Enough space for any address */
    char client_hostname[MAXLINE], client_port[MAXLINE];

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <port>\n", argv[0]);
        return 2;
    }
    listenfd = Open_listenfd(argv[1]);
    while (1) {
        clientlen = sizeof(struct sockaddr_storage);
        connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen);
        Getnameinfo((struct sockaddr *)&clientaddr, clientlen, client_hostname,
                    MAXLINE, client_port, MAXLINE, 0);
        printf("Connected to (%s, %s)\n", client_hostname, client_port);
        echo(connfd);
        Close(connfd);
    }
    return 0;
}

Echo Server: echo()

1
2
3
4
5
6
7
8
9
10
11
void echo(int connfd) {
    size_t n;
    char buf[MAXLINE];
    rio_t rio;

    Rio_readinitb(&rio, connfd);
    while ((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
        printf("Server received %d bytes\n", (int)n);
        Rio_writen(connfd, buf, n);
    }
}

Testing Servers Using telnet

telnet은 원격 호스트와 통신할 수 있는 유틸리티 프로그램이다. 보안에 취약하기 때문에 암호화된 프로토콜인 SSH로 대체되었지만, 네트워크 서비스를 테스트하는 용도로 사용할 수 있다.

1
2
$ ./echoserveri 15213

1
2
3
4
5
$ telnet localhost 15213
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

1
2
3
$ ./echoserveri 15213
Connected to (localhost, 41270)

1
2
3
4
5
6
7
$ telnet localhost 15213
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
123456789
123456789

1
2
3
4
$ ./echoserveri 15213
Connected to (localhost, 41270)
Server received 11 bytes


Web Servers


Web Server Basics

월드 와이드 웹(World Wide Web, WWW)은 클라이언트-서버 모델의 대표적인 예시이다.

flowchart LR
    client(("Web client<br>(browser)"))
    server(("Web server"))

    client -- "HTTP request" --> server
    server -- "HTTP response" --> client


HTTP-1.1 vs. HTTP-2 vs. HTTP-3 Protocol Stack Protocol stack1

Web Content

웹 서버는 클라이언트에게 콘텐츠(Content)를 반환하는데, 텍스트나 이미지 등을 MIME 타입2으로 인코딩하여 전송한다.

  • 정적 콘텐츠 (Static content): 서버에 저장되어 있는 파일(HTML, 이미지 등)로 구성된다.
  • 동적 콘텐츠 (Dynamic content): 클라이언트의 요청에 따라 그때그때 생성된다.

URLs

URL(Uniform Resource Locator)은 리소스의 위치를 나타내는 표준 주소 체계이다.

mdn-url-all Anatomy of a URL3

HTTP Messages

클라이언트(웹 브라우저)는 서버에 HTTP 요청을 보내고, 서버는 그에 대한 응답(웹 페이지)을 반환한다.

자세한 내용은 여기4 참고

Example HTTP Transaction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$ telnet www.cs.cmu.edu 80
Trying 128.2.42.95...
Connected to SCS-WEB-LB.ANDREW.cmu.edu.
Escape character is '^]'.
GET /~bryant/test.html HTTP/1.1
Host: www.cs.cmu.edu

HTTP/1.1 200 OK
Date: Wed, 12 Jun 2024 02:59:22 GMT
Server: Apache/2.4.18 (Ubuntu)
Set-Cookie: SHIBLOCATION=tilde; path=/; domain=.cs.cmu.edu
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Length: 479
Content-Type: text/html
Set-Cookie: BALANCEID=balancer.web38.srv.cs.cmu.edu; path=/;
Set-Cookie: BIGipServer~SCS~cs-userdir-pool-80=550109824.20480.0000; path=/; Httponly

<html>
<head><title>Some Tests</title></head>

<body>
<h1>Some Tests</h1>
<ul>
<li><a href="index.html">Bryant's Home</a>
<li><a href="http://csapp.cs.cmu.edu">CSAPP</a>
<li><a href="nothing.html">Nonexistent file</a>
<li><a href="http://www.nowhere.cs.cmu.edu">Nonexistent host</a>
<li><a href="http://www.google.com">Google</a>
<li><a href="http://www.cmu.edu">CMU</a>
<li><a href="http://www.yahoo.com">Yahoo</a>
<li><a href="http://www.nfl.com">NFL</a>
</ul>
</body>
</html>
Connection closed by foreign host.


Tiny Web Server


Serving Static Content

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void serve_static(int fd, char *filename, int filesize) {
    int srcfd;
    char *srcp, filetype[MAXLINE], buf[MAXBUF];

    /* Send response headers to client */
    get_filetype(filename, filetype);
    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
    sprintf(buf, "%sConnection: close\r\n", buf);
    sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
    sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
    Rio_writen(fd, buf, strlen(buf));
    printf("Response headers:\n");
    printf("%s", buf);

    /* Send response body to client */
    srcfd = Open(filename, O_RDONLY, 0);
    srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
    Close(srcfd);
    Rio_writen(fd, srcp, filesize);
    Munmap(srcp, filesize);
}

Serving Dynamic Content

flowchart LR
    client(("Client"))
    server(("Server"))
    cgi(("CGI"))

    client -- "Request" --> server -- "Create" --> cgi
    cgi -- "Content" --> server -- "Content" --> client
  • CGI(Common Gateway Interface) 프로그램에 인자를 전달하여 동적으로 웹 페이지를 구성한다.

Serving Dynamic Content with GET

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void serve_dynamic(int fd, char *filename, char *cgiargs) {
    char buf[MAXLINE], *emptylist[] = { NULL };

    /* Return first part of HTTP response */
    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Server: Tiny Web Server\r\n");
    Rio_writen(fd, buf, strlen(buf));
    if (Fork() == 0) {
        /* Real server would set all CGI vars here */
        setenv("QUERY_STRING", cgiargs, 1);
        Dup2(fd, STDOUT_FILENO);                /* Redirect stdout to client */
        Execve(filename, emptylist, environ);   /* Run CGI program */
    }
    Wait(NULL);  /* Parent waits for and reaps child */
}
  • 서버는 CGI 프로그램에게 인자를 전달하기 위해 환경 변수를 이용한다.
  • 서버는 stdout을 connected socket으로 리다이렉트하고, CGI 프로그램은 생성한 콘텐츠를 stdout에 출력한다.


References


Footnote

이 글은 저작자의 CC BY-SA 4.0 라이선스를 따릅니다.