面向对象,服务器架构,设计模式探讨
鹦其鸣声,求其友矣
原著:吕跃强 版权申明:完全属于吕跃强,任何转摘和引用必须指明出处
主 要 内 容
n 1 服务器本身的架构
n 2 设计模式,面向对象在服务器开发中的应用
参考:
网络的基础架构: ACE
高效的并发服务器结构: apache(session独立性较高)
特殊应用服务器 : darwin 流媒体服务器,
1 服务器架构的探讨
1.1迭代vs并发vs混合
迭代:那些对每个事件的处理时间短暂、简单。Session处理不复杂,并且可以分解为多个短暂步骤 ,并且分开处理
并发:session复杂。Session中的阻塞不确定,session上下文复杂。如:对某个中转的session处理,收到read请求,然后与数据库交互,然后与加密机交互,然后向服务器提交请求,读取服务器应答,再加密机交互,数据库,然后向客户端回应。像这种建议用多线程。
混合:平时迭代处理,对消耗大的请求并发处理
1.2 多线程VS多进程
n 多线程:效率高,占用资源小。共享地址空间,线程间影响大
n 多进程:效率低,占用资源大。但是安全,稳定性高。
n Apache 是多进程下面的多线程,整合了效率和安全。
1.3 并发分类
n 1.3.1 每个连接pthread_create一次
就是针对每个事件发生时开一个线程处理,处理完了线程退出。这种模式其实效率在有的系统中还是相当可观的,也就是采用原生线程的平台中。
这种模式在一些压力不大的系统中应用还是挺广泛的。而且编成简单。
1.3.2 预创建线程池模式
n 1.3.2.1预创建半同步/半异步(生产者/消费者)模式
由主线程(生产者线程)倾听事件,然后把事件和参数完一个队列里丢。消费者线程读出队列数据进行处理
一般说来主线程处理i/o事件并解析然后再往队列丢数据,然后消费者读出数据进行应用逻辑处理。当然,主线程也可以不处理i/o事件,而把句柄等直接往队列里丢,让消费者来处理i/o和应用逻辑。
优点:简化编程将低层的异步I/O和高层同步应用服务分离,且没有降低低层服务性能。集中层间通信。另外,适合相互交互的session
缺点 :需要线程间传输数据,因此而带来的动态内存分配,数据拷贝,语境切换带来开销。高层服务不可能从底层异步服务效率中 获益。
n 线程池的调度:可以用操作系统信号量或者条件变量的来,也可以自己管理线程对象,然后由程序具体激活某个线程。
这种模式在很多系统中应用非常广泛
半同步半异步框图
1.3.2.2 预创建领跑者/跟随者模式
n 每次允许一个线程leader等待在事件源集合上。同时其他线程follower 排队等候成为领导者的机会。Leader检测到事件后,马上转入follower并提升一个follower为leader,然后直接处理事件这时处于processer角色,处理完成后成为follower
缺点: 实现复杂性和缺乏灵活性
优点:增强了CPU高速缓存相似性,消除了动态内存分配和线程间的数据交换
需要注意的问题
n 确定句柄类型。UDP并发句柄,TCP socket 为迭代句柄。
n 确定句柄集类型。WaitForMultipleObjects() 是并发的,而select是迭代的
n 事件分离和处理,角色转换。底层操作系统可以将事件在内部排队,直到有leader
n 在OLAP系统中应用非常广。
1.4 内存池的管理
n 重载new/delete
譬如在我的CTcpWrRdSocket中,重载了new实现静态内存池
n 参考STL 配置器allocator 建立动态CMemory类。这个工作还没有做
1.5 服务器几个部分
n 配置文件处理
Apache的配置文件处理系统非常值得深究。 在我的框架中就一个简单类。建议自己继承一个以支持XML格式的配置
n 日志处理
(目前我的日志系统是由C库函数实现的。如果你习惯用<<的话也可以重载<<操作符。需要注意的是endl, hex, showbase等流操作子其实函数。没试过带参数的操作子)
n 边界处理socket (类图在包装器模式一节)
n 事件分离 select epoll_wait (类图在包装器模式一节)
n 线程池 (类图在最后)
n 应用逻辑处理
n 另外服务器的某些子系统可进行动态加载 (ace 和apache)
2 面向对象
2.1 设计方法:面向对象 vs 面向过程
一个程序最重要的要求就是能满足需求并且健壮的跑。对于此无论是面向对象和过程都能很好的满足。
误区:面向对象方法设计出来的软件一定比面向过程的具有更高质量。
误区: 只要学好vc++ 就能精通面向对象的设计方法
当然以后面对类似的需求,我们希望快速的高质量的搭建新系统。这样对软件的扩张性和维护性就提出了相当的要求,这时面向对象的设计方法就大行其道了。而面向过程就显得吃力。
误区:只要用了类(有的仅仅是类,就连继承都没有,更不用说多态了),就是面向对象了。
其实用了类只是基于对象编程
n 面向过程一般按照功能进行分解,最后分解成函数。这样在非常大的软件系统中存在极大的耦合(外部变量,函数耦合),并且难于管理,且扩张性和维护性不高。就是变量名和函数名管理就很头痛。而现实世界中的物体往往是属性和行为的复合体。所以面向过程适合那些以功能操作为主,对扩张性要求不高的系统。
n Rational统一过程(RUP):起始,细化,构建,移交的不断重复过程。敏捷开发(XP):其实也就是跌代。这里对象技术至关重要,否则原来的代码无法跌代,得打掉重写。上一个周期的跌代仅是验证了可行性。
n Rup和XP不是培训几周就可以实现的。
2.2 面向对象的原则
n 开闭原则:一个模块对扩张开放,对修改关闭
n 完全替换原则:派生类可完全替换基类
n 非循环依赖原则:包与包直接切忌循环依赖
n 依赖倒置原则:依赖抽象,而不要依赖与具象
n 优先使用聚合,而不是继承
n 为人写代码:代码风格要统一,简洁
开闭原则和依赖倒置原则解释
2.3 面向对象设计
2.3.1 类的粒度:共性 VS 个性
n 日志:日志类的分类根据使用的要求,日志可以远程本地, 滚动 按条,有前缀 无前缀分。远程的还可以TCP UDP分。然后除UDP外所有的类都可以再继承实现一个线程安全的。其实还可以再分。就是这样从类图看就已经非常累赘了。
n 继承的粒度和减少使用者的分枝代码矛盾把握
n 使用聚合减少类的种类(较大不同时)
n 就使用(属性)开关量减少
共性 VS 个性 类粒度的把握
2.3.2 类职责分配
n 通用职责分配软件模式(GRASP):设计原则的总结和概括。
n 9种职责分配模式:专家 创建者 低耦合 高内聚 控制者 多态 纯虚构 中介者 不要和陌生人讲话
2.3.2.1 不要和陌生人讲话
Class CInerDataWkThread
{
private:
Creator * m_ptReactor;
public:
ThreadFunc(void)
{
if( false==m_bIsStatic )
n {
n if( !SetAynCancel() )
n { return -1; }
n }
n while(1)
n {
n Wait();
n //和陌生人CObserver说话了,错误
n m_ptReactor->GetObserver()->EventProcess(&m_tEventPara);
n // 只和m_ptReactor说话,由m_ptReactor负责和CObserver说话,正确
n m_ptReactor->EventNotify();
n }
n return 0;
n }
n }
2.3.2.2 专家:与封装特性一致
这里m_tQueueTData 暴露在两个类中,导致到处是 Lock UnLock代码
根据专家原则,应该对此单独做一个类使其专门完成此职责
2.4 设计模式:变化之道 vs 永恒之美
n 模式:每个模式描述了一个在我们的环境中不断发生的问题,并且描述了解决该问题的核心方案,这样你就可以多次使用这一解决方案,不必重新劳动。
n 模式的组成:包括名称,解决的问题,方案和使用模式的结果。
n 模式的精髓:1 适应变化 2 针对接口编程,而不是实现编程(其实就是virtual + 继承)3 优先使用聚合而不是继承
2.4.1 模式的分类
n 架构模式:1.1 分层模式 1.2 管道和过滤器 1.3 黑板 2.1 C/S p2p B/S 3. MVC
n 设计模式:
n 类模式:工厂方法,适配器,模版方法,解释器
n 对象模式: 抽象工厂,原型模式,单件,桥接 复合 装饰 外观 享元 代理 命令 跌代器 中介者 观察者 职责链 策略
2.4.2 包装器模式: 通一接口VS暴露细节
n 也叫适配器(adapter)
n 定义: 将一个类的接口或一组API转换成用户希望的另外一个接口。
n 标准的可以以继承或聚合的方式实现,推荐聚合
2.4.3 桥接模式
n 一个抽象有多个实现时,通常用继承协调。抽象类定义接口,具体子类则用不同的方式实现,此方法有时不够灵活。继承机制将抽象和实现部分固定在一起。
n 感觉把一一列举变成排列组合,一次一点修改相关全改(举个例子:9个十位数和10个个位数组成90个两位数,不用烈举90个了。我用了10 个数,原来是10-19,现在变为20-29 ,只需1变为2 就可以了)
n 意图:把抽象部分和实现部分分离,使他们可以独立的变化
2.5 观察者模式:间接依赖VS直接依赖
n 谁或何时触发更新,目标的删除,观察多个目标,参数推拉模型
n 观察者在服务器中的变体:显示的指定感兴趣的改变 反应器模式
直接依赖变成间接依赖
2.6单件模式:隐式全局VS显式全局
n 不用释放内存
n 模式介绍:
n 全局变量的头痛
n 自创的多件模式
Class A
{
Public:
Static A *Instance();
~A();
Protected:
A();
Private:
Static A* m_pThis;
}
A *A::m_pThis=NULL;
A *A::Instance()
{
If( NULL==m_pThis)
{ m_pThis = new A(); return m_pThis; }
Else { return m_pThis; }
}
自创的多件模式
class CAnonymSem
{
public:
CAnonymSem();
~CAnonymSem();
static CAnonymSem *GetFirstSingleton(void);
protected:
static CAnonymSem *m_pFirst;
sem_t m_tSem;
};
CAnonymSem *CAnonymSem::m_pFirst=NULL;
CAnonymSem *CAnonymSem::GetFirstSingleton(void)
{
if( NULL==m_pFirst )
{
m_pFirst=new CAnonymSem();
return m_pFirst;
}
else {return m_pFirst; }
}
2.7 模版方法:依赖倒置vs正向依赖
好莱坞原则
class CAppServer
{
public:
void Run(void)
{
Daemon();
SignalInit();
RealRun();
}
protected:
int Daemon();
int SignalInit();
virtual int RealRun()
{ cout<<"hello, in CAppServer::RealRun()"<<endl; }
}
class CMyServer:public CAppServer
{
protected:
int RealRun()
{ cout<<"hello, in CMyServer::RealRun()"<<endl; }
}
main()
{
CAppServer *pMyApp = new CMyServer;
pMyApp->Run();
}
3 我的服务器框架类图
谢 谢 大 家,如有建议请发email到以下信箱:
dellme99@163.com,dellme99@126.com
类图贴不上来,请问如何才能把类图贴上来。
另外,如何才能把源代码贴上来