小型web服务器thttpd的学习总结(上)
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的学习总结(上)的更多相关文章
- 小型web服务器thttpd的学习总结(下)
1.主函数模块分析 对于主函数而言,概括来说主要做了三点内容,也就是初始化系统,进行系统大循环,退出系统.下面主要简单阐述下在这三个部分,又做了哪些工作呢. 初始化系统 拿出程序的名字(argv[0] ...
- C语言构建小型Web服务器
#include <stdio.h> #include <sys/socket.h> #include <stdlib.h> #include <string ...
- 嵌入式web服务器-thttpd
交叉编译thttpd http://lakie.blog.163.com/blog/static/45185220201162910432330/ thttpd安装与调试 http://blog.cs ...
- MINI_httpd移植,构建小型WEB服务器
一.简介 目的:构建小型WEB站,具备SSL. mini_httpd is a small HTTP server. Its performance is not great, but for low ...
- HttpListener 实现小型web服务器
HttpListener 实现web服务器 用于小型服务器,简单.方便.不需要部署. 总共代码量不超过50行. static void Main(string[] args) { //创建HTTP监听 ...
- Tiny server:小型Web服务器
一.背景 csapp的网络编程粗略的介绍了关于网络编程的一些知识,在最后的一节主要就实现了一个小型的Webserver.这个server名叫Tiny,它是一个小型的可是功能齐全的Webserver.在 ...
- Python——轻量级web服务器flask的学习
前言: 根据工程需要,开始上手另一个python服务器---flask,flask是一个轻量级的python服务器,简单易用.将我的学习过程记录下来,有新的知识会及时补充. 记录只为更好的分享~ 正文 ...
- Raspkate - 基于.NET的可运行于树莓派的轻量型Web服务器
最近在业余时间玩玩树莓派,刚开始的时候在树莓派里写一些基于wiringPi库的C语言程序来控制树莓派的GPIO引脚,从而控制LED发光二极管的闪烁,后来觉得,是不是可以使用HTML5+jQuery等流 ...
- 常用WEB服务器的特点介绍
经过系统的学习web服务器,现在知道常用的web服务器的优缺点,这对搭建网站架构时选择使用web服务器很有帮助,现在我简单总结一下: 1. Apache:属于重量级web服务器(重量级主要是在软件包的 ...
随机推荐
- windows下 memcached 和 redis 服务器安装
memcached 安装: 1.下载memcached 文件: 2.拷贝到运行目录: 3.命令行进入到程序目录: 运行命令: memcached -d install 如果没有报错说明安装成功 4.打 ...
- 用java源代码学数据结构<七>: BST
/* * 以int类为例 * 其它的类必须能够比较 * */ //二叉搜索树的节点点 class BSTNode{ int item; BSTNode lc; BSTNode rc; BSTNode ...
- Getting to grips with CakePHP’s events system
CakePHP seems to get a slightly unfavourable reputation when compared to the likes of Symfony or Zen ...
- Leetcode--easy系列5
#83 Remove Duplicates from Sorted List Given a sorted linked list, delete all duplicates such that e ...
- 抓取网页图片的脚本(javascript)
抓取网页图片的脚本(javascript) 本文地址: http://blog.csdn.net/caroline_wendy/article/details/24172223 脚本内容 (没有换行) ...
- Maven+TestNG+ReportNG/Allure接口自动化测试框架初探(上)
转载:http://www.51testing.com/html/58/n-3721258.html 由于一直忙于功能和性能测试,接口自动化测试框架改造的工作被耽搁了好久.近期闲暇一些,可以来做点有意 ...
- java线程总结(4/5)
转自:http://blog.csdn.net/qiaqia609/article/details/8067356 整理的一些关于线程的面试题目: 46.java中有几种方法可以实现一个线程?用什么关 ...
- 云计算之路-试用Azure:如何建立虚拟机之间的内网连接
在阿里云上,同一个帐户创建的所有虚拟机(云服务器)之间的内网是直接连通的.而Azure则完全不一样,一开始使用时有点不知所措,后来摸索出来了——在Azure中只有处于同一个虚拟网络(Virtual N ...
- 实时视频直播客户端技术盘点:Native、HTML5、WebRTC、微信小程序
1.前言 2017 年 12 月,微信小程序向开发者开放了实时音视频能力,给业内带来广阔的想象空间.连麦互动视频直播技术在 2016 年直播风口中成为视频直播的标配,然而只有在原生的 APP 上才能保 ...
- linux内核及其模块的查询,加载,卸载 lsusb等
http://blog.sina.com.cn/s/blog_53e81e2a0100zkxi.html 1,/sbin/update-modules文件,他是一个linux通用的模块管理脚本程序. ...