#CentOS on Mac#4.WebBench测试TinyHttpd

来源:互联网 发布:大数据具体应用 编辑:程序博客网 时间:2024/06/08 04:57

早有听闻WebBench和TinyHttpd是工程师不得不了解的十大优秀C语言开源项目
出乎我意料的是,两个项目的代目都十分精简约在400行左右,而且稳定性极高,将近十年未更新

任务:
1.编译运行WebBench并了解注释WebBench源代码
2.编译运行TinyHttpd并了解注释TinyHttpd源代码
3.用编译的WebBench测试编译的TinyHttpd

那就先说WebBench咯~

WebBench
源码在此
http://home.tiscali.cz/~cz210552/webbench.html

编译运行
步骤如下
1.在网站上下载源代码
wget http://home.tiscali.cz/~cz210552/distfiles/webbench-1.5.tar.gz

这里写图片描述

2.解压
tar zxvf webbench-1.5.tar.gz
//tinyhttpd同理

这里写图片描述

3.

cd webbench-1.5

4.make文件

make

这里写图片描述
(1)
出现找不到ctags或gcc

yum install ctags

这里写图片描述
5.

make install

(2)error 1

mkdir -m 644 -p /usr/local/man/man1

这里写图片描述
WebBench使用

webbench -c 1000 -t 60 http://www.baidu.com/index.html

webbench -c 并发数 -t 运行测试时间 URL

x可以直接输3w或者IP地址
通过ping获取IP地址

这里写图片描述

这里写图片描述

开始测试baidu服务器的抗压性能
注意IP地址跟域名是一样的意思

咱们先试一下100个用户30s测试

这里写图片描述

可以看出全都成功了

1000个用户30s测试

这里写图片描述

=v=结果如图

100个用户60s测试
这里写图片描述

10%左右的成功率。。

前方高能WebBench400多行的
!!!源码分析!!!!

//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.c  program:      popclient  SCCS ID:      @(#)socket.c    1.5  4/1/94  programmer:   Virginia Tech Computing Center  compiler:     DEC RISC C compiler (Ultrix 4.1)  environment:  DEC Ultrix 4.3  description:  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>//连接类型为TCP,使用IPv4网域 //host为服务器ip,clientPort建立socket连接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;    inaddr = inet_addr(host);    if (inaddr != INADDR_NONE)        memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));    else    {        hp = gethostbyname(host); //域名获取IP        if (hp == NULL)            return -1;        memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);    }    ad.sin_port = htons(clientPort);    sock = socket(AF_INET, SOCK_STREAM, 0);    if (sock < 0)        return sock;    if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)        return -1;  //出错返回-1    return sock;   //正常连接,返回socket连接符}

------------------------

//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; /* 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; //方法int clients=1;  //并发数int force=0; //等待服务器应答int force_reload=0;int proxyport=80;  //代理服务器端口char *proxyhost=NULL; //代理服务器地址int benchtime=30; //运行时间/* internal */int mypipe[2];char host[MAXHOSTNAMELEN];#define REQUEST_SIZE 2048char request[REQUEST_SIZE];//方法选择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){   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; } while((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;       //命令后的参数,atoi字符转换长整型   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参数下标 if(optind==argc) {                      fprintf(stderr,"webbench: Missing URL!\n");     usage();     return 2;                    } if(clients==0) clients=1; if(benchtime==0) benchtime=60; /* Copyright */ fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n""Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n");//optind为URL位置 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"); else   printf("%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();}//创建URL请求连接void build_request(const char *url){  char tmp[10];  int i;//请求地址和连接清零  bzero(host,MAXHOSTNAMELEN);  bzero(request,REQUEST_SIZE);  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," ");  if(NULL==strstr(url,"://"))  { fprintf(stderr, "\n%s: is not a valid URL.\n",url); exit(2);  }  if(strlen(url)>1500) //太长出错  {         fprintf(stderr,"URL is too long.\n");exit(2);  }  if(proxyhost==NULL) //代理服务器为空  if (0!=strncasecmp("http://",url,7))  { fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n");             exit(2);           }  /* protocol/host delimiter */  i=strstr(url,"://")-url+3; //i指向有用地址  /* 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)  {   /* get port from hostname */   if(index(url+i,':')!=NULL &&      index(url+i,':')<index(url+i,'/'))   {  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) proxyport=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);  }  if(http10==1) strcat(request," HTTP/1.0");  else if (http10==2) strcat(request," HTTP/1.1");  strcat(request,"\r\n");  if(http10>0) strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n");  if(proxyhost==NULL && http10>0)  { 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) strcat(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 */  i=Socket(proxyhost==NULL?host:proxyhost,proxyport);  if(i<0) {  fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n");           return 1;         }  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 */  for(i=0;i<clients;i++)  {  pid=fork();  if(pid <= (pid_t) 0)  {  /* child process or error*/          sleep(1); /* make childs faster */  break;  }  }  if( pid< (pid_t) 0)  {          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);         else      benchcore(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)//父进程读取管道数据,并做加法 { 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; //定时器方法 sa.sa_flags=0; if(sigaction(SIGALRM,&sa,NULL))    exit(3); alarm(benchtime); rlen=strlen(req); nexttry:while(1) {    if(timer expired)//超时    {       if(failed>0)       {          /* fprintf(stderr,"Correcting failed by signal\n"); */          failed--;       }       return;    }    s=Socket(host,port);      //创建链接                        if(s<0) { failed++;continue;} //连接失败    if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;}    if(http10==0)   if(shutdown(s,1)) { failed++;close(s);continue;}//发送失败    if(force==0) //读取http请求数据    {            /* read all available data from socket */   while(1)   {              if(timerexpired) break;     i=read(s,buf,1500);              /* fprintf(stderr,"%d\n",i); */     if(i<0)              {                 failed++;                 close(s);                 goto nexttry;              }      else      if(i==0) break;      else      bytes+=i;   }    }    if(close(s)) {failed++;continue;}    speed++; //测试成功 }}

