小型的 HTTP 服务器thttpd
2014-11-16
官方网址:thttpd
下载 version 2.25b 的源码后解压,切换到源码目录,编译步骤为:
./configure
make
thttpd一共有16个源文件:
config.h fdwatch.h libhttpd.h match.h
mmc.h tdate_parse.h timers.h version.h
fdwatch.c libhttpd.c match.c mmc.c
strerror.c tdate_parse.c thttpd.c timers.c
- fdwatch是套接字管理模块
- mmc是内存管理模块
- timers是定时器管理模块
- libhttpd实现http 服务端的处理方法
- thttpd实现了主函数
首先梳理编写守护进程(daemon)遵循的一般步骤:
- 在父进程中执行fork并执行exit退出。
- 在子进程调用setsid。
- 让根目录”/”成为子进程的工作目录。
- 把子进程的umask变为0。
- 关闭不必要的文件描述符。
下面是主模块的大体流程图:
main()
{
打开系统日志
处理main参数
读取流控配置文件
设置UID
获取当前工作目录
切换到后台模式
创建子进程,父进程结束
获取系统资源信息
注册处理系统信号的函数
初始化定时器资源
初始化http server对象
创建系统定时器
创建连接表定时器
创建连接表数组
将监听套接字加入select集合
主循环
{
获取套接字集合发生的事件
检查套接字上的发生的具体行为
接收新的连接
处理新的连接
}
销毁http server对象
关闭系统日志
退出
}
##mmc模块分析 映射文件到内存,如果定义了HAVE_MMAP,使用mmap,否则使用malloc。
mmc_map函数的步骤是:
- 查找哈希表,如果已经存在于哈希表中,直接从表中取出,然后返回;
- 否则查找free_maps,如果free_maps可用,直接取出来用,free_maps为空时,malloc一块内存使用;
- 接下来判断文件大小,大小不为0时,因为定义了HAVE_MMAP,所以使用mmap内存映射文件;
- 然后把这个Map结构体添加到哈希表中,同时把这个Map添加到全局变量maps的链表头部。
mmc_unmap函数的步骤:
- 首先在哈希表中查找这个Map结构,如果没有找到,遍历全局变量maps链表寻找;
- 如果都没有找到或者找到后发现引用计数<=0,打印错误日志,函数结束;
- 找到了就减少这个Map的引用计数。
mmc_cleanup函数的步骤: 遍历全局变量maps,当此Map上的引用计数为0或者时间过期了,调用really_unmap。如果空闲总数大于DESIRED_FREE_COUNT,循环删除全局变量free_maps链表的头部的元素,直到free_count<=DESIRED_FREE_COUNT。
really_unmap函数的步骤: munmap取消内存映射,将这个Map结构添加到free_maps链表的头部,减少映射总数,增加空闲总数。
mmc_destroy函数的步骤: 遍历maps链表,调用really_unmap,然后遍历free_maps链表,释放内存,减少空闲总数、分配总数。
check_hash_size函数的步骤: 当hash_table为空时,初始化hash_size、hash_mask,当hash_size >= 3倍的映射总数,直接返回,否则释放掉原先的哈希表,增大hash_size,malloc(hash_size)大小的内存,遍历maps链表,调用add_hash将所有元素添加到哈希表中。
##fdwatch模块分析
此源文件根据Makefile中的宏定义决定采用哪种模型。
select,kqueue -- BSD系统
poll -- SysV系统
fdwatch_get_nfiles函数的功能: 获取可操作的文件描述符总数; 根据HAVE_SELECT,HAVE_POLL,HAVE_DEVPOLL,HAVE_KQUEUE这其中的哪个宏被定义,调用宏INIT,因为HAVE_SELECT宏被定义,所以INIT被定义为select_init。
fdwatch_add_fd 宏ADD_FD被定义为select_add_fd
fdwatch_del_fd 宏DEL_FD被定义为select_del_fd
fdwatch 宏WATCH被定义为select_watch
fdwatch_check_fd 宏CHECK_FD被定义为select_check_fd
fdwatch_get_next_client_data 宏GET_FD被定义为select_get_fd
##libhttpd模块分析
httpd_initialize函数的功能: malloc(httpd_server),申请一块内存用来保存httpd_server这个结构体,然后设置这个结构体中的成员; 根据参数sa6P和sa4P创建套接字,绑定套接字,然后监听这个套接字; 初始化mime媒体类型。
httpd_unlisten函数的功能: 调用close关闭监听套接字
httpd_get_conn函数的功能: httpd_realloc_str分配内存空间; 调用accept接收客户机的连接,对httpd_conn结构体中的字段进行赋值
httpd_parse_request函数的功能: 调用bufgets获取远程连接的HTTP各个字段,如HTTP版本,mime类型。
httpd_close_conn函数的功能: 释放文件映射,关闭此套接字连接
ls函数的功能: 扫描指定目录下的子目录和文件,列出文件名,属性,链接数,大小,修改时间。
##thttpd主模块分析
handle_term函数的步骤是: 对信号SIGTERM,SIGINT进行处理的函数
parse_args函数的步骤是: 处理进程启动时传递的参数,设置相应的全局变量。
read_config函数的步骤是: 读取配置文件,设置相应的全局变量。
read_throttlefile函数的步骤是: 读取‘流量控制配置文件’,设置throttletab。
shut_down函数的步骤是:
- 关闭connecttab中所有的连接,销毁这些连接;
- 关闭监听套接字,销毁http服务端;
- 销毁内存管理对象、定时器对象。
handle_newconnect函数的步骤是:
- malloc一块内存保存httpd_conn,调用httpd_get_conn获取接收连接,
- 调用httpd_set_ndelay设置为非阻塞模式;
- 然后调用fdwatch_add_fd加入select集合。
handle_read函数的步骤是:
- 调用read读取此套接字上发送过来的数据,调用httpd_got_request获取http请求头,
- 调用httpd_parse_request分析请求头,调用check_throttles检查一下是否符合流控策略,
- 调用httpd_start_request发送给回HTTP响应头,
- 调用fdwatch_del_fd移除此读套接字,然后调用fdwatch_add_fd加入写套接字。
finish_connection函数的步骤是:
- 调用httpd_write_response发送完buffer中的数据;
- 调用clear_connection清除连接。
really_clear_connection函数的步骤是:
- 调用fdwatch_del_fd从select集合中移除此套接字;
- 调用httpd_close_conn关闭此连接;
- 调用clear_throttles清除此流控;
- 将此连接表的状态设置为空闲,将此空闲连接索引设置为可用,减少连接总数;
idle函数的步骤是:
- 遍历connects数组,获取状态,根据当前状态进行处理;
- 此函数为主函数中设置的空闲定时器调用例程。
show_stats函数的步骤是: 调用logstats函数,logstats函数调用thttpd_logstats、httpd_logstats、mmc_logstats、fdwatch_logstats、tmr_logstats记录日志。
main函数的步骤是:
- 调用openlog打开系统日志;
- 调用parse_args处理main函数传进来的参数;
- 调用read_throttlefile读取流控配置文件;
- 调用系统函数getcwd获取当前工作目录;
- 因为Makefile文件定义了HAVE_DAEMON,所以调用daemon启动后台服务,fork创建子进程,父进程退出;
- 因为定义了宏HAVE_SETSID,所以调用系统函数setsid;
- 调用fdwatch_get_nfiles初始化fdwatch,获取资源信息;
- 因为定义了宏HAVE_SIGSET,所以调用sigset设置对系统信号的处理函数;
- 调用tmr_init初始化定时器对象;
- 调用httpd_initialize初始化http server对象;
- 安装occasional,idle,update_throttles,show_stats 4个系统定时器;
- 设置UID;
- 调用malloc分配connects连接表数组;
- 调用fdwatch_add_fd将IPV4,IPV6监听套接字加入select集合;
- 进入主循环;
- 调用fdwatch获取套接字集合中的信息;
- 当套接字上有事件发生时,检查此套接字,调用handle_newconnect获取新的连接;
- 调用fdwatch_get_next_client_data获取数据,判断此套接字的连接状态,根据状态分别调用handle_read,handle_send,handle_linger函数;
- 调用tmr_run运行定时器;
- 判断全局变量got_usr1,当SIGUSR1信号被接收时,handle_usr1函数被调用,got_usr1被置1,此时terminate变量被置1,主循环结束,监听套接字被移除,httpd_unlisten函数被调用;
- 调用shut_down销毁http server对象;
- 关闭系统日志,守护进程自此结束。
此外,有两个文本文件mime_encoding.txt mime_types.txt,在makefile.in里做处理,用sed格式化,生成mime_types.h,mime_encodings.h,在libhttpd.c中用#include引进来。