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. scrapy-splash抓取动态数据例子六

    一.介绍 本例子用scrapy-splash抓取中广互联网站给定关键字抓取咨询信息. 给定关键字:打通:融合:电视 抓取信息内如下: 1.资讯标题 2.资讯链接 3.资讯时间 4.资讯来源 二.网站信 ...

  2. js 获取两位小数的方法

    1. 最笨的办法 function get() { var s = 22.127456 + ""; var str = s.substring(0,s.indexOf(" ...

  3. Microsoft.Office.Workflow.Actions Namespace

    Microsoft.Office.Workflow.Actions Namespace SharePoint 2010   Contains the workflow activities that ...

  4. CentOS下配置HTTPS訪问主机并绑定訪问port号

    系统环境: Linux: CentOS release 6.5 (Final) LAMP 步骤 1.lamp开启ssl # yum install openssl mod_ssl -y # 安装ope ...

  5. 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-如何使用随机数DRAND模块

    DRAND函数可以产生0-1的随机浮点数   DRAND的输入Seed有什么意义?     更多教学视频和资料下载,欢迎关注以下信息: 我的优酷空间: http://i.youku.com/aceta ...

  6. PHP基础知识(二)

    Global namespace  //看不懂看下面的中文 中英结合看看 When using namespaces, you may find that internal functions(内部( ...

  7. android 蓝牙SPP协议通信

    准备 1.蓝牙串行端口基于SPP协议(Serial Port Profile),能在蓝牙设备之间创建串口进行数据传输 2.SPP的UUID:00001101-0000-1000-8000-00805F ...

  8. 《Linux内核设计与实现》笔记-1-linux内核简单介绍

    一.Linux内核相对于传统的UNIX内核的比較: (1):Linux支持动态内核模块. 虽然Linux内核也是总体式结构,但是同意在须要的时候动态哦卸除(rmmod xxx)和载入内核模块(insm ...

  9. CLR_Via_C#学习笔记之事件

    一:首先我先引用网上别人对事件的一些说明,然后将会通过一个事例进行对事件的演示: EventArgs是包含事件数据的类的基类,用于传递事件的细节.EventHandler是一个委托声明如下 publi ...

  10. JConsole的使用手册 JDK1.5(转)

    一篇Sun项目主页上介绍JConsole使用的文章,前段时间性能测试的时候大概翻译了一下以便学习,今天整理一下发上来,有些地方也不知道怎么翻,就保留了原文,可能还好理解点,呵呵,水平有限,翻的不好,大 ...