分享
 
 
 

Spyrius

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

Spyrius是Mark Lindner先生写的一个小程序。Mark Lindner先生曾经写过pingutil和CFL几个工具包。我是从GNU找到Mark

Lindner先生的。希望这里看客经常去GNU的人也很多,并都得到自己的收获和快乐。

Spyrius是一个多线程的、通用的一个超级守护进程(SuperDaemon)。从事Linux/Unix的人员应该清楚Daemon的具体含义,模糊

的话可以想象一下inetd所做的工作。

Spyrius在一个TCP端口进行侦听来自客户端的连接,不像一些传统的Daemons,必须产生一个新的子进程将来为每一个客户端连

接来进行服务(Service)。Spyrius是一个基于多线程的工具,利用每一个线程来处理请求。用线程替代进程的好处之一就是共享

了进程的相关堆栈资源等,避免了进程创建所带来的消耗。

Spyrius另外的一个特点是将特定的功能服务进行模块化。譬如网络通信、解析报文、创建线程、等等被设计成为plug-in模块,

可以被独立的编写和调试。你可以通过配置文件组合自己的Spyrius。

Plug-In模块通过一组API和Daemon来进行交互,当然,也提供了相应的API和Client来通信。

Spyrius主程序在Spyrius.c文件中,默认指定了管理端口(SPYR_ADMIN_PORT)8381和服务端口(SPYR_SERVER_PORT)8380。主程序

从命令行读取配置参数,其中包含:

-a 指定管理端口,即SPYR_ADMIN_PORT

-p 指定服务端口,即SPYR_SERVER_PORT

-t 指定任务运行超时时间

-w 指定最大工作的Worker线程数量。

-c 读取相应的配置文件。

当然,如果没有使用命令行参数的话,Spyrius带有默认缺省指定的。

87-174行使用getopt处理完成命令行参数后,程序开始创建守护进程,进程首先fork,父进程退出程序,子进程调用

spyr_daemon_init来完成一些操作变成守护进程。

193: if(!spyr_daemon_init(sp, ap))

我们进入spyr_daemon_init里面,此时文件变成Daemon.c,从名字也可以看出这个文件代码的主要功能。在93行是

spyr_daemon_init函数。第一步,调用openlog,开启log功能,为后续的信息输出做好准备。因为程序变为守护进程后,没有控制

终端,所以无法使用stdout,stderr来进行输出。创建守护进程的步骤,Stevens先生已经提到,相信各位看官也熟悉,我这里也不

再絮叨。

接下来,spyr_daemon_init函数进行服务端口和管理端口的创建。分别是spyr_socket_create和spyr_socket_create调用。

完成这两步后,进行了进程的信号MASK设置和安装信号处理器,相关代码如下(Daemon.c:125-138):

main.c

125: /* set up signal mask (will be inherited by other threads) */

126:

127: sigfillset(&(act.sa_mask));

128: pthread_sigmask(SIG_BLOCK, &(act.sa_mask), NULL);

129:

130: act.sa_handler = spyr_fault;

131:

132: for(i = 0; i < SPYR_MAX_SIGS; i++)

133: sigaction(spyr_daemon_sigs[i].sig, &act, NULL);

134:

135: /* start sig listener */

136:

137: pthread_create(&spyr_daemon_sigthread, NULL, spyr_daemon_sigwait,

138: NULL);

其中,137-138代码部分创建了一个线程Listener,它去执行spyr_daemon_sigwait函数代码,目的就是去等待

信号,并进行信号判断,如果信号值是SIGHUP,那么将系统重新启动,如果是SIGTERM,那么将系统关闭。这就是所有

spyr_daemon_int完成的功能。列表如下:

1、调用openlog启动syslog功能

2、创建服务端口、管理端口

3、安装信号处理程序,并制定一个线程(spyr_daemon_sigthread)来处理相关信号

完成spyr_daemon_init后,程序运行spyr_worker_int来进行worker结构资源的申请。worker_t的结构如下:

typedef struct worker_t

{

int active; ///<此线程worker目前的状态

pthread_t thread;

pthread_attr_t attr; ///<此线程worker属性结构

int sd; ///<此线程worker操作的连接描述字

time_t when; ///<此线程worker本次任务开始执行时间

char addr[SPYR_MAX_ADDRLEN]; ///<此线程worker应答的客户端地址

char label[SPYR_MAX_LABELLEN];

int timeout; ///<此线程worker任务超时时间值

int module;

int logtype;

FILE *logendpoint;

} worker_t;

