对 zebra 的一点理解 thread+socket+read部分 (备忘)

来源:互联网 发布:淘宝网买手机壳 编辑:程序博客网 时间:2024/05/18 02:01

一、主要从 daemon 的 thread角度 分析备忘。


注意: 具体函数功能详见附录,分析要结合zebra源代码(thread.c中)。


1、每个daemon(e.g igmp-snooping、ring)都定义一个 master 的 全局变量;

2、master 内 有六个 struct thread_list *read、write 等六个 链表;

3、最小的 执行单元为 thread , 必要的时候会 挂在 对应的 thread_list 上;

4、最终 最重要的是 各个daemon的 main函数最后的 while(thread_fetch(master, &thread))  thread_call(&thrad);


5、thread_fetch 功能是查找master 结构下的 几个 struct thread_list 链;

6、其中, 三个链是 比较 重要的,struct thread_list *eventtimerready;

7、thread_fetch  的查找顺序 也是上边这个 顺序, 所以其没有真正的 优先级 抢断问题(真正的线程pthread);

8、查找每个链的过程  就是 看看 该链上  有没有 准备好的 thread;

9、有的话从 该链上移除, 做适当的 type 等重新赋值, 然后加入到  unuse  链上; 《thread_run() 的执行过程》  

      注:移到 unuse 链上 ,而不是 直接free掉, 是为了以后 有新的thread要挂到  eventtimerready等链上的时                  候,可以直接 来 unuse 链上来取, 不用重新 malloc, 节省时间。

10、然后 返回查找到的 thread,然后main中的while下  调用thread_call(&thrad)  执行 该块 thread;


11、此外 还有 两个重要的  明示给 用户(程序员)的  thread_list * read、write,对应 socket 的 read、write事件;

12、当有 read、write类型的socket 要加入 到list 时, 调用 thread_add_read/write 把相应的 sfd及 func以thread的形         式加入到 对应的  read 或者 write  thread_list;

13、同时,重要的是: thread_add_read 里 只要是把 对应的  sfd 加入到 master->readfd/writefd(fd_set 类型);

14、而后 thread_fetch里调用 select 对 read、write、error 的fd_set进行 监听;

15、并调用两次thread_process_fd把  有事件的fd(FD_ISSET) 对应的 thread  从 thread_list * read、write 链中移除

16、并进一步加入ready链中;

17、在thread_fetch 最后, 再对ready链中 移除头元素,加入unuse 链,返回该thread块,并等待thread_call执行;


18、对于 thread_fetch中timer链的查找,遍历timer链中所有thread,查找超时的 thread,返回该thread:过程如下

19、当用 thread_add_timer 添加 timer 事件时,用参数func、time、master等, 构造一个 thread;

20、其中thread->u.sands 为 (gettimeofday + time),  是未来的 某一个时间值,遍历timer链上的thread时, 再                     gettimeofday一下, 然后与 thread->u.sands对比,确定该thread 是否  超时 可执行;


21、对于thread_fetch中 监听 read、write事件的 select 的timeout 时间问题: 函数 thread_time_wait()

22、(NO sort)遍历 timer链上 所有thread,取最小的  thread->u.sands  ,与现在的gettimeofday比较,若小于(表明最                       小的thread块已经超时)则timeout,应该给一个最小的值,免得timer事件 误差太大,timeout=10us;

                       若大于gettimeofday(sec>=0)(还未超时),则 timeout=该值与gettimeofday之差;再while1的时候下一个                         thread正好超时一点,不会误差太大;

                       若 timer链上无thread,则select 为 阻塞。(timeout=NULL);   (没有事件处理阻塞,进程挂起无影响)

   



二、daemon与vtysh间的通信(依赖daemon的thread机制)


注:①其间的通信主要是 vtysh从终端获取输入的CLI命令,然后解析,根据DEFSH或者其他发往指定daemon;

       ②vtysh进程会和每个daemon进行connect;

       ③connect分两种, 一种是vtysh进程main中 connect_default(rcs默认启动的daemon);

                                       二是 vtysh_send之前 会 connect 对应的 daemon

       


