对于主函数而言,概括来说主要做了三点内容,也就是初始化系统,进行系统大循环,退出系统。下面主要简单阐述下在这三个部分,又做了哪些工作呢。
拿出程序的名字(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
循环的内容是:
新来的连接处理
首先,保证连接数不大于最大的连接数
接着,找到连接池中的最靠前的free连接,新建一个用户数据结构,表明为未初始化;
调用http模块的httpd_get_conn函数,初始化该用户信息,然后填充些连接信息到数据结构中,开启read定时器;
设置该连接为非阻塞的连接;
fd可读时的处理
首先,看是否有更多的空间来存取用户的请求数据,如果没有的话,给read_buf增加空间,每次1000字节,5000封顶;
然后,从连接中读取数据;
判断当前读入的数据是否能构成一个合理的http request;
如果可以的话,进行http解析请求;
设置需要给用户放回的数据;
设置连接的状态为SENDING,停止该连接的读定时,开启该用户的写定时;
fd可写时的处理
fd需要linger时的处理
如果有数据直接读取数据扔掉
在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这里那个部分组成的。