工作流程:

1>服务器启动,在指定端口或随机选取端口绑定httpd服务。

2>收到一个http请求时(其实就是listen端口accept的时候),派生一个线程运行accept_request函数。

3>取出http请求中method(get或post)和url,对于get方法,如果有携带参数,则query_string指针指向url中?后面的get参数。

4>格式化url到path数组,表示浏览器请求的文件路径,在tinyhttpd中服务器文件是在htdocs文件夹下。当url以/结尾,或者url是个目录,则默认在path中加上index.thml,表示访问主页。

5>如果文件路径合法,对于无参数的get请求,直接输出服务器文件到浏览器,即用http格式写到套接字上,跳到(10)。其他情况(带参数get,post方法,url为科执行文件),则调用execute_cgi函数执行cgi脚本。

6>读取整个http请求并丢弃,如果是post则找出content-length,把http状态码200写到套接字里面。

7>建立两个管道,cgi_input和cgi_output,并fork一个子进程。

8>在子进程中,把stdout重定向到cgi_output的写入端,把stdin重定向到cgi_input的读取端,关闭cgi_input的写入端和cgi_output的读取端,是指request_method的环境变量,get的话设置query_string的环境变量,post的话设置content-length的环境变量,这些环境变量都是为了给cgi脚本调用,接着用execl运行cgi程序。

9>在父进程中,关闭cgi_input的读取端和cgi_output的写入端,如果post的话,把post数据写入到cgo_input,已被重定向到stdin读取cgi_output的管道输出到客户端,等待子进程结束。

