xuht

2012年2月1日

设计模式在C语言中的应用--读nginx源码(转)

市面上的“设计模式“书籍文章,皆针对Java/C++/C#等面向对象语言,似乎离开了面向对象的种种特性,设计模式就无法实现,没有用武之地了。


是这样吗?设计模式的概念是从建筑领域引入的,本身从没歧视过面向过程编程语言,它只是对一类问题的普遍解决方案而已。面向对象语言因为有类、多态等特点,使得开发者们容易达到:隐藏细节、封装变化,而这与设计模式的目的比较一致,所以大师们爱把设计模式与面向对象语言二位一体的使用。然而,存在即合理,C语言直到今日仍然在大型软件工程中担纲主角,其种种设计方法其实与我们通常见到的设计模式本质是相同的。例如nginx这个纯C语言写就的的高性能WEB服务器,就有许多地方使用到了市面书籍提到的设计模式。下面通过nginx源码来看看C语言是怎么做的。当然,UML图都是我根据代码意图所画,并不准确(C语言真没法画UML),只用于方便理解,呵呵。


strategy模式:

该模式用于客户代码在“无知”状态下,可以使用种种不同的实现。下面我们以nginx对网络IO操作的封装部分来看看C语言的实现吧。

设计模式就是通过封装变化来解耦,所以,我们先要找出网络IO操作的变化点来。nginx是跨平台的,它会支持linux、freebsd、solaris等操作系统,而每个操作系统的网络IO操作是不同的,这就是变化点了。

 

所以,nginx首先定义了ngx_os_io_t来封装这些变化。

 

  1. typedef struct {  
  2.     ngx_recv_pt        recv;  
  3.     ngx_recv_chain_pt  recv_chain;  
  4.     ngx_recv_pt        udp_recv;  
  5.     ngx_send_pt        send;  
  6.     ngx_send_chain_pt  send_chain;  
  7.     ngx_uint_t         flags;  
  8. } ngx_os_io_t;  

这里有五个函数指针(*_pt都是函数指针)和一个变量,用于收发网络数据,我把它理解为OO中的abstract class(每个ngx_os_io_t定义的变量都会重新实现这五个函数)。

 

拥有函数指针的struct,我通常认为它们是OO中的abstract class,实现它们的文件(一堆函数)要对应到OO上,我则喜欢把它们当做子类来看。对于void*这样的成员,要根据意图来看了,通常我会转换成聚合加继承的关系。


ngx_io会在相应的ngx_os_specific_init方法中,来策略性的选择到底使用哪个实现。客户代码只需要简单的调用ngx_io中的方法即可。


adapter模式:

这个模式用以适配接口,通常都是我们已经定义好一种接口了,有一个新的实现却有着不同的接口,接下来adapter就开始发力了。下面我们仍然以nginx对网络IO操作的封装部分来看。

linux平台下可能存在普通的IO或者异步IO方式。我们在最初已经封装好ngx_os_io_t接口了,客户代码都是这么直接使用的。现在linux实现了异步IO,而它的调用方式与普通的读写IO接口完全不同,所以,如果要支持aoi就需要一层adapter来适配ngx_os_io_t,这就是adapter方式了。


上图中,ngx_os_aio适配了原生的异步IO接口,这样,用户代码仍然像以前一样,只要直接使用ngx_io中的五个接口方法,当nginx的IO部分支持linux aio后,用户代码不需要修改。


bridge桥模式:

桥模式用于将抽象和实现分离,各自都能独立的变化。下面以nginx的核心概念module举例,虽然有些牵强,因为nginx的代码从来没这么用过:通常都是一个抽象module context只对应着一个实现module来用,但是,毕竟这种结构下还是可以达到抽象与实现分离的目的,桥模式只好对应到这上面了。

nginx是以module的概念贯穿始终的。它有一个基本的抽象层ngx_core_module_t(从意图上判断,context有抽象接口的功能,虽然简单从语法上看不出)。然后,nginx module有三个基本类型,分别是event(处理各种事件模型,如epoll/select等),http(处理各种http协议的事件),mail(处理mail相关的事件)。针对每种类型的module,都有许多个实现,比如event module就有9个实现,这里的每个实现其实也是个子类。


但是,在我们理解桥模式时,这些子类暂时要被看成是event module的实例。代码中看,像ngx_epoll_module这样的子类中,还是把一些通用的细节隐藏给ngx_event_core_module来做(管理这个词更合适)了。从这个角度可以认为,通过context接口,把三个基本module实现分开了。来看看类图:


nginx自己用时,是以ngx_module_t中的type成员来决定使用哪个实现的。目前的nginx代码中,如果用了一种接口就一定会指定相应的type。可是实际上,这也可以用来展示桥模式。以事件module为例来看看:


由于UML本就是针对OO语言的,所以以上我画的类图都比较牵强,什么是继承?什么是聚合?在C语言中,往往都是通过几个函数指针,或者void*指针实现各种封装和多态。没有什么语法上的关联,我就只能从代码意图中来判断了。而代码意图这个比较虚,因为不同的角度理解出来都不一样,所以这个确实不好画。太灵活了点,我只能从一个便于说明的角度来看,例如:上面的ngx_devpoll_module其实就是一个ngx_module_t,呵呵,但是,实际上它最关心的是ngx_event_actions_t的实现,如果完全根据语法来看,根本说不通的。但从代码意图中看,这些module并不关心ngx_module_t,所以我认为,它们只是在实现ngx_event_module_t了。

当然以上只是一家之言,不必当真,如果对nginx源码有研究的话,欢迎各位拍砖。