下边结合 daemon (e.g igmp-snooping) 与 vtysh 进程 通信来 举例:

1、 igmp-snooping 的 main 中,如下图所示吧:



2、实际上 但从 发送CLI命令字符 来看, 是daemon端 维护一个 select, vtysh 端 connect及 send;

3、可待续补充......



三、vtysh端的read、write的阻塞与  数据传输 不丢失保证

1、待 补充.....




四、daemon端接收其他报文的socket

1、igmp-snooping 要  申请一个 g_snoop_pkt_sock=socket(AF_INET,SOCK_PACKET,htons(0800));

2、该socket 用来 接收 igmp 报文;

3、根据网上的资料,对于socket类型的文件,不显式用fcntl (sockfd,F_SETFL,O_NONBLOCK) 设定时,默认阻塞;

4、所以 recv(g_snoop_pkt_sock, ,)  为 阻塞的;   但该阻塞 永远不会发生!!!  原因如下:

5、在 igmp-snooping enable中用 thread_add_read 把 g_snoop_pkt_sockadd到read 链里, 即,受select监控。

6、只有在 有 igmp 报文 到达 该g_snoop_pkt_sock  时,select检测到,并执行thread块,即igmp_snooping_read,

      进而才会执行到  阻塞的   recv(g_snoop_pkt_sock, ,),但此时为已经 有报文过来,所以该recv不会阻塞;

7、结论:对于该 接收其他AF_INET报文的 socket,在该架构下, 阻塞与否 几乎无差异!!


8、ring 中 接收ring报文 的   几乎 和上述相同,g_ring_sock = socket(AF_INET, SOCK_PACKET, htons(0x7010));

9、下边的 步骤 同上述 2---7, 另, ring 的 socket 用的 read。 




以下附录两篇 关于 zebra 的 文章, 以防收藏被删:


附录一: 原文链接:http://blog.csdn.net/xuyanbo2008/article/details/7439733


==========================================================

一、线程机制概述

zebra这个软件包整体结构大致可分为两大块:协议模块和守护进程模块。协议模块实现各协议的功能,各协议以子模块的形式加载到zebra中;守护进程模块的功能主要是管理各协议的信令传输、表项操作、系统操作调用等事务,为各协议提供底层信息以及相关的硬件处理等功能支持。Zebra与各协议的交互采用的是C-S模式,在每个协议子模块中均有一个Client端与守护进程模块中的Server端交互,它们所使用的socketzebra内部使用的socket,不与外部交互。


  zebra中的线程是分队列调度的,每个队列以一个链表的方式实现。线程队列可以分成五个列:eventtimerreadyreadwrite。队列的优先级由高到低排列。但是,readwrite队列并不参与到优先级的排列中,实际操作时,如果readwrite队列中的线程就绪,就加入ready队列中,等待调度。调度时,首先进行event队列中线程的调度,其次是timerready


实际上,zebra中的线程是“假线程”,它并没有实现线程中的优先级抢占问题。在zebra的线程管理中,有个虚拟的时间轴,必须等前一时间的线程处理完,才看下一时间的线程是否触发。由于电脑处理速度较快且处理每个线程的时间间隔较小,所以处理其多线程来可以打到类似“并行处理”的效果。


zebra源码中有关线程管理的各个函数放置于zebra-0.95a\lib文件夹的thread.hthread.c两个文件中。



二、线程管理源码分析

这是线程队列中每一个单个线程的代码描述,线程队列被描述成双向链表的形式,thread结构体是双向链表的元素。共有六种线程:readwritetimereventreadyunused。因此,线程队列也有六种。

struct thread

{

unsigned char type;        /* thread类型,共有六种 */

struct thread *next;        /* 指向下一thread的指针,双向链表 */

struct thread *prev;        /*指向前一thread的指针*/

struct thread_master *master;      /* 指向该thread所属thread_master结构体的指针 */

int (*func) (struct thread *); /* event类型thread的函数指针 */

void *arg;               /* event类型thread的参数 */

union {

