小型web服务器thttpd的学习总结(下)

1、主函数模块分析

对于主函数而言,概括来说主要做了三点内容,也就是初始化系统,进行系统大循环,退出系统。下面主要简单阐述下在这三个部分,又做了哪些工作呢。

初始化系统

  1. 拿出程序的名字(argv[0])用来作为参数打开那个log(syslog)

  2. 解析命令行的参数(parse_args),初始化内部的参数变量

  3. 检查当前主机名(addr) 没有的话利用gethostbyname从hostname中获取

  4. 检查当前要使用的主机端口(port)

  5. 读取Throttle file(门限文件,这里省略)

  6. 检查logfile的值,有的话就创建一个logfp咯

  7. 获得系统用户的相关信息(getpwnam),使用的系统用户为nobody(安全),记录下uid,gid值

  8. 切换程序的工作空间为参数中的dir值

  9. 获得当前工作目录(保证以'/'结尾)

  10. 调用daemon函数进入后台工作

  11. 查看pidfile,如果pidfile不为空则打开该文件,写入pid值

  12. 根据参数选择是否chroot,(chroot的原因见这个链接

  13. 设置信号处理函数signal(处理SIGTERM SIGINT SIGPIPE SIGHUP SIGUSR1)

  14. 初始化http处理模块(调用该模块init函数)

  15. 设置一个occasional timer用于时不时的清除定时器模块和mmc模块的无用内存,如果有需要的话,设置一个status timer,用于记录状态

  16. 为了安全,放弃root权限,变成nobody(使用setgroups和setgid,setuid函数族)

  17. 利用fdwatch包装的api,获得最多可以复用的fd数

  18. 创建一个连接池(数组)每个连接的数据结构如下,并完成初始化操作。

    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;

系统大循环

  1. 获得当前的时间,开始大循环

  2. 循环的条件时 terminate != 0 || numconnects > 0

  3. 循环的内容是:

    • 如果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时的处理

如果有数据直接读取数据扔掉

退出系统

  1. 清除已分配的内存
  2. 关闭系统日志
  3. 退出

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服务器)