@RunZhi
2016-09-14T00:14:14.000000Z
字数 28364
阅读 1358
操作系统实验报告
1.编译并运行WebBench,测试baidu服务器的性能
2.熟读并理解WebBench源码,将每一行程序的理解注释加入该任务四的实验报告中,要求注释偏口语化.
3.编译并运行TinyHttpd,并用浏览器访问该小型web服务器
4.熟读并理解TinyHttpd的源码,将每一行程序的理解注释加入该任务四的实验报告中,要求注释偏口语化
5.
将自己编译的WebBench程序去测试自己编译的TinyHttpd程序,看看会发生什么“现象”?
请分析:
A) 为什么会发生这个“现象”?
B) 是谁的问题导致该现象的发生?
C) 请修复该问题.
下载webbench源代码并且运行程序:
sudo make
sudo 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; //并发数量,默认为1
int force = 0; //0表示等待读取从服务器返回的数据,1表示不等待
int force_reload = 0; //0表示缓存,1表示不缓存
int proxyport = 80; //代理服务器的端口号
char *proxyhost = NULL; //代理服务器的ip
int benchtime = 30; //压力测试时间,默认为30秒
/* internal */
int mypipe[2]; //使用管道进行父进程与子进程的通信
char host[MAXHOSTNAMELEN]; //服务器端的ip
#define REQUEST_SIZE 2048
char 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_long
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;
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值在参数表中的index
if(optind==argc) {
fprintf(stderr, "webbench: Missing URL!\n");
usage();
return 2;
}
// 设置默认参数
if(clients==0) clients=1;
if(benchtime==0) benchtime=60;
/* Copyright */
// 输出copyright
fprintf(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");
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();
}
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)
// 默认端口号为80
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);
}
// 确定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设置为close
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 */
// 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);
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)
{
// 通过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数加1
failed++;
continue;
}
if( rlen!=write(s, req, rlen) )
{
// 实际写入数据长度和预期写入数据长度不符,失败
failed++;
// 套接字引用计数-1
close(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,并对套接字引用计数减1
failed++;
close(s);
// 跳回循环继续测试
goto nexttry;
}
else
if(i==0)
break;
else
// 成功读取的字节数增加
bytes+=i;
}
}
if(close(s))
{
// 若套接字引用计数变为0,则失败数减1
failed++;
continue;
}
// 成功次数+1
speed++;
}
}
// 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>
/*
为了方便,把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)
//三次握手失败则返回-1
return -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请求,开启cgi
if (strcasecmp(method, "POST") == 0)
cgi = 1;
i = 0;
// 读取 URL. j用于定位URL的位置,i作为储存url数组的index
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)
{
// 处理GET method
query_string = url;
while ((*query_string != '?') && (*query_string != '\0'))
query_string++;
if (*query_string == '?')
{
// 开启cgi
cgi = 1;
*query_string = '\0';
query_string++;
}
}
// 格式化url到path数组,html文件都在htdocs中
sprintf(path, "htdocs%s", url);
if (path[strlen(path) - 1] == '/')
// 默认情况为访问index.html
strcat(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.html
strcat(path, "/index.html");
if ((st.st_mode & S_IXUSR) ||
(st.st_mode & S_IXGRP) ||
(st.st_mode & S_IXOTH) )
// 如果: 文件所有者具有可执行权限 或是 用户组具有可执行权限 或是 其它用户具有可执行权限,则设置cgi
cgi = 1;
if (!cgi)
// 不是cgi, 把服务器文件返回,否则执行cgi
serve_file(client, path);
else
// 执行cgi
execute_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_length
numchars = 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 状态码 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 */
{
// 子进程处理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++; //收到一个字节累加一次
}
else
c = '\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';
// 读取并丢弃header
while ((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;
// 建立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");
// 返回socket id
return(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; //端口为0
int 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);
}