    int val;                /* event类型thread的第二个参数*/

    int fd;                  /* read/write类型thread相应的文件描述符 */

    struct timeval sands;   /* thread的剩余时间,timeval类型,此结构体定义在time.h中,有两个元素,秒和微秒 */

} u;

RUSAGE_T ru;                    /* 详细用法信息,RUSAGE这个宏在该thread有用法描述时定义为rusage类型,描述其详细进程资源信息,没有用法描述时定义为timeval类型 */

};


一个thread_list结构体描述一个thread双向链表,也即一个进程队列。

struct thread_list

{

struct thread *head;/* 该线程队列头指针 */

struct thread *tail; /* 该线程队列尾指针 */

int count; /* 该线程队列元素数目 */

};


总的线程管理结构体,里面存有六种线程队列,三种文件描述符以及占用空间信息。

struct thread_master

{

//六种线程队列

struct thread_list read;

struct thread_list write;

struct thread_list timer;

struct thread_list event;

struct thread_list ready;

struct thread_list unuse;

//三种文件描述符

fd_set readfd;

fd_set writefd;

fd_set exceptfd;

//thread_master所占空间大小

unsigned long alloc;

};

1.1 相关函数简介

下面给出了zebra关于线程管理的相关函数的简要功能介绍。

1.1.1 thread_master_create ()

为创建一个新的thread_master结构体动态开辟一块内存空间。

1.1.2 thread_list_add ()

list双向链表尾部插入一个新的thread

1.1.3 thread_list_add_before ()

在函数参数point所指向的thread前面插入一个新的thread

1.1.4 thread_list_delete ()

删除参数中指定的thread

1.1.5 thread_add_unuse ()

向指定thead_master中的unused链表尾部插入新thread

1.1.6 thread_list_free ()

从内存中释放掉指定thread_master中的指定thread链表所占空间。

1.1.7 thread_master_free ()

彻底释放指定thread_master所占内存空间。

1.1.8 thread_trim_head ()

若指定thread链表中非空,删除该链表头指针所指thread,并将其返回,即从线程队列中取出一个线程。

1.1.9 thread_empty ()

判断指定thread链表是否为空。

1.1.10 thread_timer_remain_second ()

得到指定线程的剩余时间。

1.1.11 thread_get ()

若指定thread_master中的unuse链表非空,从该队列中取出一个thread,根据参数初始化并返回之。否则,给该thread_master多开辟一块空间给新的thread,根据参数初始化该thread并返回之。

1.1.12 thread_add_read ()

根据所给参数在指定thread_master中添加并初始化一个read类型的thread并返回之。

1.1.13 thread_add_write ()

根据所给参数在指定thread_master中添加并初始化一个write类型的thread并返回之。

1.1.14 thread_add_timer ()

根据所给参数在指定thread_master中添加并初始化一个timer类型的thread。若timer链表不要求排序,则直接返回新thread,若要求排序,则将新thread插入到队列的相应位置后再返回之。

1.1.15 thread_add_event ()

根据所给参数在指定thread_master中添加并初始化一个event类型的thread并返回之。

1.1.16 thread_cancel ()

删除指定thread,删除后将其类型置为THREAD_UNUSED,并将其插入到该thread_masterunuse链表中。

1.1.17 thread_cancel_event ()

将指定thread_masterevent链表中与参数中arg相匹配的thread删除。

1.1.18 thread_timer_wait ()

找出指定thread_mastertimer链表中最小的剩余时间并将其返回。

1.1.19 thread_run ()

将指定thread的值赋给thread类型的fetch,然后将其类型置为THREAD_UNUSED,并将其插入unuse链表,返回fetch

1.1.20 thread_process_fd ()

将指定thread链表中的元素取出插入到该thread_masterready链表中,返回该链表中插入元素的个数。

1.1.21 thread_fetch ()

