1:功能需求
开发一个公共库文件sloop.c,实现三个常用功能以供其它模块调用。三个功能如下:
功能一:实现一般的信号监听,调用模块只需传入要监听的信号和相应的回调函数就可以在信号到时调用回调函数处理信号(优先级高)。
功能二:实现定时器,精度至usecs,调用模块只需传入过期的sec,usec和相应的回调函数就可以在时间到后执行回调函数(可以有一定时间误差)(优先级中)。
功能三:实现套接字的监听,调用模块只需传入要监听的套接字描述符和相应的回调处理函数就可以在描述符就绪是执行回调函数,分为监听读,写两种(优先级低)。
2:设计思路
2.1 技术选择
对于信号监听,使用移植性高的sigaction函数,为了统一管理,将使用pipe(fd)+sigaction + select的模式,选择pipe是为了减少信号处理函数的复杂度,在信号处理函数中只需将收到的信号write给fd[1],然后在select中检测fd[0]的可读状态,就可以得到收到的信号的值。这样就可以将信号的处理过程过渡为文件描述符的监听,与功能三重合,统一实现。这里的pipe其实也可以使用socketpair,但socketpair返回的两个套接字都是全双工的,似乎有些"浪费",而pipe正好的单双工,刚好符合要求也不"浪费"。可参考我的博文socketpair + signal + select 的套路描述了这样做的优势。
对于定时器,大体思路就是登记定时器时传入秒、微妙,过期时间就是当前时间加上传入的秒、微妙,主循环每次将登记的时间与当前时间比较,若当前时间>登记时间,就表示定时到,再执行回调函数,这样可能会造成一定的时间误差。
对于套接字的监听,分为可读和可写两种,选择select就足够满足需求了。
2.2 数据结构
通过上面的分析,我们关心的有三种情况,所以拟定三种结构体来描述它们,即信号结构体struct sloop_struct、时间结构体struct sloop_timeout、套接字结构体struct sloop_socket。那么如何来管理它们呢?最先想到的当然是用链表来进行管理,而且使用双链表,优势在于插入和遍历都比单链表要简易。因为考虑到监听的任务量不会很大,所以使用静态数据的方式替代在堆上分配内存。我们假定最多监听的socket和最多定时器都为128个,而最多监听的信号个数为16个,这样就可以先将静态分配的结构体数组挂在free_socket、free_timeout及free_signal三个双链表中,当调用者登记时,再从free_***双链表中取出挂在readers、writers、signals、timeout等使用的双链表中,这样管理可用的链表和正使用链表。
综上所述,三个结构体定义如下:
1 //记录一个待监听(读 or 写)套接字 2 struct sloop_socket 3 { 4 struct dlist_head list;//双链表挂载点(不用时挂在free_socket,使用时挂在readers or writers) 5 unsigned int flags; 6 int sock;//套接字描述符 7 void * param; 8 sloop_socket_handler handler;//状态就绪回调函数 9 }; 10 11 //记录一个定时器 12 struct sloop_timeout 13 { 14 struct dlist_head list;//双链表挂载点(不用时挂在free_timeout,使用时挂在timeout) 15 unsigned int flags; 16 struct timeval time;//超时时间 17 void * param; 18 sloop_timeout_handler handler;//超时回调函数 19 }; 20 21 //记录一个信号 22 struct sloop_signal 23 { 24 struct dlist_head list;//双链表挂载点(不用时挂在free_signal,使用时挂在signals) 25 unsigned int flags; 26 int sig;//信号值 27 void * param; 28 sloop_signal_handler handler;//信号回调函数 29 };
将free_***等组织到一个结构体中:
1 struct sloop_data 2 { 3 int terminate;//退出标志 4 int signal_pipe[2];//信号监听会使用到的管道 5 void * sloop_data; 6 struct dlist_head free_sockets; 7 struct dlist_head free_timeout; 8 struct dlist_head free_signals; 9 struct dlist_head readers; 10 struct dlist_head writers; 11 struct dlist_head signals; 12 struct dlist_head timeout; 13 };
2.3 双链表操作
双链表的操作通通定义在一个dlist.h文件中,包括添加、删除等一般操作:
/* dlist.h */
1 ifndef _DLIST_H_ 2 #define _DLIST_H_ 3 4 typedef struct dlist_head dlist_t; 5 struct dlist_head 6 { 7 struct dlist_head * next; 8 struct dlist_head * prev; 9 }; 10 11 #define DLIST_HEAD_INIT(e) {&(e),&(e)} 12 #define DLIST_HEAD(name) dlist_t name = {&(name),&(name)} 13 #define INIT_DLIST_HEAD(e) do { (e)->next = (e)->prev = (e); } while (0) 14 15 static inline void __dlist_add(dlist_t * entry, dlist_t * prev, dlist_t * next) 16 { 17 next->prev = entry; 18 entry->next = next; 19 entry->prev = prev; 20 prev->next = entry; 21 } 22 static inline void __dlist_del(dlist_t * prev, dlist_t * next) 23 { 24 next->prev = prev; 25 prev->next = next; 26 } 27 28 /***************************************************************************/ 29 30 #define dlist_entry(e, t, m) ((t *)((char *)(e)-(unsigned long)(&((t *)0)->m))) 31 32 static inline int dlist_empty(struct dlist_head * head) { return head->next == head; } 33 static inline void dlist_add(dlist_t * entry, dlist_t * head) { __dlist_add(entry, head, head->next); } 34 static inline void dlist_add_tail(dlist_t * entry, dlist_t * head) { __dlist_add(entry, head->prev, head); } 35 static inline void dlist_del(dlist_t * entry) 36 { 37 __dlist_del(entry->prev, entry->next); 38 entry->next = entry->prev = (void *)0; 39 } 40 static inline void dlist_del_init(dlist_t * entry) 41 { 42 __dlist_del(entry->prev, entry->next); 43 entry->next = entry->prev = entry; 44 } 45 static inline dlist_t * dlist_get_next(dlist_t * entry, dlist_t * head) 46 { 47 entry = entry ? entry->next : head->next; 48 return (entry == head) ? NULL : entry; 49 } 50 static inline dlist_t * dlist_get_prev(dlist_t * entry, dlist_t * head) 51 { 52 entry = entry ? entry->prev : head->prev; 53 return (entry == head) ? NULL : entry; 54 } 55 56 #endif
有一个宏定义dlist_entry是为了通过链表指针来获得指向整个结构体的指针,因为我们操作的都是结构体的list成员,它是一个指向struct dlist_head结构体的指针,而list又是sloop_***结构体的第一个成员,所以需要将指向list的指针转换为指向list所在的结构体的指针,这样才能去访问此结构体的其它成员。
结语:总体说明了模块的功能和实现的大体方法,描述了结果比较重要的结构体如何组织的和为何这样组织。下面将记录详细的实现过程 sloop公共程序之初始过程