深入理解计算机系统(第三版)第十一章重要内容摘要

11.1客户端-服务器编程模型

每个网络应用都是基于客户端-服务器模型的。一个应用是由一个服务器进程和一个或者多个客户端进程组成的。

 

 

 

客户端和服务器是进程,而不是常提到的机器或者主机。

11.2网络

对主机而言,网络只是又一种I/O设备,是数据源和数据接收方。

每个以太网适配器都有一个全球唯一的48位地址。它存储在这个适配器的非易失性存储器上。

 

 

11.3全球IP因特网

每台因特网主机都运行实现TCP/IP协议

因特网的客户端和服务器混合使用套接字接口函数和Unix I/O函数来进行通信

 

 

 13.3.1 IP地址

 

TCP/IP为任意整数数据项定义了统一的网络字节顺序(network byte order)(大端字节顺序),例如IP地址,它放在包头中跨过网络被携带。在IP地址结构中存放的地址总是以(大端法)网络字节顺序存放的,即使主机字节顺序(host byte order)是小端法。

 

 

 

 

11.3.2因特网域名

因特网也定义了一组更加人性化的域名(domain name),以及一种将域名隐射到IP地址的机制。域名是一串用句点分隔的单词(字母、数字和破折号),例如whaleshark.ics.cs.cmu.edu。

 

 

 

因特网定义了域名集合和IP地址集合之间的映射。这个映射是通过分布世界范围内的数据库(称为DNS(domain Name System,域名系统))来维护的。

 

我们可以用Linux的NSLOOKUP程序来探究DNS映射的一些属性,这个程序能展示与某个IP地址对应的域名。

 

11.3.3 因特网连接

 

## 11.4套接字接口

 

11.4.1 套接字地址结构

 

11.4.2 socket函数

 

11.4.3connect函数

 

11.4.4bind函数

 

 

 

11.4.5listen函数

 

11.4.6accept函数

11.4.7主机和服务的转换

1.getaddrinfo函数

 

 

 

 

 

 

 

 

 

 

 

 

 2.getnameinfo

 

 

 

11.4.8套接字接口的辅助函数

1.open_clientfd函数

 

 

 

 


/* $begin open_clientfd */
int open_clientfd(char *hostname, char *port) {
    int clientfd, rc;
    struct addrinfo hints, *listp, *p;

    /* 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;  /* ... using a numeric port arg. */
    hints.ai_flags |= AI_ADDRCONFIG;  /* Recommended for connections */
    if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo failed (%s:%s): %s\n", hostname, port, gai_strerror(rc));
        return -2;
    }
  
    /* Walk the list for one that we can successfully connect to */
    for (p = listp; p; p = p->ai_next) {
        /* Create a socket descriptor */
        if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) 
            continue; /* Socket failed, try the next */

        /* Connect to the server */
        if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1) 
            break; /* Success */
        if (close(clientfd) < 0) { /* Connect failed, try another */  //line:netp:openclientfd:closefd
            fprintf(stderr, "open_clientfd: close failed: %s\n", strerror(errno));
            return -1;
        } 
    } 

    /* Clean up */
    freeaddrinfo(listp);
    if (!p) /* All connects failed */
        return -1;
    else    /* The last connect succeeded */
        return clientfd;
}
/* $end open_clientfd */</pre>

 

 2.open_listenfd函数

通过调用open_listenfd函数,服务器创建一个监听描述符,准备好接收连接请求。

 

 

 

/* $begin open_listenfd */
int open_listenfd(char *port) 
{
    struct addrinfo hints, *listp, *p;
    int listenfd, rc, 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 ((rc = getaddrinfo(NULL, port, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo failed (port %s): %s\n", port, gai_strerror(rc));
        return -2;
    }

    /* Walk the list for one that we can bind to */
    for (p = listp; p; p = p->ai_next) {
        /* Create a socket descriptor */
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) 
            continue;  /* Socket failed, try the next */

        /* Eliminates "Address already in use" error from bind */
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,    //line:netp:csapp:setsockopt
                   (const void *)&optval , sizeof(int));

        /* Bind the descriptor to the address */
        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
            break; /* Success */
        if (close(listenfd) < 0) { /* Bind failed, try the next */
            fprintf(stderr, "open_listenfd close failed: %s\n", strerror(errno));
            return -1;
        }
    }


    /* Clean up */
    freeaddrinfo(listp);
    if (!p) /* No address worked */
        return -1;

    /* Make it a listening socket ready to accept connection requests */
    if (listen(listenfd, LISTENQ) < 0) {
        close(listenfd);
    return -1;
    }
    return listenfd;
}/* $end open_listenfd */

  最后我们调用listen函数,将listenfd转换为一个监听描述符,并返回给调用者。如果listen失败,我们要小心地避免内存泄漏,在返回前关闭描述符。

11.4.9echo客户端和服务器的示例

11.5Web服务器

11.5.1Web基础

Web客户端和服务端之间的交互用的是一个基于文本地应用级协议,叫做HTTP

Web内容可以用一种叫做HTML(Hypertext Markup Language,超文本标记语言)的语言来编写。

 

11.5.2Web内容

 

 

 

 

 

 

 

11.5.3HTTP事务

 

 1.HTTP请求

 

 

 

 2.HTTP响应

 

 

 

 

 

11.5.4服务动态内容

1.客户端如何将程序参数传递给服务器

 

 

 2.服务器如何将参数传递给子进程

 

 3.服务器如何将其他信息传递给子进程

 

 4.子进程将它的输出发送到哪里

一个CGI程序将它的动态内容发送到标准输出。在子进程加载并运行CGI程序之前。它使用Linux dup2函数将标准输出重定向到和客户端相关联地已连接描述符。因此,任何CGI程序写到标准输出的东西都会直接到达客户端。

  注意,因为父进程不知道子进程生成的内容的类型或大小,所以子进程就要负责生成Content_type和Content-length响应报头,以及终止报头的空行。

11.6综合:TINY Web服务器

 

posted @ 2021-01-19 11:44  丸子球球  阅读(146)  评论(0)    收藏  举报