10>关闭与浏览器的链接,完成一次http请求与回应,因为http是无连接的。

 /* 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(int);
//处理从套接字上监听到的一个HTTP请求,在这里可以很大一部分的体现服务器处理请求的流程
void bad_request(int);
//返回给客户端这是个错误请求,HTTP状态码是400 BAD REQUEST
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(int client)
{ char buf[];
int numchars;
char method[];
char url[];
char path[];
size_t i, j;
struct stat st;
int cgi = ; /* becomes true if server decides this is a CGI
* program */
char *query_string = NULL; numchars = get_line(client, buf, sizeof(buf));
//读取 client端发送的数据并且 返回的参数是 numchars
i = ;
j = ;
while (!ISspace(buf[j]) && (i < sizeof(method) - ))
{
method[i] = buf[j];
i++;
j++;
}
//得到传递的参数是post 还是get方法 还是其他
method[i] = '\0'; if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
{
//回给浏览器表明收到的 HTTP 请求所用的 method 不被支持
unimplemented(client);
return;
} if (strcasecmp(method, "POST") == )
cgi = ; i = ;
//http请求行格式是 method urI http-version
while (ISspace(buf[j]) && (j < sizeof(buf)))
j++;
while (!ISspace(buf[j]) && (i < sizeof(url) - ) && (j < sizeof(buf)))
{
url[i] = buf[j]; //包含请求行中的URI
i++;
j++;
}
url[i] = '\0';
///获取发送数据中的URI
if (strcasecmp(method, "GET") == )
{
//get方法 是将参数传递在URI中的?后面如果元素多用&进行链接
query_string = url;
while ((*query_string != '?') && (*query_string != '\0'))
query_string++;
// get 请求? 后面为参数
if (*query_string == '?')
{
cgi = ;
*query_string = '\0';
query_string++;
}
}
//格式url存储在path数组 并且html存储在 htdocs文件中
sprintf(path, "htdocs%s", url);
if (path[strlen(path) - ] == '/')
strcat(path, "index.html"); //字符串进行衔接 + index.html
//stat函数 根据path路径 获取文件内容 存储在 st 结构体中 成功返回0 错误返回-1
if (stat(path, &st) == -)
{
while ((numchars > ) && 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)
strcat(path, "/index.html");
//文件的权限 属主 属组 其它 三种任一个拥有执行权
if ((st.st_mode & S_IXUSR) ||
(st.st_mode & S_IXGRP) ||
(st.st_mode & S_IXOTH) )
cgi = ;
//调用cat 把服务器文件返回给浏览器 post方法或者拥有执行权限
if (!cgi)
serve_file(client, path);
else
execute_cgi(client, path, method, query_string)
//运行cgi程序的处理,也是个主要函数
} close(client);
} /**********************************************************************/
/* Inform the client that a request it has made has a problem.
* Parameters: client socket */
/**********************************************************************/
void bad_request(int client)
{
char buf[]; sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
send(client, buf, sizeof(buf), );
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, sizeof(buf), );
sprintf(buf, "\r\n");
send(client, buf, sizeof(buf), );
sprintf(buf, "<P>Your browser sent a bad request, ");
send(client, buf, sizeof(buf), );
sprintf(buf, "such as a POST without a Content-Length.\r\n");
send(client, buf, sizeof(buf), );
} /**********************************************************************/
/* 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 */
/**********************************************************************/
//读取服务器上的某个文件 写到socket套接字上
void cat(int client, FILE *resource)
{
char buf[]; fgets(buf, sizeof(buf), resource);
//检测流上的文件结束符 如果文件结束,则返回非0值,否则返回0,文件结束符只能被clearerr()清除。
while (!feof(resource))
{
send(client, buf, strlen(buf), );
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[]; sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
send(client, buf, strlen(buf), );
} /**********************************************************************/
/* 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();
} /**********************************************************************/
/* Execute a CGI script. Will need to set environment variables as
* appropriate.
* Parameters: client socket descriptor
* path to the CGI script */
/**********************************************************************/ //运行cgi程序 也是个主函数
void execute_cgi(int client, const char *path,
const char *method, const char *query_string)
{
//在父进程中,关闭cgi_input的读入端和cgi_output的写入端,如果post的话
//把post数据写入到cgi_input,已被重定向到stdin,读取cgi_output的管道
//输出到客户端,该管道输入是stdout,接着关闭所有管道,等待子进程结束。 char buf[];
int cgi_output[];
int cgi_input[];
//cgi_output[1] cgi_input[1] 为输入端
//cgi_input[0] cgi_output[0] 为输出端
pid_t pid;
int status;
int i;
char c;
int numchars = ;
int content_length = -; buf[] = 'A';
buf[] = '\0';
//读入请求头
if (strcasecmp(method, "GET") == )
while ((numchars > ) && strcmp("\n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof(buf));
else /* POST */
{
numchars = get_line(client, buf, sizeof(buf));
while ((numchars > ) && strcmp("\n", buf))
{
buf[] = '\0';
if (strcasecmp(buf, "Content-Length:") == )
content_length = atoi(&(buf[]));
numchars = get_line(client, buf, sizeof(buf));
}
if (content_length == -)
{
bad_request(client);
return;
}
} sprintf(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), ); if (pipe(cgi_output) < )
{
cannot_execute(client);
return;
}
if (pipe(cgi_input) < )
{
cannot_execute(client);
return;
} if ( (pid = fork()) < )
{
cannot_execute(client);
return;
}
if (pid == ) /* child: CGI script */
{
char meth_env[];
char query_env[];
char length_env[];
//把stdout重定向到cgi_output的写入端
dup2(cgi_output[], );
//把stdin重定向到cgi_input的读入端
dup2(cgi_input[], );
//关闭cgi_output的读入端 和 cgi_input的 写入端
close(cgi_output[]);
close(cgi_input[]);
//设置request_method的环境变量
sprintf(meth_env, "REQUEST_METHOD=%s", method);
putenv(meth_env);
if (strcasecmp(method, "GET") == )
{
//设置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, path, NULL);
exit();
}
else /* parent */
{
close(cgi_output[]);
close(cgi_input[]);
//关闭cgi_output的 写入端 和 cgi_input的读取端
if (strcasecmp(method, "POST") == )
//接受post的数据
for (i = ; i < content_length; i++)
{
recv(client, &c, , );
write(cgi_input[], &c, );
//讲post数据写入cgi_input,并且重定向到stdin中
}
//读取cgi_output的管道输出到客户端,该管道输入时stdout
while (read(cgi_output[], &c, ) > )
send(client, &c, , );
//关闭管道
close(cgi_output[]);
close(cgi_input[]);
//等待子进程
waitpid(pid, &status, );
}
} /**********************************************************************/
/* 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 = ;
char c = '\0';
int n; while ((i < size - ) && (c != '\n'))
{
//每次接受一个字符
n = recv(sock, &c, , );
/* DEBUG printf("%02X\n", c); */
if (n > )
{
if (c == '\r')
{
n = recv(sock, &c, , MSG_PEEK);
/* DEBUG printf("%02X\n", c); */
if ((n > ) && (c == '\n'))
recv(sock, &c, , );
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 */
/**********************************************************************/
//http 响应体
void headers(int client, const char *filename)
{
char buf[];
(void)filename; /* could use filename to determine file type */ strcpy(buf, "HTTP/1.0 200 OK\r\n");//响应的状态行
send(client, buf, strlen(buf), );
strcpy(buf, SERVER_STRING);
send(client, buf, strlen(buf), );
sprintf(buf, "Content-Type: text/html\r\n"); //响应头
send(client, buf, strlen(buf), );
strcpy(buf, "\r\n"); //响应正文段
send(client, buf, strlen(buf), );
} /**********************************************************************/
/* Give a client a 404 not found status message. */
/**********************************************************************/
void not_found(int client)
{
//客户端发送的请求无法实现
char buf[];
//响应行
sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), );
//响应头
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "\r\n");
//响应头和响应正文段之间有一个空行 表示响应行结束
send(client, buf, strlen(buf), );
//响应正文段 text/html
sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "your request because the resource specified\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "is unavailable or nonexistent.\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "</BODY></HTML>\r\n");
send(client, buf, strlen(buf), );
} /**********************************************************************/
/* 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 = ;
char buf[]; buf[] = 'A';
buf[] = '\0';
while ((numchars > ) && 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 allo //响应正文段cate 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 = ;
struct sockaddr_in name;
//建立套接字
httpd = socket(PF_INET, SOCK_STREAM, );
if (httpd == -)
error_die("socket");
memset(&name, , sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定端口和ip
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < )
error_die("bind");
if (*port == ) /* if dynamically allocating a port */
{
int namelen = sizeof(name);
if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -)
error_die("getsockname");
*port = ntohs(name.sin_port);
}
//监听
if (listen(httpd, ) < )
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)
{
//发回响应信息 http的请求方法不被接受
char buf[];
//返回501 错误 未实现 (Not implemented)是指Web 服务器不理解或不支持发送给它的 HTTP 数据流中找到的 HTTP 方法
sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), );
sprintf(buf, "Content-Type: text/html\r\n");//响应头
send(client, buf, strlen(buf), );
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), ); //响应正文段
sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "</TITLE></HEAD>\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "</BODY></HTML>\r\n");
send(client, buf, strlen(buf), );
} /**********************************************************************/ int main(void)
{
int server_sock = -;
u_short port = ;
int client_sock = -;
struct sockaddr_in client_name;
int client_name_len = sizeof(client_name);
pthread_t newthread; server_sock = startup(&port);
printf("httpd running on port %d\n", port); while ()
{
client_sock = accept(server_sock,
(struct sockaddr *)&client_name,
&client_name_len);
//建立链接
if (client_sock == -)
error_die("accept");
/* accept_request(client_sock); */
//多线程进行控制
if (pthread_create(&newthread , NULL, accept_request, client_sock) != )
perror("pthread_create");
} close(server_sock); return();
}

