JAWS模块分析(小东子)
前言:学习ACE也已经有半年了,虽然还很肤浅,但感觉有那么一点意思了。最近分析了一下JAWS模块,拿出来跟大家共通交流。欢迎大家的讨论。杜绝商业目的,并保持文章的完整性。
本人信箱:yuanjiandong512@163.com
一 JAWS模块概况
总则:
JAWS主要有四个主要分块:并发策略(线程池、每个申请一个线程、单线程),I/O策略(同步,反应式,异步方式),协议处理(管道方式,模块方式),文件系统(LRU,LFU)。
JAWS主要的设计模式,单体模式,抽象工厂模式,工厂方法模式,反应器模式,主动对象模式,流水线模式,服务器运行时配置方式等)
JAWS的架构如图1-1所示:(总体方案)
图1-1 JAWS Web服务器构架
JAWS的并发策略如图1-2所示:
图1-2 JAWS 并发策略(线程池、Thread-per-Request)
JAWS的I/O策略如图1-3所示:
图1-3 JAWS I/O策略(同步策略、反应式、异步方式)
JAWS的协议流水线策略如图1-4所示:
图1-4 JAWS协议流水线策略
JAWS的文件缓存策略如图1-5所示:
图1-5 JAWS文件缓存策略
二 JAWS1程序分析
一 概述
JAWS1的程序实现没有完成全部JAWS的设计思想,主要进行了并发策略、I/O策略的完成。设计结构跟JAWS2比较,没有JAWS2的面向对象结构化强,没有它封装的好。
二 程序流程
第一阶段:MAIN()到数据库的INIT()
1、main() -> ACE_Service_Config.open()(采用运行时配置模式,svc.conf文件配置)加载生成的动态库(此处采用ACE自己的生产动态库的方式,参考书1,p24)(本分析采用默认选项:线程池,同步I/O)
2、http_server::init(),分析参数,根据参数选择并发策略(同步线程池、异步线程池、PER-REQUEST),生成工厂处理方法(Synch_HTTP_Handler_Factory ()、No_Cache_Synch_HTTP_Handler_Factory ())。
3、http_server::init()生成并发策略,调用http_server ->synch_thread_pool()。
4、http_server ->synch_thread_pool()开始侦听,HTTP_Server ->acceptor_.open()。
注意:不管采用什么并发策略都在server调用自己的函数生成并发策略时在自己的函数里面执行了端口侦听,也就是说open()由HTTP_Server完成。
5、http_server ->synch_thread_pool()构建线程池对象,Synch_Thread_Pool_Task t(),并把acceptor_的引用传递过来。然后等待所有的线程退出,ACE_Thread_Manager:tm_.wait (),程序进入循环等待状态。
6、Synch_Thread_Pool_Task的构造函数Synch_Thread_Pool_Task->activate()产生多个线程开始从SVC()开始执行。
注意:多个线程共用一个主动对象,共用一个消息队列,有同一个ACE_Thread_Manager:tm_进行管理,但每个线程有自己的堆。根据需要,可以设计自己的专有线程存储。
第二阶段:线程的SVC()
1、线程进入死循环
2、创建一个ACE_SOCK_Stream stream,然后this->acceptor_.accept (stream)。
注意这里使用的acceptor_是一个加锁的接收器,也就是多个线程在这里等待,仅有一个线程获取锁继续执行。因为线程池共用一个主动对象,所以也共用一个acceptor_,所以必须采用并发策略。采用同一个acceptor_,可以确保在同一个I/O端口上连接,这也是WEB服务器必须要求的。
3、第一步的2步骤生成的工厂Synch_HTTP_Handler_Factory .create_http_handler (),创建一个处理器。(在JAWS2中没有采用这个方法,由于它的面向对象做的更完美,都是通过JAWS_DATA_BLOCK块在流水线中执行完成的,不同的流水线模块完成不同的功能)
4、处理器开始处理到来的客户连接,handler->open (stream.get_handle (), *mb)。
第三阶段:处理器处理客户连接
1、根据需要对SOCK进行了一些设置,通过ACE_OS::setsockopt()。
2、设置接收流为acceptor_.accept (stream)函数连接的stream。
3、调用this->read_complete(),开始接收数据,并开始了一序列的处理。
注意:JAWS1中并没有采用流水线设计模式,但留出了接口虚函数。
4、主要读取客户申请头,并分析,HTTP_REQUEST.CPP,根据分析的头向客户回复HTTP_RESPONSE.CPP。
第四阶段:读取客户发来的数据,分析协议,并进行回复。(本人没有进行仔细分析,本人认为没有采用文件缓存策略)
注:其它并发方式和I/O方式代替响应的方式就可以了,流程没有大的变化,就是具体实现思想有点改变。
三 JAWS2程序分析
一 概述
JAWS2采用了更多的面向对象封装,增加了流水线处理(但跟书1上讲的实现方式不一样,它采用一个循环,不断的往下一下流模块中压入JAWS_DATA_BLOCK,通过改变JAWS_DATA_BLOCK中内容而改变需要完成的任务,而书1上讲的更制式一点)和文件缓存系统。
主要有4块内容设计:
1、JAWS_Dispatch_Policy(抽象工作模式)定义了:并发策略(Thread Pool Task和Thread Per Task)处理器工厂(生成响应的IO处理方式,采用了工厂方法模式)IO接收器(IO Acceptor的方式:Synch、Asynch)IO流接收方法(IO Stream)。
2、JAWS_IO_Handler
3、JAWS_Data_Block(最重要的数据块,主要通过它在流模块中的传递,实现各个流模块的具体功能)
4、JAWS pipeline Handler,流水线,具体的accept(),stream(),文件的传输都在里面实现(具体参加见参考1)
二 程序流程
第一阶段:main()->数据库的init(),在JAWS2源代码中没有实现main(),而仅仅有动态库的源代码。
1、根据配置文件或命令行参数完成dispatch policy结构的初始化(并发策略:policy_.concurrency(),I/O策略:policy_.io(),acceptor策略:policy_.acceptor(),工厂策略:policy_.ioh_factory())
注意:在JAWS2中更多的采用面向对象结构设计,上面的并发策略都采用单体模式设计。
2、并发策略调用make(),JAWS_Thread_Pool_Singleton::instance ()->make();make函数完成线程池的创建,activate();各个线程进入SVC(),并阻塞在getq (mb)调用上。
注意:在JAWS_Concurrency_Base::singleton_mb()中使用了一个小技巧,在这里使用了“守卫锁”实现并发,只有获取锁的线程才能够阻塞在getq (mb)调用上,而其它线程阻塞在“守卫锁”上。但这个程序就只往消息队列里面压了一个JAWS Data Block数据块,阻塞在getq (mb)上的线程获取以后,消息队列里面就没有了,其它线程就不能够获取这个数据块了,而这个数据块是驱动线程能够继续执行下去的条件。在源代码中,采用编程技巧,但阻塞在getq (mb)上的线程获取JAWS Data Block数据块后,把变量mb_acquired_=1,但其它阻塞在“守卫锁”上的线程获取锁后,首先判断这个mb_acquired变量,如果等于1,则不进行getq (mb)调用,而是直接把第一个线程获取的JAWS Data Block数据块返回。
3、acceptor策略开始open(),policy_.acceptor ()->open (inet_addr)。
注意:I/O策略并没有开始accept(),这些都在流水线中完成,通过向主动对象的队列压入数据块,唤醒阻塞的线程,然后线程调用流水线完成accept,客户的申请,客户的应答。
问题:在JAWS2源码中这时候init()就结束了,没有提供到JAWS_Server::open()的调用,而JAWS_Server::open()中才完成policy_.acceptor ()->accept ()。经过查资料,也没有找到在动态加载中默认调用open()方法,有兴趣的可以查一查资料,有没有这个默认调用,发我信箱共同讨论研究。本人现认为,要不在init()中包含到JAWS_Server::open()的调用,要不在main()中包含到JAWS_Server::open()的调用。
第二阶段:JAWS_Server::open()开始执行,完成ACCEPT,客户申请,客户应答等。
1、构建一个JAWS Data Block数据块,有三个变量:JAWS IO Handler(IO处理器)初始化为0,在accept接收以后,才通过象JAWS1那样的Factory工厂创建,并更改JAWS Data Block数据块中这个变量的值;JAWS Dispatch Policy(包含第一阶段初始化的4个策略方式),初始化为第一阶段生成的Policy对象。JAWS Pipeline Handler(管道处理器),初始化为管道的第一个模块JAWS_Pipeline_Accept_Task_Singleton)
注意:1、管道里面有很多模块,第一个模块完成accept接收,第二个模块到倒数第二个模块完成客户的申请,客户应答等,最后一个模块完成事后处理,把动态生成的对象释放等处理。
注意:2、程序在开始把数据块的指针指向第一个模块,处理完第一个模块后,又把数据块的指针指向第二个模块,由于在线程的SVC函数中执行了一个死循环,所以通过改变JAWS Data Block数据块中JAWS Pipeline Handler的参数,实现调用不同的模块完成不同的任务。(跟书1描述的流水线实现有区别,本人认为书1描述的实现方式结构化更好一点,更容易理解)
2、把JAWS Data Block数据块压入主动对象的消息队列,激活阻塞在上面的等待线程,policy->concurrency ()->put (db)。
3、程序进行等待所有的线程退出,ACE_Thread_Manager::instance ()->wait (),主程序结束。
第三阶段:线程从消息队列里面取出数据块进行处理
1、所有线程都从消息队列里面返回一个JAWS Data Block数据块。
2、SVC函数调用svc_loop函数,这是个死循环。主要调用svc_hook函数完成具体的功能。然后自己完成JAWS Data Block数据块中参数的变化,使再一次循环过来,svc_hook函数能够根据改变后的JAWS Data Block数据块中参数执行。
3、从线程池里面取出一个等待线程完成流水线的任务,在这里采用了线程池的半同步/半异步模型。(这一点没有仔细研究,有兴趣的可以仔细分析,通过JAWS_Waiter实现的)
4、把数据块压入JAWS Pipeline Handler模块,task->put (mb)。
第四阶段:模块开始根据消息块进行处理
1、put函数调用handle_put(),然后把消息块的task_指针改为下一模块,具体工作handle_put()完成。
2、第一个模块的handle_put()函数完成accept(),JAWS_IO *io = policy->io (),io->accept (handler)。然后退出,交给下一个模块进行处理。
注意:这里有好几个循环,本人没有仔细分析各个具体循环的用处,循环的主要目的是实现各个模块,完成流的功能,使用多个循环可能是具体管理上的编程技巧。
四JAWS3程序分析
书1:ACE程序员指南——网络与系统编程的实用设计模式
参考1:jaws 2: Refactorization Framework Design and Utilizatin Overview