刚开始学习服务端相关的知识,对服务器程序没有了解,学习tinyhttpd是一个超轻量型的http server,可以用来了解服务器程序的本质和流程。Tinyhttpd的代码只有500行左右,比较易于阅读。
代码下载:https://sourceforge.net/projects/tinyhttpd/
在linux虚拟机上运行程序可以参照:
https://blog.csdn.net/zhuang20170703/article/details/79867581
图解(参考https://www.cnblogs.com/nengm1988/p/7816618.html根据理解进行修改):
流程概述(参考https://blog.csdn.net/jcjc918/article/details/42129311):
1. 启动服务器,在指定端口或随机端口绑定httpd服务
2. 接收到一个http请求(就是listen监听的端口accept的时候),派生一个县城运行accept_request函数(在startup函数和main函数中体现)
3. 取出http请求中的method(GET或POST)和url。对于GET方法,如果携带查询参数,则query_string指针指向url中?后面的GET参数。
4. 格式化url到path数组,表示浏览器请求的服务器文件路径,在tinyhttpd中服务器文件是在htdocs文件夹下的。当url以/结尾,或url是个目录,则默认在path中加上index.html,表示访问的主页地址。
5. 如果文件合法,对于无参数的GET请求,直接输出服务器文件到浏览器(在serve_file函数中实现),即用http格式写到套接字上,跳到(10)。其他情况(带参数GET,POST方式,url问可执行文件),则调用execute_cgi函数执行cgi脚本文件。(3.4.5为accept_request函数的主要内容)。
6. 读取整个http请求并丢弃,如果是POST则找出content-length,把http200状态码写到套接字。
7. 建立两个管道,cgi_input和cgi_output,并fork一个进程
8. 在子进程中,把 STDOUT 重定向到 cgi_outputt 的写入端,把 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 数据写入 cgi_input,已被重定向到 STDIN,读取 cgi_output 的管道输出到客户端,该管道输入是 STDOUT。接着关闭所有管道,等待子进程结束。(6.7.8.9均在execute_cgi函数实现)
10. 关闭与浏览器的连接,完成了一次 HTTP 请求与回应,因为 HTTP 是无连接的。
主要函数:
//函数说明:检查参数c是否为空格字符,
//也就是判断是否为空格(' ')、定位字符(' \t ')、CR(' \r ')、换行(' \n ')、垂直定位字符(' \v ')或翻页(' \f ')的情况。
//返回值:若参数c 为空白字符,则返回非 0,否则返回 0。
#define ISspace(x) isspace((int)(x))
#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
//每次收到请求,创建一个线程来处理接受到的请求
//把client_sock转成地址作为参数传入pthread_create
void *accept_request(void *);
//错误请求,返回400响应码
void bad_request(int);
//读取文件,写到socket套接字
void cat(int, FILE *);
//无法执行cgi程序
void cannot_execute(int);
//错误输出,写到perror
void error_die(const char *);
//执行cgi脚本,涉及脚本的动态解析
void execute_cgi(int, const char *, const char *, const char *);
//读取一行http报文,只要发现c是\n,则一行结束,如果读到\r,再用MSG_PEEK的方式读入一个字符,如果是\n,从socket读出
//如果是下一个字符则不处理,将c置为\n结束。如果读到的数据为0中断,或者小于1也视为结束,c置为\n
int get_line(int, char *, int);
//返回http头
void headers(int, const char *);
//没有发现文件
void not_found(int);
//如果不是CGI文件则调用cat()直接读取文件返回给请求的http客户端
void serve_file(int, const char *);
//开启tcp连接,绑定端口等操作;开启http服务,包括端口,监听,开启线程处理链接
int startup(u_short *);
//如果不是Get或者Post,就报方法没有实现;返回给浏览器,表明收到的http请求所用的method不被支持
void unimplemented(int);
代码详解:
/* 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.