tinyhttpd ------ C 语言实现最简单的 HTTP 服务器的更多相关文章

  1. go 语言实现一个简单的 web 服务器

    学习Go语言的一些感受,不一定准确. 假如发生战争,JAVA一般都是充当航母战斗群的角色.一旦出动,就是护卫舰.巡洋舰.航母舰载机.预警机.电子战飞机.潜艇等等浩浩荡荡,杀将过去.(JVM,数十个JA ...

  2. 一个简单的 Web 服务器 [未完成]

    最近学习C++,linux和网络编程,想做个小(mini)项目.  就去搜索引擎, 开源中国, Sourceforge上找http server的项目. 好吧,也去了知乎.    知乎上程序员氛围好, ...

  3. R语言:用简单的文本处理方法优化我们的读书体验

    博客总目录:http://www.cnblogs.com/weibaar/p/4507801.html 前言 延续之前的用R语言读琅琊榜小说,继续讲一下利用R语言做一些简单的文本处理.分词的事情.其实 ...

  4. 踢爆IT劣书出版黑幕——由清华大学出版社之《C语言入门很简单》想到的(1)

    1.前言与作者 首先声明,我是由于非常偶然的机会获得<C语言入门很简单>这本书的,绝对不是买的.买这种书实在丢不起那人. 去年这书刚出版时,在CU论坛举行试读推广,我当时随口说了几句(没说 ...

  5. 留念 C语言第一课简单的计算器制作

    留念 C语言第一课简单的计算器制作 学C语言这么久了.  /* 留念 C语言第一课简单的计算器制作 */   #include<stdio.h>  #include<stdlib.h ...

  6. 用C语言编写一个简单的词法分析程序

    问题描述: 用C或C++语言编写一个简单的词法分析程序,扫描C语言小子集的源程序,根据给定的词法规则,识别单词,填写相应的表.如果产生词法错误,则显示错误信息.位置,并试图从错误中恢复.简单的恢复方法 ...

  7. 用Go语言实现一个简单的聊天机器人

    一.介绍 目的:使用Go语言写一个简单的聊天机器人,复习整合Go语言的语法和基础知识. 软件环境:Go1.9,Goland 2018.1.5. 二.回顾 Go语言基本构成要素:标识符.关键字.字面量. ...

  8. C语言之非常简单的几道题

    C语言之非常简单的几道题(还是写写),比较简单吧,主要有几道题的数据类型(如,第三题)和语句顺序(如,第二题)需要注意一小下下. 1. 求表达式S=1*2*3……*N的值大于150时,最小的N的值 / ...

  9. C 语言实例 - 实现简单的计算器

    C 语言实例 - 实现简单的计算器 实现加减乘除计算. 实例 # include <stdio.h> int main() { char operator; double firstNum ...

随机推荐

  1. 1 Easy Read/Write Splitting with PHP’s MySQLnd

    以下均是使用翻译软件翻译的! Note: This is part one in our Extending MySQL with PHP's MySQLnd Series, read part 2 ...

  2. 【DP】【P1941】【NOIP2014D1T3】飞扬的小鸟

    传送门 Description Flappy Bird是一款风靡一时的休闲手机游戏.玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙.如果小鸟一不小心撞到了水管 ...

  3. jq的$.each遍历数组

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. HDU 4549 矩阵快速幂+快速幂+欧拉函数

    M斐波那契数列 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Total Sub ...

  5. Qt ------- QByteArray操作注意

    使用QByteArray方法把数据存入QByteArray需要是char型数据,如果需要存入无符号8位数据,如下: QByteArray data; data[0] = 0xFF; 即使通过data[ ...

  6. book_notes

    http://139.196.8.158/ https://caomall.worktile.com/tasks/projects/58fd73047619c44427c0d719 http://lo ...

  7. Java中x=x+1 与x+=1 的一点区别

    转载自:http://www.cnblogs.com/heshan664754022/archive/2013/04/01/2994028.html 作者:十年半山 今天同悦姐学到了关于Java的复合 ...

  8. [LeetCode] 12. Integer to Roman ☆☆

    Given an integer, convert it to a roman numeral. Input is guaranteed to be within the range from 1 t ...

  9. 2015/9/10 Python基础(11):错误和异常

    程序在执行的过程中会产生异常,出现错误在以前的一个时期是致命的,后来随着程序的发展,使得一些错误的处理方式是柔和的,发生错误会产生一些错误的诊断信息和一些模糊的提示.帮助我们来处理异常.今天将学习Py ...

  10. c# 自定义排序Compare

    .net FrameWork 框架博大精深,用着忘着,计划对自己能够想到知识点梳理一下,此篇是对自定义排序的理解: class Program { static void Main(string[] ...