若指定thread_masterevent队列非空取出其头元素并用run函数处理。取出并用run函数处理timer队列中每一个之前创建的线程。若指定thread_masterready队列非空取出其头元素并用run函数处理。拷贝该thread_master的文件描述符。将readwrite链表插到ready链表中,再从ready链表取头元素用run函数处理。如此无限循环下去直到所有进程都处理完。

1.1.22 thread_consumed_time ()

得到该进程所耗费的时间。

1.1.23 thread_call ()

执行该thread中的功能函数,如果该thread持续时间超过CPU规定的独占时间,发出警告。

1.1.24 thread_execute ()

根据参数创建一个event类型的thread并用thread_call()函数对其进行处理






附录二   原文链接:http://blog.chinaunix.net/uid-20608849-id-2103544.html


对zebra的一点思考(Think Of ZEBRA) 

------------------------------------------
本文系作者原创,欢迎转载!
转载请注明出处:netwalker.blog.chinaunix.net
------------------------------------------
 
此文并不针对zebra的应用,甚至不是一个架构的分析,只是对于Zebra的一点儿思考。
 
Zebra 设计得是如此简洁明快。每一种数据结构均对应于一定的应用,它们之间以一种松耦合的方式共存,而多种数据结构组成的功能模块几乎完美的结合在一起,完成了非常复杂的功能。它的设计思想就在于对C语言面向对象式的应用。
 
虽然很多程序均借鉴面向对象设计方式,但是Zebra的代码风格是易读的,非常易于理解和学习,与此同时,Zebra使用了丰富的数据结构,比如链表、向量、表和队列等,它的松耦合方式使得每一数据结构封装的功能模块很容易被精简剥离出来,以备我们特殊的应用。这就是我写下Think Of ZEBRA非常重要的原因!
 
1.ZEBRA中的thread

提起thread就会让人想起线程,Linux中的线程被称为pthread,这里的thread不是pthread,因为它只是对线程的应用层模拟。ZEBRA借助自己的thread结构,将所有的事件(比如文件描述的读写事件,定时事件等)和对应的处理函数封装起来,并取名为struct thread。然后这些threads又被装入不同的“线程“链表挂载到名为thread_master的结构中,这样所有的操作只需要面向thead_master。
 
  1. /* Thread itself. */
  2. struct thread
  3. {
  4.   unsigned char type;        /* thread type */
  5.   struct thread *next;        /* next pointer of the thread */
  6.   struct thread *prev;        /* previous pointer of the thread */
  7.   struct thread_master *master;    /* pointer to the struct thread_master. */
  8.   int (*func) (struct thread *); /* event function */
  9.   void *arg;            /* event argument */
  10.   union {
  11.     int val;            /* second argument of the event. */
  12.     int fd;            /* file descriptor in case of read/write. */
  13.     struct timeval sands;    /* rest of time sands value. */
  14.   } u;
  15.   RUSAGE_T ru;            /* Indepth usage info. */
  16. };

  17. /* Linked list of thread. */
  18. struct thread_list
  19. {
  20.   struct thread *head;
  21.   struct thread *tail;
  22.   int count;
  23. };

  24. /* Master of the theads. */
  25. struct thread_master
  26. {
  27.   struct thread_list read;
  28.   struct thread_list write;
  29.   struct thread_list timer;
  30.   struct thread_list event;
  31.   struct thread_list ready;
  32.   struct thread_list unuse;
  33.   fd_set readfd;
  34.   fd_set writefd;
  35.   fd_set exceptfd;
  36.   unsigned long alloc;
  37. };

thread_master线程管理者维护了6个“线程“队列:read、write、timer、event、ready和unuse。read队列对应于描述符的读事件,write队列对应于描述符的写事件,timer通常为定时事件,event为自定义事件,这些事件需要我们自己在适合的时候触发,并且这类事件不需要对描述符操作,也不需要延时。ready队列通常只是在内部使用,比如read,write或event队列中因事件触发,就会把该”线程”移入ready队列进行统一处理。unuse是在一个”线程”执行完毕后被移入此队列,并且在需要创建一个新的”线程”时,将从该队列中取壳资源,这样就避免了再次申请内存。只有再取不到的情况下才进行新”线程”的内存申请。

 