参考
http://www.cnblogs.com/xuning/p/3888699.html

接下来说一说TinyHttpd~

TinyHttpd
https://sourceforge.net/projects/tinyhttpd/files/latest/download

源码在此 tinyhttpd-0.1.0.tar.gz

编译运行
步骤如下
1.在网站上下载源代码

wget http://jaist.dl.sourceforge.net/project/tinyhttpd/tinyhttpd%20source/tinyhttpd%200.1.0/tinyhttpd-0.1.0.tar.gz

这里写图片描述

2.解压

tar  zxvf tinyhttpd-0.1.0.tar.gz

这里写图片描述

3.

cd tinyhttpd-0.1.0

4.make文件

make

高能出现错误!

这里写图片描述

这里写图片描述

原因分析!

TinyHttpd本来是在solaris上实现的,在socket和pthread的实现上和一般的Linux不一样。httpd.c开始的英文注释做出了对程序修改的详细指引

故(1)修改httpd.c根据开头注释

httpd.c/* 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 *);void bad_request(int);void cat(int, FILE *);void cannot_execute(int);void error_die(const char *);void execute_cgi(int, const char *, const char *, const char *);int get_line(int, char *, int);void headers(int, const char *);void not_found(int);void serve_file(int, const char *);int startup(u_short *);void unimplemented(int);/**********************************************************************//* 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 * tclient){ int client = *(int *)tclient; char buf[1024]; int numchars; char method[255]; char url[255]; char path[512]; size_t i, j; struct stat st; 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;//客户端请求方法存到数组 while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) {  method[i] = buf[j];  i++; j++; } method[i] = '\0'; if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) {  unimplemented(client);  return NULL; }//出错//post开启cgi if (strcasecmp(method, "POST") == 0)  cgi = 1;//读取url地址 i = 0; while (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) {//待处理请求为url  query_string = url;  while ((*query_string != '?') && (*query_string != '\0'))   query_string++;  if (*query_string == '?')  {   cgi = 1;   *query_string = '\0';   query_string++;  } }//格式化urlpath数组 sprintf(path, "htdocs%s", url); if (path[strlen(path) - 1] == '/')  strcat(path, "index.html”); //默认 if (stat(path, &st) == -1) { //路径找文件  while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */   numchars = get_line(client, buf, sizeof(buf));  not_found(client); //找不到 } else {//目录 默认index  if ((st.st_mode & S_IFMT) == S_IFDIR)   strcat(path, "/index.html");  if ((st.st_mode & S_IXUSR) ||      (st.st_mode & S_IXGRP) ||      (st.st_mode & S_IXOTH)    )   cgi = 1;  if (!cgi) //非cgi,返回服务器文件   serve_file(client, path);  else   execute_cgi(client, path, method, query_string); }//断开 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];//错误请求 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];//无法执行 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;//所有http首部读取丢弃 buf[0] = 'A'; buf[1] = '\0'; if (strcasecmp(method, "GET") == 0)  while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */   numchars = get_line(client, buf, sizeof(buf)); else    /* POST */ {//在post的http请求中找出cotent_length  numchars = get_line(client, buf, sizeof(buf));  while ((numchars > 0) && strcmp("\n", buf))  {   buf[15] = '\0';   if (strcasecmp(buf, "Content-Length:") == 0)    content_length = atoi(&(buf[16]));   numchars = get_line(client, buf, sizeof(buf));  }//没找到  if (content_length == -1) {   bad_request(client);   return;  } }//http状态码200 sprintf(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 */ {  char meth_env[255];  char query_env[255];  char length_env[255];//把stdout重定向到写入端  dup2(cgi_output[1], 1);//stdin同理  dup2(cgi_input[0], 0);  close(cgi_output[0]);  close(cgi_input[1]);//设置环境变量  sprintf(meth_env, "REQUEST_METHOD=%s", method);  putenv(meth_env);  if (strcasecmp(method, "GET") == 0) {//设置环境变量   sprintf(query_env, "QUERY_STRING=%s", query_string);   putenv(query_env);  }  else {   /* POST */   sprintf(length_env, "CONTENT_LENGTH=%d", content_length);   putenv(length_env);  }//运行cgi程序  execl(path, path, NULL);  exit(0); } else {    /* parent */  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数据写入    write(cgi_input[1], &c, 1);   }//读取管道输出到客户端  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 get_line(int sock, char *buf, int size){ int i = 0; char c = '\0'; int n;//标准化buf数组 while ((i < size - 1) && (c != '\n')) {  n = recv(sock, &c, 1, 0);  /* DEBUG printf("%02X\n", c); */  if (n > 0)  {//继续接受   if (c == '\r')   {//接收窗口不划动    n = recv(sock, &c, 1, MSG_PEEK);    /* DEBUG printf("%02X\n", c); */    if ((n > 0) && (c == '\n'))     recv(sock, &c, 1, 0);//吸收换行符    else     c = '\n';   }   buf[i] = c;   i++;  }  else   c = '\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){//正常http首部 char buf[1024]; (void)filename;  /* could use filename to determine file type */ 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]; 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];//读取丢弃header buf[0] = 'A'; buf[1] = '\0'; while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */  numchars = get_line(client, buf, sizeof(buf));//打开文件 resource = fopen(filename, "r"); if (resource == NULL)  not_found(client); else {//写首部,复制文件  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;//建立socket httpd = 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"); return(httpd);}/**********************************************************************//* Inform the client that the requested web method has not been * implemented. * Parameter: the client socket *//**********************************************************************/void unimplemented(int client){//method不被支持 char buf[1024]; 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; int client_sock = -1; struct sockaddr_in client_name; socklen_t client_name_len = sizeof(client_name); pthread_t newthread;//建立httpd服务 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"); } close(server_sock); return(0);}

