第十一章 网络编程


客户端-服务器模型


每个网络应用都是基于 客户端-服务器模型 ,一个应用是由一个服务器进程和一个或者多个客户端进程组成。服务器管理某种资源,并且通过操作这种资源为它的客户端提供某种服务。
客户端-服务器模型中的基本操作是事务,一个事务由四步组成:

  1. 一个客户端需要服务时,它向服务器发送一个请求,发起一个事务。
  2. 服务器收到请求后,解释它,并以适当的方式操作它的资源。
  3. 服务器给客户端发送一个响应,并等待下一个请求。
  4. 客户端收到响应并处理它。

客户端和服务器都是 进程 ,而不是机器。一台主机可以同时运行许多不同的客户端和服务器,而且一个客户端和服务器的事务可以在同一台或是不同的主机上。

网络


客户端和服务器通常运行在不同的主机上,并且通过计算机网络的硬件和软件资源来通信。
对主机而言, 网络是一种I/O设备 ,是数据源和数据接收方。

  • 最流行的局域网技术是 以太网 ,一个以太网段包括一些电缆和一个叫做 集线器 的小盒子。每根电缆一端连接到主机的适配器,而另一端连接到集线器的一个端口。集线器不加分辨地将从一个端口上收到的每个位复制到其他所有端口上。每个以太网适配器都有一个全球唯一的48位地址(MAC地址),存储在适配器的非易失性存储器上。一台主机可以发送帧到这个网段内的其他任何主机,每个帧包括一些固定数量的头部位用来标识此帧的源和目的地址以及此帧的长度,此后跟着数据位的有效载荷,每个主机适配器都可以看到这个帧,但只有目的主机实际读取它。
  • 使用电缆和 网桥 ,多个以太网可以连接成较大的局域网,称为 桥接以太网 ,其中电缆用来连接网桥与网桥,或者用来连接网桥和集线器。网桥可以随着时间自动学习哪个主机可以通过哪个端口可达。
  • 多个不兼容的局域网可以通过 路由器 连接起来,组成一个internet(互联网络)。路由器可以用来连接各种局域网和广域网构建互联网络。

假设一个客户端运行在主机A上,主机A与LAN1相连,发送数据到运行在主机B上的服务器端,主机B与LAN2相连,这个过程分为8步:

  1. 主机A的客户端进行 系统调用 ,从客户端虚拟地址空间复制数据到内核缓冲区
  2. 主机A的 协议软件 通过在数据前附加互联网络包头(用于寻址到主机B)和LAN1帧头(用于寻址到路由器),创建一个LAN1的帧(LAN1帧的有效载荷是互联网络包,互联网络包有效载荷是实际用户数据, 封装 是基本的网络互联方法之一)
  3. LAN1适配器复制该帧到网络上
  4. 此帧到达路由器时,LAN1适配器从电缆上读取它,并把它传送到协议软件
  5. 路由器从互联网络包头中提取出目的互联网络地址,并用它作为 路由表的索引 ,确定向哪转发这个包,路由器剥落旧的LAN1帧头,加上寻址到主机B的LAN2帧头,并把得到的帧传送到适配器
  6. LAN2适配器复制该帧到网络上
  7. 此帧到达主机B时,适配器从电缆上读到此帧,并将它传送到协议软件
  8. 主机B的协议软件剥落包头和帧头,服务器再进行读取这些数据的系统调用

全球IP因特网


可以把因特网看做是一个世界范围的主机集合:

  • 主机集合被映射位一组32位的IP地址
  • 这组IP地址被映射为一组称为因特网域名的标识符
  • 因特网主机上的进程能够通过连接和任何其他因特网主机上的进程通信

一个IP地址是一个32位无符号整数,网络程序将IP存放在如下IP地址结构中:

struct in_addr  
{  
    uint32_t s_addr;  /* Address in network byte order(big-endian) */  
};  

TCP/IP为任意整数数据项定义了统一的网络字节顺序( 大端字节顺序 ),IP地址结构中存放的地址总是以大端法顺序存放的。Unix提供了一些函数在网络和主机字节顺序间实现转换。

#include <arpa/inet.h>  
// 把主机上的数据按照网络字节顺序返回  
uint32_t htonl(uint32_t hostlong);  
uint16_t htons(uint16_t hostshort);  
// 把网络上的数据按照主机字节顺序返回  
uint32_t ntohl(uint32_t netlong);  
uint16_t ntohs(uint16_t netshort);  

