@RunZhi
2016-09-13T16:14:14.000000Z
字数 28364
阅读 1563
操作系统实验报告1.编译并运行WebBench,测试baidu服务器的性能
2.熟读并理解WebBench源码,将每一行程序的理解注释加入该任务四的实验报告中,要求注释偏口语化.
3.编译并运行TinyHttpd,并用浏览器访问该小型web服务器
4.熟读并理解TinyHttpd的源码,将每一行程序的理解注释加入该任务四的实验报告中,要求注释偏口语化
5.
将自己编译的WebBench程序去测试自己编译的TinyHttpd程序,看看会发生什么“现象”?
请分析:
A) 为什么会发生这个“现象”?
B) 是谁的问题导致该现象的发生?
C) 请修复该问题.
下载webbench源代码并且运行程序:
sudo makesudo make install./webbench -c 100 -t 30 http://www.baidu.com/
然后终端输出如下:

源代码的解析附在后面.
下载tinyHttpd源代码,但此时它们是无法通过编译的,需要进行一定的修改,具体修改处详见:http://blog.csdn.net/cqu20093154/article/details/41025885.
完成编译后,运行程序:
./httpd
终端显示:

打开浏览器,在地址栏输入localhost:42557,浏览器将显示:

运行tinyhttpd,然后用webbench进行测试,发现如下现象