客观的说,C语言确实在封装上很差,就像nginx,如果我们要开发一个处理http协议的module嵌入进nginx进程,必须了解ngx_http_module里到底做了什么,真没隐藏啥细节,module开发者们表示很郁闷。上面的这些设计模式,只是做到了代码上的解藕。如果nginx用C++写的话,我相信,现在第三方module都能数以万计了。


原文地址:
http://blog.csdn.net/russell_tao/article/details/7220237

posted @ 2012-02-01 18:01 xuht 阅读(456) | 评论 (0)编辑 收藏

2011年10月13日

VC2008中影响exe大小和速度的全部编译选项(转)

 我再次强调,完全脱离编程环境的C/C++学习方法,不是好的方法,现在所谓的环境中立理论就是“什么都不学”理论,VC、GCC,主流的就两个,精通其中一个就能吃遍天下,教材里就应该选择一个大讲特讲!

    作为VC的代表,今天我给大家介绍VC中的编译器选项,全面介绍不需要,MSDN里从头到尾都介绍完了,今天我只讲对生成的exe文件大小和速度有影响的。

    用VC就得用IDE,我也以IDE的工程设置里面的排列顺序介绍,某些选项需要自己手动添加的最后介绍,我后面说的默认值是release的,debug版本一般不需要调选项。

项目 - 属性 - 配置属性 - C/C++,这是编译器选项。

优化:
    通常,算法程序选择最大化速度(/O2),界面程序选择最小化大小(/O1),可以获得最佳的效果。
    优选大小或速度,只有在使用完全优化(/Ox)时才有效,完全优化一般不推荐使用,用处就是可以生成速度与/O2基本相当,但是体积更小的代码(选速度优先的话)。

    其他几个选项实际上已包含在/O1、/O2之中,具体请看MSDN。

代码生成:
    启用字符串池(/GF),会将相同的字符串合并,当然可以减小空间占用,虽然本项目默认没有打开,但是默认的/Zi选项会自动打开/GF,这里打不打开一样。
    启用C++异常:该项默认打开,在C++项目中(比如MFC中),会大大增加程序体积,增加约30%,关闭并不代表try不能用了,但会一定程度上降低健壮性,对于空间要求较高的程序,建议关闭。对于正式项目,请参见MSDN,看看会不会造成不利影响。
    运行库:默认多线程DLL(/MD),体积最优的方案,如果对方没有VS运行时库,选择/MT会将C/C++运行库静态编译,体积增加不少,因此,我的选择一般是程序与redist包一起发布,也就几M,而且以后永远可以接受/MD版本了。
    缓冲区安全检查:关闭的话,减少0.5K~1K体积(默认情况,VC的段长度512字节,因此程序体积变化的最小单位是0.5K)。
    启用增强指令集:真想用SSE3的话去用Intel C++,VS2008只支持到SSE2,而且,在我的机器上貌似使用默认设置就能达到选择SSE2的相同速度,如果安装了Intel C++ 11,可集成与VS2008,同样的地方选择SSE3效果超群
    浮点模型:精确还是快速理论上肯定对速度有影响,但是我极少使用浮点编程,我的方向是系统、安全和密码,都是整数的天下。

高级:
    编译为C还是C++影响不大,这充分说明了C++简单面向对象特性和C效率差不多(如重载,默认情况下,编译器会检查扩展名决定目标代码类型,对于cpp文件,所有的函数都会编译为可重载的类型,但是对效率几乎没有影响)。

项目 - 属性 - 配置属性 - 链接器,这是链接器选项。

输入:
    忽略库只有在库冲突时候才有用,VC绝对不会连接没有调用到的库,哪怕你明确指定了。

清单文件:
    完全使用API编程可以不生成清单。减少约1K体积。
    一般情况下,关闭UAC的那一项,可减少0.5K。

调试:
    关闭“生成调试信息(/DEBUG)”,根据程序规模,可减少1K~几十K。

优化:
    release模式,默认情况下已经该组已经最优了,/OPT:REF和/OPT:ICF已经打开,注意,VS2005、VS2008中Windows 98优化那一项没用,不像VC6取消Windows 98优化可以大大减小体积。因为VS2005、VS2008中段大小已经是512字节,VC6默认4K。

高级:
    指定入口点,可以大大减小程序体积,但是不调用CRT的入口无法自动处理参数,可用GetCommandLine和CommandLineToArgvW这两个API来处理参数。
    随机基址:默认模式启用映像随机化(/DYNAMICBASE),会大大增加程序体积,因为这是个增加程序防反编译、防破解能力的选项。如无需求,请选择禁用映像随机化(/DYNAMICBASE:NO),文件越大,体积缩小越明显,至少30%

命令行:
    小程序,可以指定段大小/ALIGN,/O1编译的化最小可以使用/ALIGN:4,这个选项不推荐,第一有点规模的程序就不能用太小的段,/O2优化的也不能用小段,而且默认的512字节段可以使用UPX压缩,再小就不能了,除非咱们编译那种600字节的Hello World,这个选项意义不大,因此微软才没有给他一个图形选项。
    同样,编译600字节hello world还需要/merge合并段选项,同样不推荐使用。

    有些选项VS2005和VS2003没有,VS2003还包括几个VS2008废除的选项,实际上VC里面程序优化效率最高的个人感觉是VS2003。VC6的界面差别比较大,选项有一定差异,但毕竟都是微软的产品,差别不大,甚至于MASM这个汇编编译器,连接选项大都与VC相同……

    再说一点,VS2008SP1的MFC工程会自动生成巨大的256*256真彩图标,因此默认的MFC对话框程序都有近100K,建议删除多余的图标,配合上述选项能减到10多K


原文地址:http://blog.csdn.net/jackyjkchen/article/details/4676635

posted @ 2011-10-13 16:10 xuht 阅读(871) | 评论 (0)编辑 收藏

仅列出标题  
<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

导航

统计

常用链接

留言簿

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