1、软件的主要架构

软件的文件布局比较清晰,主要分为6个模块,主模块是thttpd.c文件,这个文件中包含了web server的主要逻辑,并调用了其他模块的函数。其他的5个模块都是单一的功能模块,之间没有任何耦合。

  • 其中包括多路IO复用的抽象模块fdwatch.h/c,这个模块中将常用的IO复用接口,如poll/select抽象为一类接口,从而保证了接口的单一性和软件的可移植性。
  • libhttpd模块包含的是libhttpd.h/c文件,主要的功能是完成地提供http请求的解析和处理服务,对外提供相应的接口。
  • match模块则是对外提供了一个match.c用来做为关键词的匹配作用,用于cgi符号匹配。
  • mmc模块包块的也是mmc.h/c文件,用来进行文件存储的缓存管理。
  • 另外一个就是timer.h/c,自己实现的一个定时器模块,主要用来做请求接收,发送和清理内存的操作定时。

2、各个模块代码分析

2.1 fdwatch模块

该模块对外提供了6个函数,就是对一般的select/poll类函数的使用方法进行了相应的抽象,包括

//获得系统可使用的fd的最大数目,并初始化数据结构
extern int fdwatch_get_nfiles(void) //清除fdwatch中的数据结构
extern void fdwatch_clear( void ) //对fd set中的fds进行操作,其中rw表示是否可读可写
extern int fdwatch_add_fd(int fd, int rw)
extern int fdwatch_del_fd(int fd, int rw) //fd多路复用的主循环函数 参数是超时时间
extern int fdwatch(long timeout_msecs) //对fd状态的检查
extern void fdwatch_check_fd(int fd, int rw) //提取出fdwatch当前的状态
extern void fdwatch(long* nselectP, long* npollP)

如果想自己简单实现的话,可以按照如下进行实现:

//首先设置模块全局变量
static int nfiles; //可以watch的最大fd数目
static int maxfd; //当前watch的最大fd值
static int conn_nums; //当前连接的数目
static long nselect; //当前select的次数

由于如果使用select的话,需要首先有一个fd_set来标记需要关注哪些fd可读,关注哪些fd可写。而将标记fd_set传入之后,该fd_set返回的指将是当前可读或者可写的fd列表,会改变标记set的值,因此,这里设置了两个fd_set,一个用于标记需要关注的fd,另一个用于传入select函数,获得当前可处理的fd情况。

//标记 set
static fd_set master_rfdset;
static fd_set master_wfdset;
//工作 set
static fd_set working_rfdset;
static fd_set working_wfdset; //内部函数的声明
static int fdwatch_select(long timeout_msecs);

该函数用于获得当前可以复用的fd的最大个数,这个最大个数受制于几个因素,一个是进程可以打开的最大的文件描述符数,getdtablesize()返回的值,还有资源限制的最大fd数,另外还不能超过fd_setsize值,一般现在的fd_set类型都是long int的数组,每一位代表一个fd的读写情况,取值一般为1024。

int fdwatch_get_nfiles( void )
{
#ifdef RLIMIT_NOFILE
struct rlimit rl;
#endif
//进程所能打开的最大文件描述符数
nfiles = getdtablesize(); //设置资源限制的最大fd值
#ifdef RLIMIT_NOFILE
if(getrlimit(RLIMIT_NOFILE, &rl) == 0)
{
nfiles = rl.rlim_cur;
if( rl.rlim_max == RLIM_INFINITY )
rl.rlim_cur = 8192;
else
rl.rlim_cur = rl.rlim_max;
if( setrlimit( RLIMIT_NOFILE, &rl) == 0 )
nfiles = rl.rlim_cur;
}
#endif
//如果是SELECT不能超过FD_SETSIZE的值
nfiles = MIN(nfiles, FD_SETSIZE); nselect = 0; return nfiles;
}

清除标志位,直接调用FD_ZERO函数:

void fdwatch_clear( void )
{
maxfd = -1;
conn_nums = 0;
FD_ZERO( &master_wfdset );
FD_ZERO( &master_rfdset );
}

增加标志位,则是根据rw的情况调用FD_SET函数:

void fdwatch_add_fd( int fd, int rw )
{
conn_nums++;
if(fd > maxfd)
maxfd = fd;
switch( rw )
{
case FD_READ:
FD_SET(fd, &master_rfdset);
case FD_WRITE:
FD_SET(fd, &master_wfdset);
default:
return;
} }

检查标志位,同样根据rw的情况调用FD_ISSET函数:

int fdwatch_check_fd( int fd, int rw)
{
switch( rw )
{
case FD_READ:
return FD_ISSET(fd, &working_rfdset);
case FD_WRITE:
return FD_ISSET(fd, &working_wfdset);
default:
return 0;
}
}

在大循环中,将master_fdset的值赋值给working_fdset然后调用select传入working_fdset进行检测,检测的时候由参数timeout_msecs决定。

int fdwatch( long timeout_msecs )
{
return fdwatch_select( timeout_msecs );
} static int fdwatch_select( long timeout_msecs )
{
struct timeval timeout; ++nselect;
working_rfdset = master_rfdset;
working_wfdset = master_wfdset;
if(timeout_msecs == INFTIM)
{
if((maxfd + 1) <= nfiles)
return select(maxfd +1, &working_rfdset, &working_wfdset, NULL, (struct timeval*)0);
else
{
perror("maxfd out of range");
return -1;
}
}
else
{
timeout.tv_sec = timeout_msecs / 1000L;
timeout.tv_usec = timeout_msecs % 1000L * 1000L;
if((maxfd + 1) <= nfiles)
return select(maxfd + 1, &working_rfdset, &working_wfdset, NULL, &timeout);
else
{
perror("maxfd out of range");
return -1;
}
}
}

下面两个函数主要是永远检测当前select模块的情况,方便后面打log。

void fdwatch_status( long* nselectP )
{
*nselectP = nselect;
nselect = 0;
} int fdwatch_get_conn_nums(void)
{
return conn_nums;
}

可以看到fdwatch模块仅仅是对select做了一个简单的封装,从而可以更加灵活的使用接口进行fd复用的操作,从而可以正常的处理小规模的服务器并发。

2.1 定时器模块

定时器模块主要是提供一个定时服务,而采用的时间则是从gettimeofday库函数来获得。建立起定时器模块,主要需要首先确定下模块中的几个结构体,如定时器触发后,响应的函数和该函数的参数选择:

//响应函数定义
typedef void timeout_func(timeout_args args, struct timeval* now);
//传入参数
typedef union
{
void* p;
int i;
long int l;
}timeout_args;

这里将参数定义为一个联合体,从而可以传入多样类型的值,同时节省空间。所以,定时器结构的定义为:

typedef struct timer_struct
{
timeout_func* timer_proc; //响应函数
timeout_args args; //响应函数参数
struct timeval time; //定时器触发时间
long msecs; //定时多长时间
int periodic; //周期性标志 struct timer_struct* next; //做成链表
} Timer;

然后可以看下定时器模块需要提供的模块接口,大抵也就是如下几种,创建一个定时器,运行一个定时器,重置一个定时器,取消一个定时器,这里还提供了一个查看最近定时器的触发时间的接口,用来在这段时间内通过select进行查看各个连接的情况,也就是说这个时间作为上述fdwatch函数的参数传入。此外在本定时器模块中,实际上是建立了两个链表,一个是当前定时器的列表,一个是被取消的定时器的列表。因此,还提供了tmr_clean函数用于合理释放无用定时器所占用的内存。而tmr_destroy函数则是销毁所有的定时器结构。

//创建一个定时器
extern Timer* tmr_create(timeout_func* timer_proc, timeout_args args, struct timeval* now, long msecs, int periodic); //运行一个定时器
extern void tmr_run(struct timeval* now); //查看最近的定时器触发时间-毫秒
extern long tmr_timeout_ms(struct timeval* now); //查看最近的定时器触发时间-struct timeval
extern struct timeval* tmr_timeout(struct timeval* now); //重置一个定时器
extern void tmr_reset(Timer* timer, struct timeval* now); //取消一个定时器
extern void tmr_cancle(Timer* timer); //清除定时器结构
extern void tmr_clean(void); //销毁所以定时器内存
extern void tmr_destroy(void);

具体的函数实现,这里就简单的阐述一下过程,不展开代码叙述了。创建定时器时,如果无用定时器列表中有内容,就直接使用其数据,否则malloc一个,然后初始化后,插入列表中。运行一个定时器,则是根据当前时刻的时间,在列表中依次比对,对于超时的定时器运行其回调函数,接着根据周期性选择回收这个定时器还是重新设置这个定时器。最近触发时间也是在链表中找出最近的时间返回。重置定时器就是根据传入的时间,重新确定定时器的触发时间。取消定时器就是将该定时器从当前定时器列表转移到无用定时器列表中。清除定时器和销毁定时器上面已经介绍过了,就是销毁某些内存。

一次性写很多真的看着都烦呀,那另外两个主要的模块就在下篇来介绍吧。