1.2 线程管理者中的"线程"链表函数


struct thread_list是一个双向链表,对应的操作有:
//添加thread到指定的链表中的尾部
static void thread_list_add (struct thread_list *list, struct thread *thread);
//添加thread到指定的链表中指定的point前部,它在需要对链表进行排序的时候很有用
static void thread_list_add_before (struct thread_list *list, 
   struct thread *point, 
   struct thread *thread);
//在指定的链表中删除制定的thread
static struct thread *thread_list_delete (struct thread_list *list, struct thread *thread);
//释放指定的链表list中所有的thread, m 中的alloc减去释放的"线程"个数
static void thread_list_free (struct thread_master *m, struct thread_list *list);
//移除list中的第一个thread 并返回
static struct thread *thread_trim_head (struct thread_list *list);

 

1.3 thread中的read队列


考虑这样的应用:创建一个socket,并且需要listen在该socket上,然后读取信息,那么使用read队列是不二选择。下面是一个例子,这个例子将对标准输入文件描述符进行处理:

  1. static int do_accept (struct thread *thread)
  2. {
  3. char buf[1024] = "";
  4. int len = 0;
  5.     
  6. len = read(THREAD_FD(thread), buf, 1024);    
  7. printf("len:%d, %s", len, buf);
  8. return 0;
  9. }

  10. int main()
  11. {
  12.     struct thread thread;

  13.     // 创建线程管理者
  14.     struct thread_master *master = thread_master_create();    

  15.     // 创建读线程,读线程处理的描述符是标准输入0,处理函数为do_accept
  16.     thread_add_read(master, do_accept, NULL, fileno(stdin));
  17.     
  18.     // 打印当前线程管理者中的所有线程
  19.     thread_master_debug(master);
  20.     
  21. // thread_fetch select所有的描述符,一旦侦听的描述符需要处理就将对应的”线程”        的地址通过thread返回
  22.     while(thread_fetch(master, &thread))
  23.     {
  24.      // 执行处理函数
  25.        thread_call(&thread);
  26.        thread_master_debug(master);

  27.       // 这里为什么需要再次添加呢?
  28.       thread_add_read(master, do_accept, NULL, fileno(stdin));
  29.       thread_master_debug(master);
  30.     }    
  31.     
  32.     return 0;
  33. }

 

编译执行,得到如下的结果:
// 这里readlist链表中加入了一个"线程",其他链表为空
-----------
readlist  : count [1] head [0x93241d8] tail [0x93241d8] 
writelist : count [0] head [(nil)] tail [(nil)]
timerlist : count [0] head [(nil)] tail [(nil)]
eventlist : count [0] head [(nil)] tail [(nil)]
unuselist : count [0] head [(nil)] tail [(nil)]
total alloc: [1]
-----------
// 输入hello,回车
Hello

// thread_call调用do_accept进行了操作
len:6, hello

// 发现“线程“被移入了unuselist
-----------
readlist  : count [0] head [(nil)] tail [(nil)]
writelist : count [0] head [(nil)] tail [(nil)]
timerlist : count [0] head [(nil)] tail [(nil)]
eventlist : count [0] head [(nil)] tail [(nil)]
unuselist : count [1] head [0x93241d8] tail [0x93241d8]
total alloc: [1]
-----------

//再次调用thread_add_read发现unuselist被清空,并且”线程“再次加入readlist
-----------
readlist  : count [1] head [0x93241d8] tail [0x93241d8]
writelist : count [0] head [(nil)] tail [(nil)]
timerlist : count [0] head [(nil)] tail [(nil)]
eventlist : count [0] head [(nil)] tail [(nil)]
unuselist : count [0] head [(nil)] tail [(nil)]
total alloc: [1]
-----------

1.4 thread_fetch 和thread_process_fd


