任务:
(1)实现服务器与客户端间的通信。
(2)可以实现HTTP请求中的GET方法。
(3)提供静态网页浏览功能,如可浏览:HTML页面,无格式文本,常见图像格式等。
(4)提供可以传递参数的动态网页浏览功能。
(5)可以检查一些明显错误报告给客户端,如:403无权访问,404找不到所请求的文件,501不支持相应方法等。
(6)在服务器端可输出HTTP响应的相关信息。
服务器端可配置参数,如:主目录,首页文件名,HTTP端口号等项。
套接字接口
套接字接口是一组函数,它们和Unix I/O函数结合起来,用以创建网络网络应用。
客户端和服务器使用socket函数来创建一个套接字描述符。
服务器端通过bind函数告诉内核将addr中的服务器套接字地址和套接字描述符sockfd联系起来;通过listen函数告诉内核,描述符是被服务器而不是客户端使用;通过accept函数来等待来自客户端的连接请求。
HTTP(超文本传输协议)被用在Web客户端和服务器之间的交互。当客户端需要服务时,它就向服务器发送一个HTTP请求说明需要的东西,服务器收到后解析,然后进行回应。本次实验,实现了应用最广泛的GET方法,并通过解析,判断是需要动态内容,还是静态内容,还是错误处理。
doit函数:处理一个HTTP事务。
首先读和解析请求行,获得方法,如果不是所支持的GET方法,就发送给它一个错误消息,并返回到主程序,主程序随后关闭连接并等待下一个连接请求。否则,读并且调用read_requesthdrs忽略报头中的其他信息。然后将URI解析为一个文件名和一个可能为空的CGI参数字符串,并且设置一个标志,表明请求的是静态内容还是动态内容。如果所需的文件不存在,就发送一个错误信息到客户端并返回。
parse_uri函数:解析URI并转为所需的文件名。
初始时设主目录为当前目录,可执行文件的目录为./cgi-bin。如果请求的是衣一个静态内容,就清楚CGI参数字符串,然后转为文件的路径名。如果请求的是动态内容,就提取出所有的CGI参数,然后转为文件的路径名。
serve_static函数:处理静态内容。
首先打开filename文件,用mmap函数将文件映射到一个虚拟内存空间,然后关闭这个文件,rio_writen函数复制到客户端已连接的描述符。这样前段就可以显示出所需的内容。
serve_dynamic函数:处理动态内容。
首先向客户端发送成功相应的内容,然后派生一个子进程,子进程用来自请求URI的CGI参数初始化QUERY_STRING环境变量,重定向标准输出到已连接文件描述符,然后执行CGI程序。
signal_r(SIGCHLD, sigchild_handler)函数:处理僵尸进程函数。
fork创建出很多进程,这些进程执行完以后就exit(0)然后发个信号通知主进程,exit函数退出后,进程的有关资源(打开的文件描述符,栈,寄存器,有关信息等)还没有释放掉,如果进程不被回收的话就会占用存储器资源这样的进程就称为僵尸进程。所以解决办法就是主进程使用一个信号处理函数,等待僵尸进程回收。
1 /* 2 * @filename: webServer.c 3 * @author: Flyuz 4 * @date: 2018年6月25日 5 * @description: 主程序 6 */ 7 8 #include "functionLib.h" 9 10 void doit(int fd); 11 void serve_static(int fd, char *filename, int filesize); 12 int parse_uri(char *uri, char *filename, char *cgiargs); 13 void read_requesthdrs(rio_t *rp); 14 void clienterror(int fd, char *cause, char *errnum, char *shortmsg, 15 char *longmsg); 16 void get_filetype(char *filename, char *filetype); 17 void serve_dynamic(int fd, char *filename, char *cgiargs); 18 19 20 int main(int argc, char **argv) { 21 int listenfd, connfd; 22 socklen_t clientlen; 23 24 struct sockaddr_in clientaddr; 25 26 if (argc != 2) { 27 fprintf(stderr, "Usage: %s <port> ", argv[0]); 28 exit(1); 29 } 30 31 printf("The web server has been started..... "); 32 33 listenfd = Open_listenfd(argv[1]); 34 /* 35 信号处理函数 36 用来处理僵尸进程 37 */ 38 signal_r(SIGCHLD, sigchild_handler); 39 40 while (1) { 41 clientlen = sizeof(clientaddr); 42 if((connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen)) < 0) 43 { 44 if(errno == EINTR) 45 continue; 46 else 47 printf("Accept error..."); 48 } 49 pid_t pid = Fork(); 50 if(pid == 0) 51 { 52 doit(connfd); 53 Close(connfd); 54 exit(0); 55 } 56 else 57 { 58 Close(connfd); 59 } 60 } 61 } 62 63 void doit(int fd) { 64 int is_static; 65 struct stat sbuf; 66 rio_t rio; 67 char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE]; 68 char filename[MAXLINE]; //设置根目录 69 char cgiargs[MAXLINE]; 70 71 //初始化 rio 结构 72 Rio_readinitb(&rio, fd); 73 //读取http请求行 74 Rio_readlineb(&rio, buf, MAXLINE); 75 //格式化存入 把该行拆分 76 sscanf(buf, "%s %s %s", method, uri, version); 77 78 //只能处理GET请求,如果不是GET请求的话返回错误 79 if (strcasecmp(method, "GET")) { 80 clienterror(fd, method, "501", "Not Implemented", 81 "Flyuz does not implement thid method"); 82 return; 83 } 84 85 //读取并忽略请求报头 86 read_requesthdrs(&rio); 87 88 // memset(filename,0,sizeof(filename)); 89 //解析 URI 90 is_static = parse_uri(uri, filename, cgiargs); 91 92 //文件不存在 93 if (stat(filename, &sbuf) < 0) { 94 clienterror(fd, filename, "404", "Not found", 95 "Flyuz couldn't find this file"); 96 return; 97 } 98 99 if (is_static) { //服务静态内容 100 if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) { 101 clienterror(fd, filename, "403", "Forbidden", 102 "Flyuz couldn't read the file"); 103 return; 104 } 105 serve_static(fd, filename, sbuf.st_size); 106 } else { //服务动态内容 107 if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) { 108 clienterror(fd, filename, "403", "Forbidden", 109 "Flyuz couldn't run the CGI program"); 110 return; 111 } 112 serve_dynamic(fd, filename, cgiargs); 113 } 114 } 115 116 /* 117 * 读取http 请求报头,无法使用请求报头的任何信息,读取之后忽略掉 118 */ 119 void read_requesthdrs(rio_t *rp) { 120 char buf[MAXLINE]; 121 122 Rio_readlineb(rp, buf, MAXLINE); 123 printf("%s", buf); 124 //空文本行终止请求报头,碰到 空行 就结束 空行后面是内容实体 125 while (strcmp(buf, " ")) { 126 Rio_readlineb(rp, buf, MAXLINE); 127 printf("%s", buf); 128 } 129 return; 130 } 131 132 /* 133 * 解析URI 为 filename 和 CGI 参数 134 * 如果是动态内容返回0;静态内容返回 1 135 */ 136 int parse_uri(char *uri, char *filename, char *cgiargs) { 137 if (!strstr(uri, 138 "cgi-bin")) { //默认可执行文件都放在cgi-bin下,这里表示没有找到 139 strcpy(cgiargs, ""); 140 strcpy(filename, "."); 141 strcat(filename, uri); 142 /* 143 if(uri[strlen(uri)-1] == "/") //设置默认文件 144 strcat(filename, "index.html"); 145 */ 146 147 return 1; // static 148 } else { //动态内容 149 char *ptr = strchr(uri, '?'); 150 if (ptr) { //有参数 151 strcpy(cgiargs, ptr + 1); 152 *ptr = '