申请完成worker资源后,调用spyr_socket_init(),目前此函数并没有实质代码。然后是调用spyr_module_int。

看官老爷注意,在spyr_module_init中,Spyrius完成了动态plug-in的功能。在spyr_module_init中,调用

spyr_module_parseconfig来对命令行-c指定的配置文件进行格式化处理,当然,这部分少不了文件IO和字符串处理,不过

我觉得Mark Lindner先生字符处理的部分写的有些乱,呵呵~

在这里,大致的意思是要为plug-in的模块进行设置,相关结构如下:

typedef struct module_t

{

int enabled; ///<此模块是否启动

char name[12]; ///<模块名称

char login[12]; ///<模块是否需要注册名称

char passwd[12]; ///<模块名称匹配的密码

char file[128]; ///<模块所在的文件(动态库文件)

char funcname[40]; ///<模块暴露的函数名称(dlsym要挂接的函数名)

void *handle; ///< dlopen后返回的值

char *env; ///<运行所需的env

int (*func)(worker_t *); ///<dlsym返回函数指针

pthread_mutex_t mutex; ///<线程的互斥对象

} module_t;

可以看到,每一个设置的plug-in,都被填充到一个modult_t结构中。这就是spyr_module_init和spyr_module_parseconfig所作

的工作。

回到main.c中,此时已经完成module_int,接下来是spyr_admin_init。这是完成管理线程部分的工作的代码。

main.c

00217: if(!spyr_admin_init())

管理线程其实也是用worker_t结构,不过创建线程时,指定的任务是spyr_admin。参看:

Admin.c

0090 pthread_create(&(admin.thread), &(admin.attr), (void *)spyr_admin, NULL);

在spyr_admin中,有大量的代码是用来和CLIENT来通信的。我们可以通过连接到管理端口来查看当前配置的plug-in列表,同时

可以指定某一个plug-in的当前运行的线程状态、是否重新加载、是否停止等。这都是在spyr_admin中完成的。看官老爷注意的一点

是,当要对某一线程进行操作时,代码使用spyr_worker_locktable和spyr_worker_unlocktable来对线程进行锁定,以免此过程出

现不同步的错误。

spyr_admin完成后,main.c中就剩下最后一个spyr_listen函数,此时Spyrius主进程也就绪,进入服务端口accept状态。

Listen.c

0101 void spyr_listen()

0102 {

0103 int cs;

0104

0105 for(;;)

0106 {

0107 if((cs = spyr_socket_accept(ms)) < 0)

0108 continue;

0109

0110 else if(spyr_worker_create(cs) < 0)

在spyr_socket_accept中使用一个accept来进入阻塞状态,如0184行代码所示。

Socket.c

0178 int spyr_socket_accept(socket_t *s)

0179 {

1080 int cs;

0181

0182 /* accept a connection */

0183

0184 if((cs = accept(s->sd, (struct sockaddr *)&(s->sin), &(s->slen))) < 0)

0185 {

0186 syslog(LOG_WARNING, "accept(): %s", strerror(errno));

0187 return(-1);

0188 }

0189

0190 spyr_socket_unblock(cs);

0191

0192 return(cs);

0193 }

看官老爷注意0190行的spyr_socket_unblock函数,此函数将accept返回的cs进行Unblock处理,也就是将描述字设置为非阻塞状

态,此过程虽然简单,但在网络服务器中,使用非阻塞IO(Non-Block IO),相比阻塞IO,效果却是非常明显。尤其是同时要处理多

个连接时。我曾经写过一个非阻塞IO的的程序测试,Server接受Client的大数据传送信息,同时将收到的信息进行写文件操作,利

用单缓冲来做的,将Socket描述字和文件描述字设置为非阻塞+Select后,比用之前时间减少了数倍。

spyr_listen完成accept后(同时设置端口为Non-Block),spyr_worker_create创建worker线程。我们跟进去看一下:

Worker.c

0096 int spyr_worker_create(int sd)

{

int worker;

spyr_worker_locktable();

if(nworkers == config.workers)

{

spyr_worker_unlocktable();

return(-1);

}

/* find free worker slot */

for(worker = 0; worker < config.workers; worker++)

if(!(workers[worker].active)) break;

/* set up worker entry */

workers[worker].sd = sd;

workers[worker].active = 1;

workers[worker].when = time(NULL);

workers[worker].timeout = SPYR_DFL_TIMEOUT;

workers[worker].logtype = SPYR_LOG_SYSLOG;

if(!spyr_socket_getpeername(sd, workers[worker].addr, SPYR_MAX_ADDRLEN - 1))

strcpy(workers[worker].addr, "<unresolved>");

strcpy(workers[worker].label, "-");

pthread_attr_init(&(workers[worker].attr));

pthread_attr_setdetachstate(&(workers[worker].attr),

PTHREAD_CREATE_DETACHED);

/* spawn worker */

pthread_create(&(workers[worker].thread), &(workers[worker].attr),

(void *)spyr_server,

(void *)&(workers[worker]));

nworkers++;

spyr_worker_unlocktable();

return(worker);

0155 }

由于篇幅太长,我就没有标记行号。我们看到,首先,spyr_worker_locktable加锁,防止全局变量config.workers变化,判断

当前workers数目nworkers是否达到配置指定的最大的数目。如果达到,那么此连接将被关闭,返回信息"Too Busy"。没办法,当前

worker都在忙,呵呵。当还有空闲的worker资源可用时,对workers所有的“槽位”进行扫描。用“槽位”来描述,看官老爷觉得形

象么?当找到空闲的“槽位”(workers.active为false),则进行相关设置,譬如Socket描述字,超时时间等等。接下来新创建的线

程放到这个“槽位”中,并且被指定任务为spyr_server。当然,此时的nworkers要++。然后解锁,这个锁的时间够长:)

来看spyr_server:

Listen.c

0124 void *spyr_server(worker_t *self)

我是越来越偷懒了,代码都懒得贴了,呵呵。 继续说,spyr_server代码伊始,对当前工作的线程进行取消点(cancellation)相

关设置,有两个函数:

0132 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &x);