Linux中可以使用HOSTNAME命令确定主机的点分十进制IP地址:

linux> hostname -i  
127.0.1.1  

应用程序可以使用inet_ptoninet_ntop函数实现IP地址和点分十进制串之间的转换:

#include <arpa/inet.h>  
// AF_NET表示32位的IPv4地址,若使用AF_INET6则表示128位的IPv6地址  
// 将点分十进制串src转换为二进制的网络字节顺序的IP地址dst  
// 若成功则返回1,若src为非法点分十进制地址则返回0,出错时返回-1  
int inet_pton(AF_INET, const char *src, void *dst);  
// 将二进制的网络字节顺序的IP地址src转换为对应的点分十进制表示  
// 并把得到的以null结尾的字符串最多size字节复制到dst中  
// 若成功则返回指向点分十进制字符串的指针,出错则返回NULL  
const char *inet_ntop(AF_INET, const void *src, char *dst, socklen_t size);  

因特网客户端和服务器互相通信使用的是IP地址,因特网还定义了域名以及一种将域名映射到IP地址的机制。
因特网定义了域名集合和IP地址集合之间的映射,这个映射通过分布世界范围内的数据库(DNS,域名系统)维护。
DNS数据库由上百万的主机条目结构组成,每条定义了一组域名和一组IP地址之间的映射:

  • 最简单的情况,一个域名和一个IP地址一一映射
  • 某些情况,多个域名可以映射到同一组的多个IP地址
  • 某些合法域名没有映射到任何IP地址

因特网客户端和服务器通过在 连接 上发送和接收字节流来通信:

  • 从连接一对进程的意义上而言,连接是 点对点
  • 数据可以同时双向流动,因此连接是 全双工
  • 由源进程发出的字节流最终被目的进程以它发出的顺序收到,因此连接是 可靠

一个 套接字 是连接的一个端点,每个套接字都有相应的套接字地址,是由一个因特网地址和一个16位的整数端口组成的,用地址:端口表示。客户端套接字地址中的端口是由内核自动分配的,称为 临时端口 ;服务器套接字地址中的端口通常是某个 知名端口 ,是和这个服务相对应的。文件/etc/services包含一张知名名字和知名端口之间的映射。
一个连接是由它两端的套接字地址唯一确定的,这对套接字地址叫做套接字对,可以用下列元组表示:

(cliaddr:cliport, servaddr:servport)  

套接字接口


套接字接口是一组函数,它们和Unix I/O函数结合起来,用以创建网络应用。

一个套接字是一个通信的端点,从程序角度来看,一个套接字是一个有 相应描述符的打开文件
因特网套接字地址存放在类型为sockaddr_in的16字节结构中,但是connectbindaccept函数要求一个指向与协议相关的套接字地址结构的指针,因此定义了套接字函数指向通用的sockaddr结构的指针,应用程序将与协议特定的结构的指针强制转换为这个通用结构。

/* IP socket address structure */  
struct sockaddr_in  
{  
    uint16_t sin_family;       // Protocol family (always AF_INET)  
    uint16_t sin_port;         // Port number in network byte order  
    struct in_addr sin_addr;   // IP address in network byte order  
    unsigned char sin_zero[8]; // Pad to sizeof(struct sockaddr)  
};  
/* Generic socket address structure (for connect, bind and accept) */  
struct sockaddr  
{  
    uint16_t sa_family;  // Protocol family  
    char sa_data[14];    // Address data  
};  

客户端和服务器使用socket函数来 创建一个套接字描述符 ,其参数最好使用getaddrinfo函数生成,这样就与协议无关了。socket返回的clientfd描述符仅是部分打开的,还不能用于读写。

#include <sys/types.h>  
#include <sys/socket.h>  
// 成功时返回非负描述符,出错返回-1  
int socket(int domain, int type, int protocol);  
// 使用硬编码使套接字成为连接的一个端点  
clientfd = Socket(AF_INET, SOCK_STREAM, 0);  

客户端通过调用connect函数 建立和服务器的连接connect函数试图与套接字地址为addr的服务器建立一个因特网连接,其中addrlen是sizeof(sockaddr_in)connect函数会阻塞,一直到连接成功建立或是发生错误。如果成功,clientfd描述符现在就可以读写了,连接是由套接字对(x:y, addr.sin_addr:addr.sin_port)确定的。

#include <sys/socket.h>  
// 成功时返回0,出错返回-1  
int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen);  

