对zebra的一点思考Think_Of_ZEBRA
- 目录
- 对zebra的一点思考(Think Of ZEBRA
对zebra的一点思考(Think Of ZEBRA
此文并不针对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。
- / Thread itself. /
- struct thread
- {
- unsigned char type; / thread type /
- struct thread next; / next pointer of the thread */
- struct thread prev; / previous pointer of the thread */
- struct thread_master master; / pointer to the struct thread_master. */
- int (func) (struct thread ); / event function /
- void arg; / event argument */
- union {
- int val; / second argument of the event. /
- int fd; / file descriptor in case of read/write. /
- struct timeval sands; / rest of time sands value. /
- } u;
- RUSAGE_T ru; / Indepth usage info. /
- };
- / Linked list of thread. /
- struct thread_list
- {
- struct thread *head;
- struct thread *tail;
- int count;
- };
- / Master of the theads. /
- 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;
- unsigned long alloc;
- };
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队列是不二选择。下面是一个例子,这个例子将对标准输入文件描述符进行处理:
- static int do_accept (struct thread *thread)
- {
- char buf[1024] = "";
- int len = 0;
-
- len = read(THREAD_FD(thread), buf, 1024);
- printf("len:%d, %s", len, buf);
- return 0;
- }
- int main()
- {
- struct thread thread;
- // 创建线程管理者
- struct thread_master *master = thread_master_create();
- // 创建读线程,读线程处理的描述符是标准输入0,处理函数为do_accept
- thread_add_read(master, do_accept, NULL, fileno(stdin));
-
- // 打印当前线程管理者中的所有线程
- thread_master_debug(master);
-
- // thread_fetch select所有的描述符,一旦侦听的描述符需要处理就将对应的”线程” 的地址通过thread返回
- while(thread_fetch(master, &thread))
- {
- // 执行处理函数
- thread_call(&thread);
- thread_master_debug(master);
- // 这里为什么需要再次添加呢?
- thread_add_read(master, do_accept, NULL, fileno(stdin));
- thread_master_debug(master);
- }
-
- return 0;
- }
编译执行,得到如下的结果: // 这里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是用来获取需要执行的线程的,它是整个程序的核心。这里需要对它进行重点的分析。
- struct thread thread_fetch(struct thread_master m, struct thread *fetch)
- {
- int num;
- int ready;
- struct thread *thread;
- fd_set readfd;
- fd_set writefd;
- fd_set exceptfd;
- struct timeval timer_now;
- struct timeval timer_val;
- struct timeval *timer_wait;
- struct timeval timer_nowait;
- timer_nowait.tv_sec = 0;
- timer_nowait.tv_usec = 0;
- while(1)
- {
- / 最先处理event队列 /
- if((thread = thread_trim_head(&m->event)) != NULL)
- return thread_run(m, thread, fetch);
- / 接着处理timer队列 /
- gettimeofday(&timer_now, NULL);
- for(thread = m->timer.head; thread; thread = thread->next)
- {
- / 所有到时间的线程均将被处理 /
- if(timeval_cmp(timer_now, thread->u.sands) >= 0)
- {
- thread_list_delete(&m->timer, thread);
- return thread_run(m, thread, fetch);
- }
- }
- / 处理ready中的线程 /
- if((thread = thread_trim_head (&m->ready)) != NULL)
- return thread_run(m, thread, fetch);
- / Structure copy. /
- readfd = m->readfd;
- writefd = m->writefd;
- exceptfd = m->exceptfd;
- / Calculate select wait timer. /
- timer_wait = thread_timer_wait(m, &timer_val);
-
- / 对所有描述符进行listen /
- num = select(FD_SETSIZE, &readfd, &writefd, &exceptfd, timer_wait);
- xprintf("select num:%d\n", num);
- if(num == 0)
- continue;
- if(num < 0)
- {
- if(errno == EINTR)
- continue;
- return NULL;
- }
- / 处理read中线程 /
- ready = thread_process_fd(m, &m->read, &readfd, &m->readfd);
- / 处理 write中线程 /
- ready = thread_process_fd(m, &m->write, &writefd, &m->writefd);
- if((thread = thread_trim_head(&m->ready)) != NULL)
- return thread_run(m, thread, fetch);
- }
- }
显然,Zebra中的thread机制并没有真正的优先级,而只是在处理的时候有些处理一些队列。他们的次序是:event、timer、 ready、 read和write。后面代码分析会得出read和write并没有明显的先后,因为它们最终都将被移入ready然后再被依次执行。而select同 时收到多个描述符事件的概率是很低的。
thread_process_fd对于read和write线程来说是另一个关键的函数。
- Int thread_process_fd (struct thread_master m, struct thread_list list,
- fd_set fdset, fd_set mfdset)
- {
- struct thread *thread;
- struct thread *next;
- int ready = 0;
- for (thread = list->head; thread; thread = next)
- {
- next = thread->next;
- if (FD_ISSET (THREAD_FD (thread), fdset))
- {
- assert (FD_ISSET (THREAD_FD (thread), mfdset));
- FD_CLR(THREAD_FD (thread), mfdset);
- // 将侦听到的描述符对应的线程移到ready链表中
- thread_list_delete (list, thread);
- thread_list_add (&m->ready, thread);
- thread->type = THREAD_READY;
- ready++;
- }
- }
- return ready;
- }
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等的结果。通常这很简单,但是如果我们需要添加一些步骤,比如用户验证过程呢?
- Request Auth
- Client-------------------------------->Server
- Response PWD?
- Client<--------------------------------Server
- Provide PWD
- Client-------------------------------->Server
- Auth Result
- Client<--------------------------------Server
- SYS_LOG
- Client-------------------------------->Server
- SYS_LOG_INFO
- 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系统 》!
对zebra的一点思考Think_Of_ZEBRA相关推荐
- 对Zebra的一点思考(Think of Zebra)
转自:netwalker.blog.chinaunix.net http://blog.chinaunix.net/uid-20608849-id-2103544.html 此文并不是针对Zebra的 ...
- 对 zebra 的一点理解 thread+socket+read部分 (备忘)
一.主要从 daemon 的 thread角度 分析备忘. 注意: 具体函数功能详见附录,分析要结合zebra源代码(thread.c中). 1.每个daemon(e.g igmp-snooping. ...
- mysql 手动写时间_关于数据库中如何存储时间的一点思考
1.切记不要用字符串存储日期 我记得我在大学的时候就这样干过,而且现在很多对数据库不太了解的新手也会这样干,可见,这种存储日期的方式的优点还是有的,就是简单直白,容易上手. 但是,这是不正确的做法,主 ...
- 对于表列数据类型选择的一点思考
对于表列数据类型选择的一点思考 简介 SQL Server每个表中各列的数据类型的选择通常显得很简单,但是对于具体数据类型的选择的不同对性能的影响还是略有差别.本篇文章对SQL Server表列数据类 ...
- 关于STM32驱动DS1302实时时钟的一点思考
关于STM32驱动DS1302实时时钟的一点思考 之前用51驱动过DS1302,没用多久就输出了正确的时间.当时以为这块芯片其实没啥,很简单.但是现在用STM32做项目,用到同样的芯片,以为这有何难, ...
- 对高并发流量控制的一点思考
前言 在实际项目中,曾经遭遇过线上5W+QPS的峰值,也在压测状态下经历过10W+QPS的大流量请求,本篇博客的话题主要就是自己对高并发流量控制的一点思考. 应对大流量的一些思路 首先,我们来说一下什 ...
- 关于c语言结构体偏移的一点思考
注:此处只是利用了编译器的特性来计算结构体偏移 这句话就一笔带过,说得有点牵强附会.以后有时间自己再详细了解一下编译器的特性... more exceptional c++ 中文版 26页 https ...
- App用户体验的一点思考
App用户体验的一点思考 最近我在团队中负责TImers4Me这款Android软件的开发.维护和更新,软件每次在市场上的发布都能得到用户一些有价值的反馈,通过收集整理用户们的使用反馈,我们常能看到一 ...
- 对高并发流量控制的一点思考 推荐
前言 在实际项目中,曾经遭遇过线上5W+QPS的峰值,也在压测状态下经历过10W+QPS的大流量请求,本篇博客的话题主要就是自己对高并发流量控制的一点思考. 应对大流量的一些思路 首先,我们来说一下什 ...
最新文章
- Factory - 工厂模式
- 让Hinton后悔投少了的AI公司:吴恩达弟子徒孙创办,LeCun李飞飞Jeff Dean投资,产品让人无法拒绝...
- 组图:1904年圣路易斯奥运会
- 如何在一小时内更新100篇文章?-Evernote Sync插件介绍
- Java ArrayList的实现原理详解
- springboot Java实现多文件的zip压缩操作 + 通过浏览器下载文件的两种方式
- Skywalking-07:OAL原理——解释器实现
- 罗振宇2021跨年演讲1:长大以后有多少责任和烦恼?
- Dev C++支持c++11标准的编译方法
- 我的知识管理工具列表 zz
- 最新emoji表情代码大全_如何给微信公众号菜单添加emoji(亲测有效)
- VS2013下载网址及破解注册码
- 在线Java/C++/C语言/Python/web网页编辑器(IDE)整理
- OpenGL: WIN7的64BIT下OPENGL着色语言(GLSL)开发环境配置
- 阿里巴巴校招内推一面总结
- 购买实体网站服务器,实体服务器购买
- Python的爬虫程序
- python----引用其他py文件中的函数
- python基础经典问题-判断身份证号码是否有效
- 基于cp-abe算法的访问控制方法在linux下的实现和算法优化,基于CP-ABE的访问控制研究...
热门文章
- 在斜坡上哪个物体滚的最快_(教科版)小学科学三年级下册第一单元第4课《物体在斜面上运动》教案...
- 论文复现:WS-DAN细粒度分类问题经典之作
- ssm+java+vue微信小程序的驾校预约管理系统#毕业设计
- Java+JSP基于ssm广州市家教中介服务网站
- 关于android整合好视通SDK经验总结(一)
- 生成对抗网络(GANs)最新家谱:为你揭秘GANs的前世今生
- 视觉机器学习20讲-MATLAB源码示例(17)-RBM学习算法
- 基于RBM的推荐算法
- 吃鸡打不开显示服务器,steam h1z1吃鸡 打不开提示错误。求大神帮助我增福长寿...
- 应用Abaqus有限元软件动力学模块模拟岩石单轴压缩断裂过程