顾名思义,thread_fetch是用来获取需要执行的线程的,它是整个程序的核心。这里需要对它进行重点的分析。

  1. struct thread *thread_fetch(struct thread_master *m, struct thread *fetch)
  2. {
  3.   int num;
  4.   int ready;
  5.   struct thread *thread;
  6.   fd_set readfd;
  7.   fd_set writefd;
  8.   fd_set exceptfd;
  9.   struct timeval timer_now;
  10.   struct timeval timer_val;
  11.   struct timeval *timer_wait;
  12.   struct timeval timer_nowait;

  13.   timer_nowait.tv_sec = 0;
  14.   timer_nowait.tv_usec = 0;

  15.   while(1)
  16.   {
  17.     /* 最先处理event队列 */
  18.     if((thread = thread_trim_head(&m->event)) != NULL)
  19.             return thread_run(m, thread, fetch);

  20.     /* 接着处理timer队列 */
  21.     gettimeofday(&timer_now, NULL);
  22.     for(thread = m->timer.head; thread; thread = thread->next)
  23.     { 
  24.        /* 所有到时间的线程均将被处理 */
  25.         if(timeval_cmp(timer_now, thread->u.sands) >= 0)
  26.          {
  27.          thread_list_delete(&m->timer, thread);
  28.          return thread_run(m, thread, fetch);
  29.         }
  30.     }

  31.     /* 处理ready中的线程 */
  32.     if((thread = thread_trim_head (&m->ready)) != NULL)
  33.        return thread_run(m, thread, fetch);

  34.     /* Structure copy. */
  35.     readfd = m->readfd;
  36.     writefd = m->writefd;
  37.     exceptfd = m->exceptfd;

  38.     /* Calculate select wait timer. */
  39.     timer_wait = thread_timer_wait(m, &timer_val);
  40.     
  41.     /* 对所有描述符进行listen */
  42.     num = select(FD_SETSIZE, &readfd, &writefd, &exceptfd, timer_wait);
  43.         xprintf("select num:%d\n", num);
  44.     if(num == 0)
  45.             continue;

  46.     if(num < 0)
  47.     {
  48.          if(errno == EINTR)
  49.          continue;
  50.          return NULL;
  51.     }

  52.       /* 处理read中线程 */
  53.     ready = thread_process_fd(m, &m->read, &readfd, &m->readfd);

  54.       /* 处理 write中线程 */ 
  55.       ready = thread_process_fd(m, &m->write, &writefd, &m->writefd);

  56.       if((thread = thread_trim_head(&m->ready)) != NULL)
  57.        return thread_run(m, thread, fetch);
  58.   }
  59. }

显然,Zebra中的thread机制并没有真正的优先级,而只是在处理的时候有些处理一些队列。他们的次序是:event、timer、 ready、 read和write。后面代码分析会得出read和write并没有明显的先后,因为它们最终都将被移入ready然后再被依次执行。而select同时收到多个描述符事件的概率是很低的。

 

thread_process_fd对于read和write线程来说是另一个关键的函数。

 

  1. Int thread_process_fd (struct thread_master *m, struct thread_list *list,
  2.          fd_set *fdset, fd_set *mfdset)
  3. {
  4.   struct thread *thread;
  5.   struct thread *next;
  6.   int ready = 0;
  7.   for (thread = list->head; thread; thread = next)
  8.   {
  9.       next = thread->next;
  10.       if (FD_ISSET (THREAD_FD (thread), fdset))
  11.      {
  12.      assert (FD_ISSET (THREAD_FD (thread), mfdset));
  13.      FD_CLR(THREAD_FD (thread), mfdset);
  14.  // 将侦听到的描述符对应的线程移到ready链表中
  15.      thread_list_delete (list, thread);
  16.      thread_list_add (&m->ready, thread);
  17.      thread->type = THREAD_READY;
  18.      ready++;
  19.     }
  20.     }
  21.   return ready;
  22. }

Thread_process_fd 将侦听到的描述符对应的线程移到ready链表中,并且进行文件描述的清除操作,文件描述符的添加在thread_add_read和thread_add_write中进行。

 

1.5 thread中的其他链表

 

