小型web服务器thttpd的学习总结(下)
1、主函数模块分析
对于主函数而言,概括来说主要做了三点内容,也就是初始化系统,进行系统大循环,退出系统。下面主要简单阐述下在这三个部分,又做了哪些工作呢。
初始化系统
- 拿出程序的名字(argv[0])用来作为参数打开那个log(syslog) 
- 解析命令行的参数(parse_args),初始化内部的参数变量 
- 检查当前主机名(addr) 没有的话利用gethostbyname从hostname中获取 
- 检查当前要使用的主机端口(port) 
- 读取Throttle file(门限文件,这里省略) 
- 检查logfile的值,有的话就创建一个logfp咯 
- 获得系统用户的相关信息(getpwnam),使用的系统用户为nobody(安全),记录下uid,gid值 
- 切换程序的工作空间为参数中的dir值 
- 获得当前工作目录(保证以'/'结尾) 
- 调用daemon函数进入后台工作 
- 查看pidfile,如果pidfile不为空则打开该文件,写入pid值 
- 根据参数选择是否chroot,(chroot的原因见这个链接) 
- 设置信号处理函数signal(处理SIGTERM SIGINT SIGPIPE SIGHUP SIGUSR1) 
- 初始化http处理模块(调用该模块init函数) 
- 设置一个occasional timer用于时不时的清除定时器模块和mmc模块的无用内存,如果有需要的话,设置一个status timer,用于记录状态 
- 为了安全,放弃root权限,变成nobody(使用setgroups和setgid,setuid函数族) 
- 利用fdwatch包装的api,获得最多可以复用的fd数 
- 创建一个连接池(数组)每个连接的数据结构如下,并完成初始化操作。 - typedef struct { 
 int conn_state; //连接状态
 httpd_conn* hc; //用户信息
 int tnums[MAXTHROTTLENUMS]; /* throttle indexes /
 int numtnums; //
 long limit; //
 time_t started_at; //起始时间
 Timer idle_read_timer; //空闲读取定时器
 Timer* idle_send_timer; //空闲发送定时器
 Timer* wakeup_timer; //苏醒定时器
 Timer* linger_timer; //
 long wouldblock_delay; //
 off_t bytes; //****
 off_t bytes_sent; //发送的数据
 off_t bytes_to_send; //还需要发送的数据
 } connecttab;
系统大循环
- 获得当前的时间,开始大循环 
- 循环的条件时 terminate != 0 || numconnects > 0 
- 循环的内容是: - 如果fdwatch_recompute标志是1,清除原来的fdwatch内部变量,重新设置哪些文件需要被监控读和写。如果某连接状态是reading或者lingering则观测该连接的读状态;而如果某个连接的状态是sending,则观测该连接的写状态,(记得还要观测服务器的监听fd读状态)。
- 然后就开始fdwatch所有描述符了,时间参数是下一个定时器触发的时间,从而在定时器触发前一直监听。
- 接着开始处理观测结果,如果没有fd准备好,那就开始运行定时器。
- 否则,根据是新的连接还是当前连接池中的连接以及其事件进行相应的处理。
 
新来的连接处理
- 首先,保证连接数不大于最大的连接数 
- 接着,找到连接池中的最靠前的free连接,新建一个用户数据结构,表明为未初始化; 
- 调用http模块的httpd_get_conn函数,初始化该用户信息,然后填充些连接信息到数据结构中,开启read定时器; 
- 设置该连接为非阻塞的连接; 
fd可读时的处理
- 首先,看是否有更多的空间来存取用户的请求数据,如果没有的话,给read_buf增加空间,每次1000字节,5000封顶; 
- 然后,从连接中读取数据; 
- 判断当前读入的数据是否能构成一个合理的http request; 
- 如果可以的话,进行http解析请求; 
- 设置需要给用户放回的数据; 
- 设置连接的状态为SENDING,停止该连接的读定时,开启该用户的写定时; 
fd可写时的处理
- 查看response中是否有值,如果没有,则直接开始写文件,该文件已经被映射到内存,直接从hc中的file_address中读即可。而如果有值的话,则写response中和file_address中的数据;
- 如果没有写成功的话,设置连接状态为pause,并设置wakeup定时器,过会儿重新发送;
- 重新设置定时器,根据发送数据的情况将responlen清零,并设置bytes_sent中的值,按情况清除连接还是直接返回。
fd需要linger时的处理
如果有数据直接读取数据扔掉
退出系统
- 清除已分配的内存
- 关闭系统日志
- 退出
2、httpd模块分析
在httpd模块中,定义了两个核心数据结构,服务器数据(http_server)和用户连接数据(httpd_conn)。
服务器的数据结构的定义分别如下:
/* A server. */
typedef struct {
	char* hostname;                  //主机名(ex:localhost)
	struct in_addr host_addr;		  //主机地址
	int port;                        //端口号
	char* cgi_pattern;				  //cgi样式
	char* cwd;						  //当前工作路径
	int listen_fd;					  //监听套接字
	FILE* logfp;                     //log文件描述符
	int no_symlinks;                 //有无符号连接标志
	int vhost;                       //虚拟主机标志
} httpd_server;
下面是连接的数据结构:
/* A connection. */
typedef struct {
	    int initialized;                 //初始化标志
	    httpd_server* hs;		  //服务器结构地址
          struct in_addr client_addr;       //客户端地址
	    char* read_buf;                  //读缓存
	    int read_size, read_idx, checked_idx; //缓存标志位
	    int checked_state;               //检测状态标志
	    int method;                      //请求方法标志
	    int status;                      //当前连接状态
	    off_t bytes;
	    char* encodedurl;				  //encode后的url
	    char* decodedurl;                //decode后的url
	    char* protocol;                  //http协议类型
	    char* origfilename;				  //原来的文件名
	    char* expnfilename;              //扩展后的文件名
	    char* encodings;
	    char* pathinfo;
	    char* query;
	    char* referer;
	    char* useragent;
	    char* accept;
	    char* accepte;
	    char* cookie;
	    char* contenttype;
	    char* reqhost;
	    char* hdrhost;
	    char* authorization;
	    char* remoteuser;
	    char* response;                 //发送缓存
	    int maxdecodedurl, maxorigfilename, maxexpnfilename, maxencodings,maxpathinfo, maxquery, maxaccept, maxaccepte, maxreqhost,maxremoteuser, maxresponse;
#ifdef TILDE_MAP_2
	    char* altdir;
	    int maxaltdir;
#endif
	    int responselen;
	    time_t if_modified_since, range_if;
	    off_t contentlength;
	    char* type;		/* not malloc()ed */
	    char* hostname;	/* not malloc()ed */
	    int mime_flag;
	    int one_one;	/* HTTP/1.1 or better */
	    int got_range;
	    int tildemapped;	/* this connection got tilde-mapped */
	    off_t init_byte_loc, end_byte_loc;
	    int keep_alive;
	    int should_linger;
	    struct stat sb;
	    int conn_fd;
        char* file_address;
} httpd_conn;
该模块提供的函数接口有:
//初始化http server数据结构
extern httpd_server* httpd_initialize(
char* hostname, u_int addr, int port, char* cgi_pattern, char* cwd,
FILE* logfp, int no_symlinks, int vhost );
//改变http server结构中的logfp
extern void httpd_set_logfp( httpd_server* hs, FILE* logfp );
//清除http server结构
extern void httpd_terminate( httpd_server* hs );
//当有一个新连接来临时,接收这个连接,并将该连接http client初始化
extern int httpd_get_conn( httpd_server* hs, httpd_conn* hc );
//根据连接hc的read_buf中的内容,判断当前接收的数据是否是一个完成的http请求,并返回对应结果
extern int httpd_got_request( httpd_conn* hc );
//解析上述的http请求,并把解析后的值放入hc对应的数据单元中
extern int httpd_parse_request( httpd_conn* hc );
//准备需要向客户端发送的数据
extern int httpd_start_request( httpd_conn* hc );
//把hc中response中的内容写给用户
extern void httpd_write_response( httpd_conn* hc );
//关闭一个连接并释放连接的空间
extern void httpd_close_conn( httpd_conn* hc, struct timeval* nowP );
//释放hc中所有的空间
extern void httpd_destroy_conn( httpd_conn* hc );
//向客户端发送一个错误信息
extern void httpd_send_err(
httpd_conn* hc, int status, char* title, char* form, char* arg );
//根据method号找到method内容
extern char* httpd_method_str( int method );
//重新分配一段string
extern void httpd_realloc_str( char** strP, int* maxsizeP, int size );
其中,系统操作这个httpd模块则可以分为如下几部进行理解。
- 使用对应的接口进行httpd模块的初始化,对应http server的初始化采用httpd_initialize接口,初始化好了之后就在对应的端口上进行监听套接字; 
- 当监听的套接字可读之后,就可以使用httpd_get_conn函数,accept该用户,并开辟一个用户的httpd_conn结构,并该结构利用已有的信息进行初始化; 
- 接着当该用户的套接字可读时,又将会去调用httpd_got_request接口,该接口将会去将套接字上的数据读到hc结构中的read_buf中去,然后对于read_buf中的数据进行检测,查看收到的数据是否能构成一个完整的http请求; 
- 如果接收到的确实是一个完整的http请求,就会去调httpd_parse_request接口,对read_buf中的数据进行解析,并将http头中解析到的字段(如method,url等)放入hc结构体中。 
- 当数据都解析完成后,系统将会调用httpd_start_request接口来准备需要回复给用户的数据,这个数据的准备是根据解析到的具体情况来进行处理的,有可能就是一个index.html文件,而有可能就是在hc的response中放了一些错误信息。 
- 而准备好要发送的数据之后,就可以设置连接的状态为SENDING,这样下次select后就会对于该连接调用handle_send函数,将数据发送出去,并关闭连接。 
再具体的说的话,可以看到thttpd预先开辟了大约1024个conn_tab结构,这里的conn_tab指的是连接的具体信息,其数据结构核心数据如下:
typedef struct
{
	int conn_state;
	httpd_conn* hc; //has to define
	long limit;
	time_t started_at;
	int numtnums;
	Timer* idle_read_timer;
	Timer* idle_send_timer;
	Timer* wakeup_timer;
	Timer* linger_timer;
	off_t bytes;
	off_t bytes_sent;
	off_t bytes_to_send;
} conn_tab;
其中包含了连接的状态conn_state,内嵌了具体的用户信息hc,发送限制limit,开始时间started_at,四个定时器(读定时,写定时,连接苏醒定时,连接保持定时),和连接当前要发送的字节数bytes_to_send,已经发送的字节bytes_sent,这里的bytes好像没有啥重要意义;
对于接收到的一个用户而言,则按照上述的httpd_conn结构的定义,则初始化的时候需要考虑如下内容,init初始化标志,hs服务器结构地址,自己的client地址,然后就是读取信息需要的read_buf和用于其标记的idx和checked状态位,然后就是解析所需要的method, encodedurl, decodeurl, protocol, origfilename, expnfilename, encodings, pathinfo, query, referer, useragent, accept, accepete, cookie, contenttype, reqhost, hdrhost, quthorization, remoteuser, 及其最大字符长度,最后就是返回需要的response_buf,file_address, contentlength,还有些标志位信息如mimeflag,http1.1标志one_one,keep_alive标志,should_linger标志以及got_range标志。
这里以一个普通的GET请求为例,讲一下http_conn中各字段的值分别是什么。
HTTP REQUEST: GET /index.html?a=1 HTTP/1.1
解析完成后:
method              1(GET)
protocol            HTTP/1.1
reqhost 		 	 ""
encodedurl          /index.html?a=1(可能有16进制数)
decodefurl          /index.html?a=1(没有16进制)
origfilename        index.html (url是"/"时设为“.”)
expnfilename        index.html (没有符号链接,且证明了文件存在性)
pathinfo            ""
query               a=1
accept              text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
accepte             gzip, deflate, sdch
remoteuser          ""
response            存放着http头部信息p
referer             ""
useragent           Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36
cookie              ""
contenttype         ""
hdrhost             127.0.0.1
authorization       ""
id_modified_since   -1
range_if            -1
contentlength       -1
got_range            0
init_byte_loc        0
end_byte_loc         -1
keep_alive           1
should_linger        1
hostname             NULL
mime_flag            1
bytes                111(html文件的大小)
file_address         文件内存地址
其中最后反馈的数据就是由response和file_address这里那个部分组成的。小型web服务器thttpd的学习总结(下)的更多相关文章
- 小型web服务器thttpd的学习总结(上)
		1.软件的主要架构 软件的文件布局比较清晰,主要分为6个模块,主模块是thttpd.c文件,这个文件中包含了web server的主要逻辑,并调用了其他模块的函数.其他的5个模块都是单一的功能模块,之 ... 
- C语言构建小型Web服务器
		#include <stdio.h> #include <sys/socket.h> #include <stdlib.h> #include <string ... 
- 嵌入式web服务器-thttpd
		交叉编译thttpd http://lakie.blog.163.com/blog/static/45185220201162910432330/ thttpd安装与调试 http://blog.cs ... 
- MINI_httpd移植,构建小型WEB服务器
		一.简介 目的:构建小型WEB站,具备SSL. mini_httpd is a small HTTP server. Its performance is not great, but for low ... 
- HttpListener 实现小型web服务器
		HttpListener 实现web服务器 用于小型服务器,简单.方便.不需要部署. 总共代码量不超过50行. static void Main(string[] args) { //创建HTTP监听 ... 
- Tiny server:小型Web服务器
		一.背景 csapp的网络编程粗略的介绍了关于网络编程的一些知识,在最后的一节主要就实现了一个小型的Webserver.这个server名叫Tiny,它是一个小型的可是功能齐全的Webserver.在 ... 
- 用C写一个web服务器(三) Linux下用GCC进行项目编译
		.container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .conta ... 
- Python——轻量级web服务器flask的学习
		前言: 根据工程需要,开始上手另一个python服务器---flask,flask是一个轻量级的python服务器,简单易用.将我的学习过程记录下来,有新的知识会及时补充. 记录只为更好的分享~ 正文 ... 
- Raspkate - 基于.NET的可运行于树莓派的轻量型Web服务器
		最近在业余时间玩玩树莓派,刚开始的时候在树莓派里写一些基于wiringPi库的C语言程序来控制树莓派的GPIO引脚,从而控制LED发光二极管的闪烁,后来觉得,是不是可以使用HTML5+jQuery等流 ... 
随机推荐
- DIV强制不换行
			一.单个DIV:1.用nobr元素 <html> <head> </head> <body> <div><nobr>不换行不换行 ... 
- 【转】dependency injection 的例子
			Dependency Injection in PHP. Create your own DI container. / blog / PHP By my opinion one of the big ... 
- atitit.提升开发效率---使用server控件生命周期  asp.net 11个阶段  java jsf 的6个阶段比較
			atitit.提升开发效率---使用server控件生命周期 asp.net 11个阶段 java jsf 的6个阶段比較 例如以下列举了server控件生命周期所要经历的11个阶段. (1)初始 ... 
- GET RESTful With Python
			Python调用RESTful:http://blog.akiban.com/get-restful-with-python/ 本文就是参考该英文做了一下试验,后续补充一下翻译. This post ... 
- iOS exit(),abort(),assert()函数区别
			iOS exit(),abort(),assert()函数区别 exit() 退出程序 abort() 停止程序, assert()检查里面的参数如果为nil抛出异常: 
- vc 获取函数名称真实地址
			首先写一个很简单的main函数: int main(){ printf("main的地址(?):%08x",main); } 单步调试,可得知 main函数的真实入口地址是:00b ... 
- 解决RegexKitLite编译报错
			原地址:http://blog.csdn.net/kepoon/article/details/7586861 在编译RegexKitLite的时候,报错如下: Undefined symbols f ... 
- Android版俄罗斯方块的实现
			学习Android的基本开发也有一段时间了,可是由于没有常常使用Android渐渐的也就忘记了. Android编程学的不深,不过为了对付逆向,可是有时还是会感到力不从心的.毕竟不是一个计算机专业毕业 ... 
- 01-hibernate注解:类级别注解,@Entity,@Table,@Embeddable
			@Entity @Entity:映射实体类 @Entity(name="tableName") name:可选,对应数据库中一个表,若表名与实体类名相同,则可以省略. 注意:使用@ ... 
- Hibernate 一对一关联查询
			版权声明:本文为博主原创文章,如需转载请标注转载地址. 博客地址:http://www.cnblogs.com/caoyc/p/5602418.html 一对一关联,可以分为两种.一种是基于外键的关 ... 