另注:本文中的代码是自己手写,和原代码并非都是一致的。

小型web服务器thttpd的学习总结(上)的更多相关文章

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

    1.主函数模块分析 对于主函数而言,概括来说主要做了三点内容,也就是初始化系统,进行系统大循环,退出系统.下面主要简单阐述下在这三个部分,又做了哪些工作呢. 初始化系统 拿出程序的名字(argv[0] ...

  2. C语言构建小型Web服务器

    #include <stdio.h> #include <sys/socket.h> #include <stdlib.h> #include <string ...

  3. 嵌入式web服务器-thttpd

    交叉编译thttpd http://lakie.blog.163.com/blog/static/45185220201162910432330/ thttpd安装与调试 http://blog.cs ...

  4. MINI_httpd移植,构建小型WEB服务器

    一.简介 目的:构建小型WEB站,具备SSL. mini_httpd is a small HTTP server. Its performance is not great, but for low ...

  5. HttpListener 实现小型web服务器

    HttpListener 实现web服务器 用于小型服务器,简单.方便.不需要部署. 总共代码量不超过50行. static void Main(string[] args) { //创建HTTP监听 ...

  6. Tiny server:小型Web服务器

    一.背景 csapp的网络编程粗略的介绍了关于网络编程的一些知识,在最后的一节主要就实现了一个小型的Webserver.这个server名叫Tiny,它是一个小型的可是功能齐全的Webserver.在 ...

  7. Python——轻量级web服务器flask的学习

    前言: 根据工程需要,开始上手另一个python服务器---flask,flask是一个轻量级的python服务器,简单易用.将我的学习过程记录下来,有新的知识会及时补充. 记录只为更好的分享~ 正文 ...

  8. Raspkate - 基于.NET的可运行于树莓派的轻量型Web服务器

    最近在业余时间玩玩树莓派,刚开始的时候在树莓派里写一些基于wiringPi库的C语言程序来控制树莓派的GPIO引脚,从而控制LED发光二极管的闪烁,后来觉得,是不是可以使用HTML5+jQuery等流 ...

  9. 常用WEB服务器的特点介绍

    经过系统的学习web服务器,现在知道常用的web服务器的优缺点,这对搭建网站架构时选择使用web服务器很有帮助,现在我简单总结一下: 1. Apache:属于重量级web服务器(重量级主要是在软件包的 ...

随机推荐

  1. hive中的concat,concat_ws,collect_set用法

    select id, str_to_map(concat_ws(',',collect_set(concat(substr(repay_time,0,7), ':',round(interest,2) ...

  2. intelliJ idea提示api注释

  3. C调用栈重温

    C栈的地址是从高位地址不断忘低位地址膨胀的,最先调用的函数所处的栈地址最高,后被调用的地址在低位: A->H这些地址表明了表明了基本的调用关系,AB是函数入参,CD是函数内的变量. 先调用者在高 ...

  4. JavaScript的String对象

    1.创建String对象 Html标签的格式编排方法:可以将String对象的字符串内容输出成对应的html标签. 示例: var str = "JavaScript程序设计"; ...

  5. 解决ios7.0 以后自己定义导航栏左边button靠右的问题

    1.自己定义button //左button UIButton *leftBtn = [[UIButton , , , )]; [leftBtn addTarget:self action:@sele ...

  6. 算法笔记_063:蓝桥杯练习 送分啦(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 问题描述 这题想得分吗?想,请输出“yes”:不想,请输出“no”. 输出格式 输出包括一行,为“yes”或“no”. 2 解决方案 初步一看,这题 ...

  7. Android资源图片读取机制

    在新建一个Android项目时.在res目录下会自己主动生成几个drawable目录,drawable-ldpi,drawable-mdpi,drawable-hdpi,一直以来都对此不太清楚.图片应 ...

  8. 反射与annotation

    1,可以通过反射取得使用的全部annotation 2,可以通过反射取得指定的annotation. 一个annotation要想变得有意义, 必须结合反射机制取得annotation中设置的全部内容 ...

  9. sql 中 ALTER 和 UPDATE 的区别

    alter 是DDL语句,是修改数据库中对象(表,数据库,视图..)的语句. 如需在表中添加列,请使用下面的语法: ALTER TABLE table_name ADD column_name dat ...

  10. Asp.Net MVC4的学习概况

    周一正式开始了毕业工作.然后学习调试了近4天,刚刚总算在同事的帮助下做出了一个基于Asp.Net MVC4的Hello World显示. 这是一篇最为基础的记录教程,记录内容可能有点混乱,旨在能在刚调 ...