所有的request都是fail的!再看看httpd的进程,早已经不见了.
上网查找资料后,得知问题出现在tinyhttpd的httpd.c的一处源代码:
...void unimplemented(int client){char buf[1024];// HTTP method 不被支持sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");send(client, buf, strlen(buf), 0);// 输出服务器信息到网页上sprintf(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</TITLE></HEAD>\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</BODY></HTML>\r\n");send(client, buf, strlen(buf), 0);}...
因为webbench在访问一次之后就断掉了,tinyhttpd是否好几次把信息发送过去,由于webbench连接成功并且收到一次消息后便断了连接(具体看webbench.c的如下代码片段)
static int bench(void){..../* check avaibility of target server */// host/proxyhost为服务器端ip, proxyport为服务器端口号建立socket连接i=Socket(proxyhost==NULL?host:proxyhost, proxyport);if(i<0) {// 出现错误fprintf(stderr, "\nConnect to server failed. Aborting benchmark.\n");return 1;}//对套接字i的引用计数-1,当引用计数为0的时候,关闭套接字close(i);....
而webbench给tinyhttpd发送的是无method的请求,因此tinyhttpd将会执行unimplemented函数:
void unimplemented(int client){char buf[1024];// HTTP method 不被支持sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");send(client, buf, strlen(buf), 0);// 输出服务器信息到网页上sprintf(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</TITLE></HEAD>\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</BODY></HTML>\r\n");send(client, buf, strlen(buf), 0);}
由于webbench是收到了一次消息便断了连接,因此在tinyhttpd在执行完上面函数的第11行的时候(send函数),就会向系统发送异常消息,而程序没有对消息进行处理,因此程序会dump掉。
要改的方法很多,其中一种方法就是,把要发送的消息一次性发送完.把那一大段sprintf send的直接改成:
strcat(buf, "HTTP/1.0 501 Method Not Implemented\r\n");strcat(buf, SERVER_STRING);strcat(buf, "Content-Type: text/html\r\n\r\n<HTML><HEAD><TITLE>Method Not Implemented\r\n</TITLE></HEAD>\r\n<BODY><P>HTTP request method not supported.\r\n</BODY></HTML>\r\n");printf("%s\n",buf);send(client, buf, strlen(buf), 0);
也可以让send不发异常消息给系统,做法是:把send函数的最后一个参数设为:MSG_NOSIGNAL.
然后重新开始测试,即可.测试结果如下:

(出现了Cannot allocate memory错误,但个人认为这不是错误而是电脑硬件条件不够好的缘故)
//webbench.c/** (C) Radim Kolar 1997-2004* This is free software, see GNU Public License version 2 for* details.** Simple forking WWW Server benchmark:** Usage:* webbench --help** Return codes:* 0 - sucess* 1 - benchmark failed (server is not on-line)* 2 - bad param* 3 - internal error, fork failed**/#include "socket.c"#include <unistd.h>#include <sys/param.h>#include <rpc/types.h>#include <getopt.h>#include <strings.h>#include <time.h>#include <signal.h>/* values */volatile int timerexpired = 0; //判断压力测试是否已经达到预定时间int speed = 0; //记录成功得到服务器响应的进程数量int failed = 0; //记录失败的进程数量int bytes = 0; //记录进程成功读取的字节数/* globals */int http10 = 1; /* http版本,0 - http/0.9, 1 - http/1.0, 2 - http/1.1 *//* Allow: GET, HEAD, OPTIONS, TRACE */#define METHOD_GET 0#define METHOD_HEAD 1#define METHOD_OPTIONS 2#define METHOD_TRACE 3#define PROGRAM_VERSION "1.5"int method = METHOD_GET; //请求方式,默认为get方式int clients = 1; //并发数量,默认为1int force = 0; //0表示等待读取从服务器返回的数据,1表示不等待int force_reload = 0; //0表示缓存,1表示不缓存int proxyport = 80; //代理服务器的端口号char *proxyhost = NULL; //代理服务器的ipint benchtime = 30; //压力测试时间,默认为30秒/* internal */int mypipe[2]; //使用管道进行父进程与子进程的通信char host[MAXHOSTNAMELEN]; //服务器端的ip#define REQUEST_SIZE 2048char request[REQUEST_SIZE]; //所要发送的http请求static const struct option long_options[]={{"force", no_argument, &force, 1},{"reload", no_argument, &force_reload, 1},{"time", required_argument, NULL, 't'},{"help", no_argument, NULL, '?'},{"http09", no_argument, NULL, '9'},{"http10", no_argument, NULL, '1'},{"http11", no_argument, NULL, '2'},{"get", no_argument, &method, METHOD_GET},{"head", no_argument, &method, METHOD_HEAD},{"options", no_argument, &method, METHOD_OPTIONS},{"trace", no_argument, &method, METHOD_TRACE},{"version", no_argument, NULL, 'V'},{"proxy", required_argument, NULL, 'p'},{"clients", required_argument, NULL, 'c'},{NULL, 0, NULL, 0}};/* prototypes */static void benchcore(const char* host, const int port, const char *request);static int bench(void);static void build_request(const char *url);static void alarm_handler(int signal){//设置标记,表示已经达到了预定的测试时间timerexpired=1;}static void usage(void){//输出如何使用webbench信息的函数fprintf(stderr,"webbench [option]... URL\n"" -f|--force Don't wait for reply from server.\n"" -r|--reload Send reload request - Pragma: no-cache.\n"" -t|--time <sec> Run benchmark for <sec> seconds. Default 30.\n"" -p|--proxy <server:port> Use proxy server for request.\n"" -c|--clients <n> Run <n> HTTP clients at once. Default one.\n"" -9|--http09 Use HTTP/0.9 style requests.\n"" -1|--http10 Use HTTP/1.0 protocol.\n"" -2|--http11 Use HTTP/1.1 protocol.\n"" --get Use GET request method.\n"" --head Use HEAD request method.\n"" --options Use OPTIONS request method.\n"" --trace Use TRACE request method.\n"" -?|-h|--help This information.\n"" -V|--version Display program version.\n");};int main(int argc, char *argv[]){int opt=0;int options_index=0;char *tmp=NULL;// 命令行参数不合法,输出使用手册if(argc==1){usage();return 2;}// 检查参数,使用getopt_longwhile((opt=getopt_long(argc, argv, "912Vfrt:p:c:?h", long_options, &options_index))!=EOF ){switch(opt){case 0 :break;case 'f':force=1;break;case 'r':force_reload=1;break;case '9':http10=0;break;case '1':http10=1;break;case '2':http10=2;break;case 'V':printf(PROGRAM_VERSION"\n");exit(0);case 't':benchtime=atoi(optarg);break;case 'p':/* proxy server parsing server:port */tmp=strrchr(optarg, ':');proxyhost=optarg;if(tmp==NULL){break;}if(tmp==optarg){fprintf(stderr, "Error in option --proxy %s: Missing hostname.\n", optarg);return 2;}if(tmp==optarg+strlen(optarg)-1){fprintf(stderr, "Error in option --proxy %s Port number is missing.\n", optarg);return 2;}*tmp='\0';proxyport=atoi(tmp+1);break;case ':':case 'h':case '?': usage();return 2;break;case 'c': clients=atoi(optarg);break;}}// optind为一个不包含选项的命令行参数,这里就是指URL值在参数表中的indexif(optind==argc) {fprintf(stderr, "webbench: Missing URL!\n");usage();return 2;}// 设置默认参数if(clients==0) clients=1;if(benchtime==0) benchtime=60;/* Copyright */// 输出copyrightfprintf(stderr, "Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n""Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n");build_request(argv[optind]);/* print bench info */// 打印测试的信息printf("\nBenchmarking: ");switch(method){case METHOD_GET:default:printf("GET");break;case METHOD_OPTIONS:printf("OPTIONS");break;case METHOD_HEAD:printf("HEAD");break;case METHOD_TRACE:printf("TRACE");break;}printf(" %s", argv[optind]);switch(http10){case 0: printf(" (using HTTP/0.9)");break;case 2: printf(" (using HTTP/1.1)");break;}printf("\n");if(clients==1)printf("1 client");elseprintf("%d clients", clients);printf(", running %d sec", benchtime);if(force)printf(", early socket close");if(proxyhost!=NULL)printf(", via proxy server %s:%d", proxyhost, proxyport);if(force_reload)printf(", forcing reload");printf(".\n");return bench();}void build_request(const char *url){// 对全局变量char request[REQUEST_SIZE]进行操作,根据URL构建http请求char tmp[10];int i;//字节清零bzero(host, MAXHOSTNAMELEN);bzero(request, REQUEST_SIZE);//确定http版本if(force_reload && proxyhost!=NULL && http10<1) http10=1;if(method==METHOD_HEAD && http10<1) http10=1;if(method==METHOD_OPTIONS && http10<2) http10=2;if(method==METHOD_TRACE && http10<2) http10=2;//根据请求,设置内容switch(method){default:case METHOD_GET:strcpy(request, "GET");break;case METHOD_HEAD:strcpy(request, "HEAD");break;case METHOD_OPTIONS:strcpy(request, "OPTIONS");break;case METHOD_TRACE:strcpy(request, "TRACE");break;}strcat(request, " ");// 检测URL内容是否合法if(NULL==strstr(url, "://")){fprintf(stderr, "\n%s: is not a valid URL.\n", url);exit(2);}// 检测URL长度是否合法if(strlen(url)>1500){fprintf(stderr, "URL is too long.\n");exit(2);}if(proxyhost==NULL)if (0!=strncasecmp("http://", url, 7)){//不使用代理服务器,只允许使用HTTP协议fprintf(stderr, "\nOnly HTTP protocol is directly supported, set --proxy for others.\n");exit(2);}/* protocol/host delimiter */// 获得://后面第一个字符的相对索引号i=strstr(url, "://")-url+3;/* printf("%d\n", i); */if(strchr(url+i, '/')==NULL) {// 没有其它的'/'符号,不合法fprintf(stderr, "\nInvalid URL syntax - hostname don't ends with '/'.\n");exit(2);}if(proxyhost==NULL){//不使用代理服务器, 必定是http协议/* get port from hostname */if( index(url+i, ':')!=NULL && index(url+i, ':')<index(url+i,'/') ){// 如果是 server:port的格式// 获取主机地址strncpy(host, url+i, strchr(url+i, ':')-url-i);bzero(tmp, 10);strncpy(tmp, index(url+i, ':')+1, strchr(url+i, '/')-index(url+i, ':')-1);/* printf("tmp=%s\n", tmp); */// 目标端口proxyport=atoi(tmp);if(proxyport==0)// 默认端口号为80proxyport=80;}else{// 获取主机地址strncpy(host, url+i, strcspn(url+i, "/"));}// printf("Host=%s\n", host);// 获取域名后的目标地址strcat(request+strlen(request), url+i+strcspn(url+i, "/"));}else{// printf("ProxyHost=%s\nProxyPort=%d\n", proxyhost, proxyport);// 使用了代理服务器strcat(request, url);}// 确定http版本号if(http10==1)strcat(request, " HTTP/1.0");else if (http10==2)strcat(request, " HTTP/1.1");// 换行strcat(request, "\r\n");//添加 User-Agent信息if(http10>0)strcat(request, "User-Agent: WebBench "PROGRAM_VERSION"\r\n");if(proxyhost==NULL && http10>0){// 如果http版本大于0.9,则添加Host信息strcat(request, "Host: ");strcat(request, host);strcat(request, "\r\n");}if(force_reload && proxyhost!=NULL){// 如果不使用缓存且有代理服务器,则不缓存strcat(request, "Pragma: no-cache\r\n");}if(http10>1)// 如果HTTP1.1,则存在长链接,应将Connection设置为closestrcat(request, "Connection: close\r\n");/* add empty line at end */if(http10>0) strcat(request, "\r\n"); //再添加换行// printf("Req=%s\n", request);}/* vraci system rc error kod */static int bench(void){int i, j, k;pid_t pid=0;FILE *f;/* check avaibility of target server */// host/proxyhost为服务器端ip, proxyport为服务器端口号建立socket连接i=Socket(proxyhost==NULL?host:proxyhost, proxyport);if(i<0) {// 出现错误fprintf(stderr, "\nConnect to server failed. Aborting benchmark.\n");return 1;}//对套接字i的引用计数-1,当引用计数为0的时候,关闭套接字close(i);/* create pipe */// 子进程向父进程回报数据if(pipe(mypipe)){perror("pipe failed.");return 3;}/* not needed, since we have alarm() in childrens *//* wait 4 next system clock tick *//*cas=time(NULL);while(time(NULL)==cas)sched_yield();*//* fork childs */// 通过fork来产生clients个子程序来进行测试for(i=0;i<clients;i++){pid=fork();if(pid <= (pid_t) 0){// fork返回了0,要么此时是子进程, 要么是产生了错误/* child process or error*/// 子进程休眠,动机不明sleep(1); /* make childs faster */break;}}if( pid< (pid_t) 0){// fork函数运行错误fprintf(stderr, "problems forking worker no. %d\n", i);perror("fork failed.");return 3;}if(pid== (pid_t) 0){// 子进程运行此处的代码/* I am a child *///进行压力测试if(proxyhost==NULL)benchcore(host, proxyport, request);elsebenchcore(proxyhost, proxyport, request);/* write results to pipe */// 向管道写数据f=fdopen(mypipe[1], "w");if(f==NULL){// 管道打开错误处理perror("open pipe for writing failed.");return 3;}/* fprintf(stderr, "Child - %d %d\n", speed, failed); */fprintf(f, "%d %d %d\n", speed, failed, bytes);fclose(f);return 0;}else{// 父进程运行此处的代码f=fdopen(mypipe[0], "r");if(f==NULL){perror("open pipe for reading failed.");return 3;}// 设置没有缓冲区setvbuf(f, NULL, _IONBF, 0);// 初始化speed=0;failed=0;bytes=0;while(1){// 通过f从管道读取数据// fscanf是阻塞式函数pid=fscanf(f, "%d %d %d", &i, &j, &k);if(pid<2){fprintf(stderr, "Some of our childrens died.\n");break;}// 统计数据speed+=i;failed+=j;bytes+=k;/* fprintf(stderr, "*Knock* %d %d read=%d\n", speed, failed, pid); */// 子进程数据读完后,退出if(--clients==0) break;}fclose(f);// 输出测试数据printf("\nSpeed=%d pages/min, %d bytes/sec.\nRequests: %d susceed, %d failed.\n",(int)((speed+failed)/(benchtime/60.0f)),(int)(bytes/(float)benchtime),speed,failed);}return i;}void benchcore(const char *host, const int port, const char *req){int rlen;char buf[1500];int s, i;struct sigaction sa;/* setup alarm signal handler */sa.sa_handler=alarm_handler; //设置alarm_handler为信号处理函数sa.sa_flags=0;// sigaction函数 成功会返回0,失败会返回-1// 设置超时将产生信息SIGALRM, 用sa的sa_handler函数处理if(sigaction(SIGALRM, &sa, NULL))exit(3);//开始计时alarm(benchtime);rlen = strlen(req);nexttry:while(1){if(timerexpired){// 超时返回if(failed>0){/* fprintf(stderr, "Correcting failed by signal\n"); */failed--;}return;}s=Socket(host, port);if(s<0){ // 连接失败, failed数加1failed++;continue;}if( rlen!=write(s, req, rlen) ){// 实际写入数据长度和预期写入数据长度不符,失败failed++;// 套接字引用计数-1close(s);continue;}if( http10 == 0 ) //对http0.9做的处理,不懂if( shutdown(s, 1) ){// 禁止在一个套接口上进行数据的接收与发送?failed++;close(s);continue;}if(force==0){// 等待读取服务器返回的数据/* read all available data from socket */while(1){//超时退出if(timerexpired)break;// 读取返回数据,i为读取到的字节数i=read(s, buf, 1500);/* fprintf(stderr, "%d\n", i); */if(i<0){// 读取文件失败,失败数加1,并对套接字引用计数减1failed++;close(s);// 跳回循环继续测试goto nexttry;}elseif(i==0)break;else// 成功读取的字节数增加bytes+=i;}}if(close(s)){// 若套接字引用计数变为0,则失败数减1failed++;continue;}// 成功次数+1speed++;}}
// socket.c/* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $** This module has been modified by Radim Kolar for OS/2 emx*//***********************************************************************module: socket.cprogram: popclientSCCS ID: @(#)socket.c 1.5 4/1/94programmer: Virginia Tech Computing Centercompiler: DEC RISC C compiler (Ultrix 4.1)environment: DEC Ultrix 4.3description: UNIX sockets code.***********************************************************************/#include <sys/types.h>#include <sys/socket.h>#include <fcntl.h>#include <netinet/in.h>#include <arpa/inet.h>#include <netdb.h>#include <sys/time.h>#include <string.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <stdarg.h>/*为了方便,把sockaddr_in定义放在这方便阅读struct sockaddr_in {short int sin_family; //协议族unsigned short int sin_port; //存储端口号struct in_addr sin_addr; //IP地址unsigned char sin_zero[8]; //作内存对齐用};*//*struct hostent {char *h_name; //主机的规范名char **h_aliases; //主机的别名int h_addrtype; //主机ip地址的类型int h_length; //主机ip地址的长度char **h_addr_list; //主机ip地址};*//*const char* host: 目标主机地址int clientPort: 主机地址的端口*/int Socket(const char *host, int clientPort){int sock;unsigned long inaddr;struct sockaddr_in ad;struct hostent *hp;memset(&ad, 0, sizeof(ad)); //初始化ad.sin_family = AF_INET; //协议族使用AF_INET,AF_INET是一种很常用的地址协议族inaddr = inet_addr(host); //将点分十进制转为整型数if (inaddr != INADDR_NONE) //如果host是一个合法IP地址memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr)); //将转换得到的IP地址复制到结构体ad的sin_addr中else{//否则,host可能是一个域名或主机名,或是非法名称hp = gethostbyname(host); //根据host获得IP地址if (hp == NULL) //获取失败,为非法名称return -1; //返回-1//获得了IP地址,复制到ad中(可能有多个IP地址,复制第一个)memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);}ad.sin_port = htons(clientPort); //转换字节顺序为高序字节//创建TCP套接字,系统自动推断用什么协议sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0)//创建失败return sock;if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)//三次握手失败则返回-1return -1;return sock;}
/* J. David's webserver *//* This is a simple webserver.* Created November 1999 by J. David Blackstone.* CSE 4344 (Network concepts), Prof. Zeigler* University of Texas at Arlington*//* This program compiles for Sparc Solaris 2.6.* To compile for Linux:* 1) Comment out the #include <pthread.h> line.* 2) Comment out the line that defines the variable newthread.* 3) Comment out the two lines that run pthread_create().* 4) Uncomment the line that runs accept_request().* 5) Remove -lsocket from the Makefile.*/#include <stdio.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <ctype.h>#include <strings.h>#include <string.h>#include <sys/stat.h>#include <pthread.h>#include <sys/wait.h>#include <stdlib.h>#define ISspace(x) isspace((int)(x))#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"void* accept_request(void *pclient); //处理从套接字上监听到的一个HTTP请求void bad_request(int); //返回给客户端这是个错误请求void cat(int, FILE *); //读取服务器上某个文件写到socket套接字void cannot_execute(int); //处理发生在执行cgi程序时出现的错误void error_die(const char *); //把错误信息写到perror并退出void execute_cgi(int, const char *, const char *, const char *); //运行cgi程序的处理int get_line(int, char *, int); //读取套接字的一行void headers(int, const char *); //把HTTP响应的头部写到套接字void not_found(int); //处理找不到请求的文件时的情况void serve_file(int, const char *); //调用cat把服务器文件返回给浏览器int startup(u_short *); //初始化httpd服务void unimplemented(int); //返回给浏览器表明收到的HTTP请求所用的method不被支持/**********************************************************************//* A request has caused a call to accept() on the server port to* return. Process the request appropriately.* Parameters: the socket connected to the client *//**********************************************************************/void* accept_request(void *pclient){int client = *(int*)pclient;char buf[1024];int numchars;char method[255];char url[255];char path[512];size_t i, j;struct stat st; //文件状态// 指定是cgi程序的标记int cgi = 0; /* becomes true if server decides this is a CGI* program */char *query_string = NULL;// 获得请求的第一行numchars = get_line(client, buf, sizeof(buf));i = 0;j = 0;//把请求方法存到method数组while (!ISspace(buf[j]) && (i < sizeof(method) - 1)){method[i] = buf[j];i++;j++;}// 结束标记method[i] = '\0';// 如果不是GET也不是POST,则无法处理if (strcasecmp(method, "GET") && strcasecmp(method, "POST")){unimplemented(client);return NULL;}// POST请求,开启cgiif (strcasecmp(method, "POST") == 0)cgi = 1;i = 0;// 读取 URL. j用于定位URL的位置,i作为储存url数组的indexwhile (ISspace(buf[j]) && (j < sizeof(buf)))// 略过空格字符j++;while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))){url[i] = buf[j];i++;j++;}url[i] = '\0';if (strcasecmp(method, "GET") == 0){// 处理GET methodquery_string = url;while ((*query_string != '?') && (*query_string != '\0'))query_string++;if (*query_string == '?'){// 开启cgicgi = 1;*query_string = '\0';query_string++;}}// 格式化url到path数组,html文件都在htdocs中sprintf(path, "htdocs%s", url);if (path[strlen(path) - 1] == '/')// 默认情况为访问index.htmlstrcat(path, "index.html");if (stat(path, &st) == -1) {// 找不到文件,则把所有的headers的信息都丢弃while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */numchars = get_line(client, buf, sizeof(buf));not_found(client); //发送寻找失败的信息}else{if ((st.st_mode & S_IFMT) == S_IFDIR)// 如果找到的是目录,访问目录下的index.htmlstrcat(path, "/index.html");if ((st.st_mode & S_IXUSR) ||(st.st_mode & S_IXGRP) ||(st.st_mode & S_IXOTH) )// 如果: 文件所有者具有可执行权限 或是 用户组具有可执行权限 或是 其它用户具有可执行权限,则设置cgicgi = 1;if (!cgi)// 不是cgi, 把服务器文件返回,否则执行cgiserve_file(client, path);else// 执行cgiexecute_cgi(client, path, method, query_string);}// 断开与客户端的链接,http具有无连接特点,每次连接只处理一个请求close(client);return NULL;}/**********************************************************************//* Inform the client that a request it has made has a problem.* Parameters: client socket *//**********************************************************************/void bad_request(int client){char buf[1024];// 回应客户端错误的HTTP请求sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");send(client, buf, sizeof(buf), 0);sprintf(buf, "Content-type: text/html\r\n");send(client, buf, sizeof(buf), 0);sprintf(buf, "\r\n");send(client, buf, sizeof(buf), 0);sprintf(buf, "<P>Your browser sent a bad request, ");send(client, buf, sizeof(buf), 0);sprintf(buf, "such as a POST without a Content-Length.\r\n");send(client, buf, sizeof(buf), 0);}/**********************************************************************//* Put the entire contents of a file out on a socket. This function* is named after the UNIX "cat" command, because it might have been* easier just to do something like pipe, fork, and exec("cat").* Parameters: the client socket descriptor* FILE pointer for the file to cat *//**********************************************************************/void cat(int client, FILE *resource){char buf[1024];// 读取文件中的所有数据写到socket中fgets(buf, sizeof(buf), resource);while (!feof(resource)){send(client, buf, strlen(buf), 0);fgets(buf, sizeof(buf), resource);}}/**********************************************************************//* Inform the client that a CGI script could not be executed.* Parameter: the client socket descriptor. *//**********************************************************************/void cannot_execute(int client){char buf[1024];// 回应客户端 cgi无法执行sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "Content-type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<P>Error prohibited CGI execution.\r\n");send(client, buf, strlen(buf), 0);}/**********************************************************************//* Print out an error message with perror() (for system errors; based* on value of errno, which indicates system call errors) and exit the* program indicating an error. *//**********************************************************************/void error_die(const char *sc){// 显示出错信息perror(sc);exit(1);}/**********************************************************************//* Execute a CGI script. Will need to set environment variables as* appropriate.* Parameters: client socket descriptor* path to the CGI script *//**********************************************************************/void execute_cgi(int client, const char *path,const char *method, const char *query_string){char buf[1024];int cgi_output[2];int cgi_input[2];pid_t pid;int status;int i;char c;int numchars = 1;int content_length = -1;buf[0] = 'A';buf[1] = '\0';if (strcasecmp(method, "GET") == 0)// GET请求下,把所有的HTTP header读取并丢弃while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */numchars = get_line(client, buf, sizeof(buf));else /* POST */{// 找出 content_lengthnumchars = get_line(client, buf, sizeof(buf));while ((numchars > 0) && strcmp("\n", buf)){// 使用 \0 进行分隔buf[15] = '\0';if (strcasecmp(buf, "Content-Length:") == 0)// 找到Content-Length并且处理content_length = atoi(&(buf[16]));numchars = get_line(client, buf, sizeof(buf));}if (content_length == -1) {// 找不到Content-Length,发送请求错误信息bad_request(client);return;}}// HTTP 状态码 200sprintf(buf, "HTTP/1.0 200 OK\r\n");send(client, buf, strlen(buf), 0);// 建立管道if (pipe(cgi_output) < 0) {// 错误处理cannot_execute(client);return;}if (pipe(cgi_input) < 0) {cannot_execute(client);return;}// 建立一个进程进行处理if ( (pid = fork()) < 0 ) {cannot_execute(client);return;}if (pid == 0) /* child: CGI script */{// 子进程处理CGI部分char meth_env[255];char query_env[255];char length_env[255];// 把 stdout 重定向到 cgi_output的写入端dup2(cgi_output[1], 1);// 把 stdin 重定向到 cgi_input的读取端dup2(cgi_input[0], 0);// 关闭cgi_input的写入端和cgi_output的读取端close(cgi_output[0]);close(cgi_input[1]);// 设置request_method的环境变量sprintf(meth_env, "REQUEST_METHOD=%s", method);putenv(meth_env);if (strcasecmp(method, "GET") == 0) {// 设置 query_string 的环境变量sprintf(query_env, "QUERY_STRING=%s", query_string);putenv(query_env);}else { /* POST */// 设置 content_length的环境变量sprintf(length_env, "CONTENT_LENGTH=%d", content_length);putenv(length_env);}// 用execl运行cgi程序execl(path, query_string, NULL);exit(0);}else{ /* parent */// 父进程处理部分,关闭cgi_output的写入端和cgi_input的读取端close(cgi_output[1]);close(cgi_input[0]);if (strcasecmp(method, "POST") == 0)// 接收POST过来的数据for (i = 0; i < content_length; i++) {recv(client, &c, 1, 0);// 把POST数据写入cgi_input(重定向到stdin)write(cgi_input[1], &c, 1);}// 读取cgi_output的管道输出到客户端(管道的输入是stdin)while (read(cgi_output[0], &c, 1) > 0)send(client, &c, 1, 0);// 关闭管道close(cgi_output[0]);close(cgi_input[1]);// 等待子进程waitpid(pid, &status, 0);}}/**********************************************************************//* Get a line from a socket, whether the line ends in a newline,* carriage return, or a CRLF combination. Terminates the string read* with a null character. If no newline indicator is found before the* end of the buffer, the string is terminated with a null. If any of* the above three line terminators is read, the last character of the* string will be a linefeed and the string will be terminated with a* null character.* Parameters: the socket descriptor* the buffer to save the data in* the size of the buffer* Returns: the number of bytes stored (excluding null) *//**********************************************************************//*int sock: 套接字描述符char *buf: 用于储存收到数据的缓冲区int size: 缓冲区大小*/int get_line(int sock, char *buf, int size){int i = 0;char c = '\0';int n;while ((i < size - 1) && (c != '\n')){// 一次接受一个字节n = recv(sock, &c, 1, 0);/* DEBUG printf("%02X\n", c); */if (n > 0){// n大于0说明收到了数据(收到一个字节)if (c == '\r'){// 如果收到'\r',则再接受一个字节,用于处理换行符为\r\n的情况n = recv(sock, &c, 1, MSG_PEEK); //MSG_PEEK标记用于防止读取成功后从tcp buffer中删除已读取的数据/* DEBUG printf("%02X\n", c); */if ((n > 0) && (c == '\n'))// 读取recv(sock, &c, 1, 0);else// 使得buf统一以\n结尾c = '\n';}// 把收到的字节储存起来buf[i] = c;i++; //收到一个字节累加一次}elsec = '\n'; //统一让buf数组以\n结尾}buf[i] = '\0';return(i); // 返回收到并存储的字节数}/**********************************************************************//* Return the informational HTTP headers about a file. *//* Parameters: the socket to print the headers on* the name of the file *//**********************************************************************/void headers(int client, const char *filename){char buf[1024];(void)filename; /* could use filename to determine file type */// 发送http头strcpy(buf, "HTTP/1.0 200 OK\r\n");send(client, buf, strlen(buf), 0);strcpy(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);strcpy(buf, "\r\n");send(client, buf, strlen(buf), 0);}/**********************************************************************//* Give a client a 404 not found status message. *//**********************************************************************/void not_found(int client){char buf[1024];// 404页面sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<BODY><P>The server could not fulfill\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "your request because the resource specified\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "is unavailable or nonexistent.\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</BODY></HTML>\r\n");send(client, buf, strlen(buf), 0);}/**********************************************************************//* Send a regular file to the client. Use headers, and report* errors to client if they occur.* Parameters: a pointer to a file structure produced from the socket* file descriptor* the name of the file to serve *//**********************************************************************/void serve_file(int client, const char *filename){FILE *resource = NULL;int numchars = 1;char buf[1024];buf[0] = 'A';buf[1] = '\0';// 读取并丢弃headerwhile ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */numchars = get_line(client, buf, sizeof(buf));resource = fopen(filename, "r");// 打开server文件爱你if (resource == NULL)not_found(client);else{// 写http头并且复制文件headers(client, filename);cat(client, resource);}fclose(resource);}/**********************************************************************//* This function starts the process of listening for web connections* on a specified port. If the port is 0, then dynamically allocate a* port and modify the original port variable to reflect the actual* port.* Parameters: pointer to variable containing the port to connect on* Returns: the socket *//**********************************************************************/int startup(u_short *port){int httpd = 0;struct sockaddr_in name;// 建立sockethttpd = socket(PF_INET, SOCK_STREAM, 0);if (httpd == -1)// 建立失败error_die("socket");//初始化memset(&name, 0, sizeof(name));name.sin_family = AF_INET;name.sin_port = htons(*port);name.sin_addr.s_addr = htonl(INADDR_ANY);if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)error_die("bind");if (*port == 0) /* if dynamically allocating a port */{// 动态随机分配一个端口socklen_t namelen = sizeof(name);if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)error_die("getsockname");*port = ntohs(name.sin_port);}//监听if (listen(httpd, 5) < 0)error_die("listen");// 返回socket idreturn(httpd);}/**********************************************************************//* Inform the client that the requested web method has not been* implemented.* Parameter: the client socket *//**********************************************************************/void unimplemented(int client){char buf[1024];*buf = '\0' //方便进行strcat// HTTP method 不被支持strcat(buf, "HTTP/1.0 501 Method Not Implemented\r\n");strcat(buf, SERVER_STRING);strcat(buf, "Content-Type: text/html\r\n\r\n<HTML><HEAD><TITLE>Method Not Implemented\r\n</TITLE></HEAD>\r\n<BODY><P>HTTP request method not supported.\r\n</BODY></HTML>\r\n");printf("%s\n",buf);send(client, buf, strlen(buf), 0);/*sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");send(client, buf, strlen(buf), 0);// 输出服务器信息到网页上sprintf(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</TITLE></HEAD>\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</BODY></HTML>\r\n");send(client, buf, strlen(buf), 0);*/}/**********************************************************************/int main(void){int server_sock = -1;u_short port = 0; //端口为0int client_sock = -1;struct sockaddr_in client_name;socklen_t client_name_len = sizeof(client_name);pthread_t newthread;server_sock = startup(&port); //服务器端监听套接字设置printf("httpd running on port %d\n", port);while (1){// 主线程阻塞等待客户端请求client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);if (client_sock == -1)error_die("accept");/* accept_request(client_sock); */// 监听到请求,则创建工作线程.if (pthread_create(&newthread , NULL, accept_request, (void*)&client_sock) != 0)perror("pthread_create");}// 关闭套接字,关闭TCP连接close(server_sock);return(0);}