服务器使用bindlistenaccept函数来和客户端建立连接。
bind函数告诉内核将addr中的 服务器套接字地址和套接字描述符sockfd联系 起来。
参数addrlen是sizeof(sockaddr_in),最好使用getaddrinfo函数为bind提供相关参数。

#include <sys/socket.h>  
// 成功返回0,出错返回-1  
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);  

客户端是发起连接请求的主动实体,服务器是等待来自客户端的连接请求的被动实体。默认内核认为socket函数创建的描述符对应于主动套接字,服务器调用listen函数告诉内核,描述符是被服务器使用而不是客户端使用的。
listen函数将sockfd从一个主动套接字 转化为一个监听套接字 ,该套接字可以接受来自客户端的连接请求。backlog参数暗示了内核在开始拒绝连接请求之前,队列中要排队的未完成的连接请求数量。

#include <sys/socket.h>  
// 成功返回0,出错返回-1  
int listen(int sockfd, int backlog);  

服务器通过调用accept函数 等待来自客户端的连接请求
accept函数等待来自客户端的连接请求到达侦听描述符listenfd,然后在addr中填写客户端的套接字地址,并返回一个 已连接描述符 ,这个描述符可以被用来利用Unix I/O函数与客户端通信。

监听描述符与已连接描述符的区别

  • 监听描述符作为客户端连接请求的一个端点,通常被创建一次,并存在于服务器的整个生命周期
  • 已连接描述符是客户端和服务器之间已经建立起来了的连接的一个端点,服务器每次接受连接请求时都会创建一次,它只存在于服务器为一个客户端服务的过程中
  • 区分监听描述符和已连接描述符使得我们可以建立并发服务器,它能够同时处理许多客户端连接
#include <sys/socket.h>  
// 成功时返回非负连接描述符,出错返回-1  
int accept(int listenfd, struct sockaddr *addr, int *addrlen);  

为了编写 独立于任何特定版本的IP协议 的网络程序,可以使用getaddrinfogetnameinfo函数。
getaddrinfo函数将主机名、主机地址、服务名和端口号的字符串表示 转化成套接字地址结构
给定host和service,getaddrinfo返回一个result,它指向addrinfo结构的链表,其中每个结构指向一个对应于host和service的套接字地址结构。客户端调用getaddrinfo后,会遍历这个列表,依次尝试每个套接字地址,直到调用socketconnect成功。服务器也会尝试遍历列表中的每个套接字地址,直到调用socketbind成功。
为了避免内存泄漏,应用程序最后要调用freeaddrinfo释放该链表。
gai_strerror可以把getaddrinfo返回的非零错误代码转化为消息字符串。

#include <sys/types.h>  
#include <sys/socket.h>  
#include <netdb.h>  
// 成功时返回0, 错误时返回非0错误码  
// host参数可以是域名或点分十进制IP地址  
// service可以是服务名(如http),或十进制端口号  
// host和service中必须指定两者中的一个  
// hints是一个addrinfo结构,实际中用memset将结构清零,然后设置一些字段  
// 默认最多返回三个addrinfo结构,每个ai_socktype字段不同:连接/数据报/原始套接字  
// hints的ai_flags设置AI_PASSIVE时说明套接字地址用作监听套接字,此时host参数应该为NUNLL  
// hints的ai_flags(位掩码)设置AI_ADDRCONFIG是使用连接推荐使用的标志  
int getaddrinfo(const char *host, const char *service,   
        const struct addrinfo *hints, struct addrinfo **result);  
void freeaddrinfo(struct addrinfo *result);  
// 返回错误码对应的错误消息  
const char *gai_strerror(int errcode);  

// getaddrinfo创建输出列表中的addrinfo结构时会填写除ai_flags外的每个字段  
// addrinfo中的字段是不透明的,可以直接传递给套接字接口中的函数,应用程序代码无需再做任何处理  
struct addrinfo  
{  
    int ai_flags;      // Hints argument flags  
    int ai_family;     // First arg to socket function  
    int ai_socktype;   // Second arg to socket function  
    int ai_protocol;   // Third arg to socket function  
    char *ai_canonname;// Canonical hostname  
    size_t ai_addrlen; // Size of ai_addr struct  
    struct sockaddr *ai_addr; // Ptr to socket address structure  
    struct addrinfo *ai_next; // Ptr to next item in linked list  
};  

getnameinfo函数将一个套接字地址结构 转换成相应的主机和服务名字符串
getnameinfo将套接字地址结构sa转换为对应的主机和服务名字符串,并复制到host和service缓冲区。