write链表的操作类似于read链表,而event链表是直接操作的。timer链表只是添加对时间的比对操作。
在加入对应的链表时,使用不同的添加函数。
struct thread *
thread_add_read (struct thread_master *m, int (*func) (struct thread *), void *arg, int fd);
struct thread *thread_add_write (struct thread_master *m, int (*func) (struct thread *), void *arg, int fd);
struct thread *thread_add_event (struct thread_master *m, int (*func) (struct thread *), void *arg, int fd);
struct thread *thread_add_timer (struct thread_master *m, int (*func) (struct thread *), void *arg, int fd);

 

1.6 thread 机制中的其他函数

 

//执行thread
void thread_call (struct thread *thread);

//直接创建并执行,m参数可以为NULL
struct thread *thread_execute (struct thread_master *m,
int (*func)(struct thread *),  void *arg,  int val);

//取消一个线程,thread中的master指针不可为空
void thread_cancel (struct thread *thread);

//取消所有event链表中的参数为arg的线程
void thread_cancel_event (struct thread_master *m, void *arg);

//类似于thread_call,区别是thread_call只是执行,不将其加入unuse链表。thread_run执行后会将其加入unuse链表。
struct thread *thread_run (struct thread_master *m, struct thread *thread,  struct thread *fetch);

// 释放m及其中的线程链表
void thread_master_free (struct thread_master *m);

 

1.7 一些时间相关的函数

 

static struct timeval timeval_subtract (struct timeval a, struct timeval b);

static int timeval_cmp (struct timeval a, struct timeval b);
当然也提供了简单的DEBUG函数thread_master_debug。

 

2.对ZEBRA中thread的应用

 

对thread的应用的探讨是最重要的,也是最根本的。ZEBRA的thread机制,模拟了线程,便于平台间的移植,使流水线式的程序编码模块化,结构化。


线程列表间的组合很容易实现状态机的功能。可以自定义应用层通信协议。比如我们定义一个sysstat的远程监控协议。 Client请求Server,请求Code 可以为SYS_MEM,SYS_RUNTIME,SYS_LOG等信息获取动作,也可以是SYS_REBOOT,SYS_SETTIME等动作请求, Server回应这个SYS_MEM等的结果。通常这很简单,但是如果我们需要添加一些步骤,比如用户验证过程呢?


 

     
  1.               Request Auth
  2. Client-------------------------------->Server
  3.               Response PWD?
  4. Client<--------------------------------Server
  5.               Provide PWD
  6. Client-------------------------------->Server
  7.               Auth Result
  8. Client<--------------------------------Server
  9.               SYS_LOG
  10. Client-------------------------------->Server
  11.               SYS_LOG_INFO
  12. Client<--------------------------------Server

 

再考虑三次认证错误触发黑名单事件!状态机就是在处理完上一事件后,添加不同的事件线程。


 

3.对ZEBRA的思考

 

Zebra由Kunihiro Ishiguro开发于15年前,Kunihiro Ishiguro离开了Zebra,而后它的名字被改成了quagga,以至于在因特网上输入Zebra后,你得到只有斑马的注释。Zebra提供了一整套基于TCP/IP网络的路由协议的支持,如RIPv1,RIPv2的,RIPng,OSPFv2,OSPFv3,BGP等,然而它的亮点并不在于此,而在于它对程序架构的组织,你可以容易的剥离它,使他成为专用的cli程序,也已可以轻易的提取其中的一类数据结构,也可以借用他的thread机制实现复杂的状态机。

编码的价值往往不在于写了多少,而在于对他们的组织!好的组织体现美好的架构、设计的艺术,可以给人启迪,并在此基础上激发出更多的灵感。如果一个初学者想学习程序设计的架构,无疑选择Zebra是一个明智的选择,你不仅可以学到各种数据结构,基于C的面向对象设计,还有CLI,以及各种网络路由协议,最重要是的Zebra条理清晰,代码紧凑,至少不会让你焦头烂额!

如果你不知道代码中的xprintf是怎么一回事,那么看看另一篇文章《一个通用的debug系统 》!


截止 2015.8.18.19.50




0 0