(2)在makefile文件中改为

gcc -W -Wall -o httpd httpd.c -lpthread

make完结果

这里写图片描述

TinyHttpd使用
1.运行应用

./httpd

这里写图片描述

得到端口号为52870

2.在浏览器firefox中输入地址

http://localhost:52870

这里写图片描述

TinyHttpd源码分析

1.函数功能如下

 accept_request:  处理从套接字上监听到的一个 HTTP 请求,在这里可以很大一部分地体现服务器处理请求流程。 bad_request: 返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST. cat: 读取服务器上某个文件写到 socket 套接字。 cannot_execute: 主要处理发生在执行 cgi 程序时出现的错误。 error_die: 把错误信息写到 perror 并退出。 execute_cgi: 运行 cgi 程序的处理,也是个主要函数。 get_line: 读取套接字的一行,把回车换行等情况都统一为换行符结束。 headers: 把 HTTP 响应的头部写到套接字。 not_found: 主要处理找不到请求的文件时的情况。 sever_file: 调用 cat 把服务器文件返回给浏览器。 startup: 初始化 httpd 服务,包括建立套接字,绑定端口,进行监听等。 unimplemented: 返回给浏览器表明收到的 HTTP 请求所用的 method 不被支持。

2.注释详细如上

[综合]WebBench测试TinyHttpd
基本如上
1.用TinyHttpd小型web服务器
这里写图片描述

2.用WebBench对地址进行测试
这里写图片描述

出现错误

全部failed且TinyHttpd自动断开

开始以为并发数的问题,减小后并未改善

这里写图片描述

网上查阅资料后

解决方法

(1)原因

因为WebBench在一次访问完之后就断掉了,但是TinyHttpd要分次把一个网页的内容发送给WebBench,所以第一次发的时候是成功的,第二次就失败了。而且TinyHttpd在send的时候没有异常判断和处理,所以程序卡死

(2)解决
httpd.c的unimplemented中将后面的14行代码发送注释掉

void unimplemented(int client){ char buf[1024]; 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);*/}

3.make重新编译

4.运行

./httpd

运行结果全部成功

这里写图片描述

[总结]
小小的预告!
下一篇会详细解释最后最综合的网络实验,从一开始搜bing到有时候科学上网google,再到后面看cnblog、csdn找到有用的答案或错误分享解决方案,到逐渐从github找到大神。

结论:最make sense的还是开发者的README.md,自己编的程序自己最清楚嘛,次之是github的大神或者stackflow。对英文有一定要求。至于bing、google搜出来的答案当然也会有用的但是鱼龙混杂,能找到有帮助但却浪费了一定时间。

0 0