#include <sys/socket.h>  
#include <netdb.h>  
// 成功时返回0,错误时返回非零的错误代码  
// sa指向大小为salen字节的套接字地址结构  
// host指向大小为hostlen字节的缓冲区  
// service指向大小为servlen字节的缓冲区,host和service至少设置其中之一不为NULL  
int getnameinfo(const struct sockaddr *sa, socklen_t salen,  
        char *host, size_t hostlen, char *service, size_t servlen, int flags);  

客户端和服务器互相通信时可以使用高级的辅助函数。
客户端调用open_clientfd建立与服务器的连接。

// 客户端与运行在hostname主机port端口上的服务器建立连接  
// 返回一个打开的套接字描述符,该描述符可以用Unix I/O函数做输入输出  
int open_clientfd(char *hostname, char *port)  
{  
    int clientfd;  
    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 */  
    Getaddrinfo(hostname, port, &hints, &listp);  

    /* 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 */  
        if(connect(clientfd, p->ai_addr, p->ai_addrlen) != -1)  
            break;  /* Success */  
        Close(clientfd);   /* Connect failed, try another */          
    }  

    /* Clean up*/  
    Freeaddrinfo(listp);  
    if(!p)   /* All connects failed */  
        return -1;  
    else     /* The last connect succeeded */  
        return clientfd;  
}  

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

// 打开和返回一个监听描述符,这个描述符准备好在端口port上接收连接请求  
int open_listenfd(char *port)  
{  
    struct addrinfo hints, *listp, *p;  
    int listenfd, 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 */  
    Getaddrinfo(NULL, port, &hints, &listp);  

    /* 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, (const void *)&optval, sizeof(int));  

        /* Bind the descriptor to the address */  
        if(bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)  
            break;  /* Success */  
        Close(listenfd);  /* Bind failed, try the next */  
    }  

    /* 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;  
}  

Web服务器


Web客户端和服务器之间的交互用的是一个基于文本的应用级协议: HTTP
一个Web客户端(浏览器)打开一个到服务器的因特网连接,并且请求某些内容,服务器响应所请求的内容,然后关闭连接,浏览器读取这些内容并把它显示在屏幕上。
对于Web客户端和服务器而言,内容是与一个MIME(Multipurpose Internet Mail Extensions,多用途的网际邮件扩充协议)类型相关的字节序列。
Web服务器有两种方式向客户端提供内容:

  1. 取一个磁盘文件,并将它的内容返回给客户端
  2. 运行一个可执行文件,并将它的输出返回给客户端

Web服务器返回的内容与它管理的某个文件相关联,这些文件都有一个唯一的名字,叫做URL(Universal Resource Locator,通用资源定位符)。
一个HTTP请求行的形式是:

method URI version  

HTTP支持许多方法,包括GET、POST、HEAD等。
URI(Uniform Resource Identifier,统一资源标识符)包括文件名和可选的参数。
version表明了请求遵循的HTTP版本,最新的HTTP版本为HTTP/2.0。
HTTP标准要求每个文本行都由一对回车和换行符来结束。
一个HTTP相应行的格式是:

version status-code status-message  

状态码是一个3位的正整数,常见的状态码有:

  • 200 成功
  • 301 永久移动
  • 400 错误请求
  • 403 禁止
  • 404 未发现
  • 501 未实现
  • 505 HTTP版本不支持

TINY Web服务器


TINY是一个简单的Web服务器,假设静态内容的主目录就是它的当前目录,可执行文件目录是./cgi-bin。
构造一个长时间运行而不崩溃的健壮Web服务器是一件困难的任务,比如一个健壮的服务器必须捕获SIGPIPE信号,并且检查write函数调用是否有EPIPE错误。

/*  
 * tiny.c - A simple, iterative HTTP/1.0 Web server that uses the  
 *     GET method to serve static and dynamic content  
 */  
#include "csapp.h"  

void doit(int fd);  
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);  
void read_requesthdrs(rio_t *rp);  
int parse_uri(char *uri, char *filename, char *cgiargs);  
void serve_static(int fd, char *filename, int  filesize);  
void serve_dynamic(int fd, char *filename, char *cgiargs);  
void get_filetype(char *filename, char *filetype);  