0133 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &x);

对取消点(cancellation)不了解的老爷们可以看看PosixThread手册。

接着,进行线程退出时的回调设置:

0137 pthread_cleanup_push((void *)spyr_worker_cleanup, (void *)self);

好,马上看一下spyr_worker_cleanup做了些什么:

Worker.c

0170 void spyr_worker_cleanup(void *arg)

0171 {

0172 worker_t *w = (worker_t *)arg;

0173

0174 spyr_worker_locktable();

0175

0176 w->active = 0;

0177 spyr_socket_shutdown(w->sd);

0178 nworkers--;

0179

0180 pthread_attr_destroy(&(w->attr));

0181

0181 spyr_worker_unlocktable();

0182 }

呵呵,当线程工作完成后,将所占的worker“槽位”设置为0(w->active = 0;),表示空闲,然后将Socket端口关闭

(spyr_socket_shutdown),将nworkers--,保证有新的请求来的时候,不会判断出错(还记得spyr_worker_create开始的判断么?)

,最后将所占的“槽位”信息进行destoryp(thread_attr_destroy)。

好!到这里,我们已经看清了一个worker线程的创建和消亡过程。

spyr_server剩下的部分,大致是执行动态加载的plug-in部分的代码和进行Socket描述字IO了,不在进行描述。在函数的最后部

分使用

0336 pthread_cleanup_pop(1);

0338 pthread_exit(NULL);

将此线程作了个善终。

至此,Spyrius代码处理框架已经完毕。

Spyrius一个特点是利用线程代替子进程来处理来自客户端的连接,此一个好处是可以避免进程间通讯所带来的消耗,其次是避

免创建新进程产生所带来的消耗。但缺点也是明显。任何的一个worker线程的不稳定,将导致整个Spyrio的Collapse。尤其是当

worker线程执行用户代码时,很难预料到客户程序员会写出什么样的代码(指plug-in部分引起的错误和不稳定)。

Spyrius另一个缺点是,没有预先Spawn一批worker线程。(当然,线程的创建也许速度是很快的。)如果能开始就Spawn一些

Worker线程,那么系统的效率将会更高。

同样,在worker线程的退出上,考虑也不是很完善。应该考虑以下问题:是否完成当前任务后,worker线程就应该退出?还是达

到某些条件后在退出(超时?低于固定数量的worker)?所有这些,也许应该用一个“线程池(ThreadPool)”来描述,Spyrius缺乏一

个好的线程池。

和Mark Lindner有过几次E-mail交流,自己也曾说过要完善这几个地方,可惜一年多的光景过去了,不但一点代码没有写,

Spyrius我几乎都要忘记了,写这篇文章,做些纪念吧!:(

链接:

Spyrius在FreshMeat: http://freshmeat.net/projects/spyrius/

原文来自我的BLogDriver:http://manari.blogdriver.com

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有