int main(int argc, char **argv)  
{  
    int listenfd, connfd;  
    char hostname[MAXLINE], port[MAXLINE];  
    socklen_t clientlen;  
    struct sockaddr_storage clientaddr;  

    /* Check command-line args */  
    if(argc != 2)  
    {  
        fprintf(stderr, "usage: %s <port>\n", argv[0]);  
        exit(1);  
    }  

    listenfd = Open_listenfd(argv[1]);  
    while(1)  
    {  
        clientlen = sizeof(clientaddr);  
        connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);  
        Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);  
        printf("Accept connection from (%s, %s)\n", hostname, port);  
        doit(connfd);  
        Close(connfd);  
    }  
}  

void doit(int fd)  
{  
    int is_static;  
    struct stat sbuf;  
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];  
    char filename[MAXLINE], cgiargs[MAXLINE];  
    rio_t rio;  

    /* Read request line and headers */  
    Rio_readinitb(&rio, fd);  
    Rio_readlineb(&rio, buf, MAXLINE);  
    printf("Request headers:\n");  
    printf("%s", buf);  
    sscanf(buf, "%s %s %s", method, uri, version);  
    if(strcasecmp(method, "GET"))  
    {  
        clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");  
        return;  
    }  
    read_requesthdrs(&rio);  

    /* Parse URI from GET request */  
    is_static = parse_uri(uri, filename, cgiargs);  
    if(stat(filename, &sbuf) < 0)  
    {  
        clienterror(fd, filename, "404", "Not found", "Tiny couldn't find this file");  
        return;  
    }  

    if(is_static)  /* Serve static content */  
    {  
        if(!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))  
        {  
            clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read the file");  
            return;  
        }  
        serve_static(fd, filename, sbuf.st_size);  
    }  
    else     /* Serve dynamic content */  
    {  
        if(!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode))  
        {  
            clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't run the CGI program");  
            return;  
        }  
        serve_dynamic(fd, filename, cgiargs);  
    }  
}  

void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg)  
{  
    char buf[MAXLINE], body[MAXBUF];  

    /* Build the HTTP response body */  
    sprintf(body, "<html><title>Tiny Error</title>");  
    sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body);  
    sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);  
    sprintf(body, "%s<p>%s: %s</p>\r\n", body, longmsg, cause);  
    sprintf(body, "%s<hr><em>The Tiny Web server</em>\r\n", body);  

    /* Print the HTTP response */  
    sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);  
    Rio_writen(fd, buf, strlen(buf));  
    sprintf(buf, "Content-type: text/html\r\n");  
    Rio_writen(fd, buf, strlen(buf));  
    sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));  
    Rio_writen(fd, buf, strlen(buf));  
    Rio_writen(fd, body, strlen(body));  
}  

void read_requesthdrs(rio_t *rp)  
{  
    char buf[MAXLINE];  

    Rio_readlineb(rp, buf, MAXLINE);  
    while(strcmp(buf, "\r\n"))  
    {  
        Rio_readlineb(rp, buf, MAXLINE);  
        printf("%s", buf);  
    }  
    return;  
}  

int parse_uri(char *uri, char *filename, char *cgiargs)  
{  
    char *ptr;  

    if(!strstr(uri, "cgi-bin"))  
    {   /* Static content */  
        strcpy(cgiargs, "");  
        strcpy(filename, ".");  
        strcat(filename, uri);  
        if(uri[strlen(uri) - 1] == '/')  
            strcat(filename, "home.html");  
        return 1;  
    }  
    else  
    {  /* Dynamic content */  
        ptr = index(uri, '?');  
        if(ptr)  
        {  
            strcpy(cgiargs, ptr+1);  
            *ptr = '\0';  
        }  
        else  
            strcpy(cgiargs, "");  
        strcpy(filename, ".");  
        strcat(filename, uri);  
        return 0;  
    }  
}  

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 Servre\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 header:\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);  
}  

/*  
 * get_filetype - Derive file type from filename  
 */  
void get_filetype(char *filename, char *filetype)  
{  
    if(strstr(filename, ".html"))  
        strcpy(filetype, "text/html");  
    else if(strstr(filename, ".gif"))  
        strcpy(filetype, "image/gif");  
    else if(strstr(filename, ".png"))  
        strcpy(filetype, "image/png");  
    else if(strstr(filename, ".jpg"))  
        strcpy(filetype, "image/jpeg");  
    else  
        strcpy(filetype, "text/plain");  
}  

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)  
    {/* Child  */  
        /* 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 */  
}  

评论

还没有登陆?评论请先登陆注册

还没有评论,抢个沙发吧!

 联系方式 contact me

Github
Email
QQ
Weibo