2012年11月27日星期二

【转】CSS z-index 属性的使用方法和层级树的概念

摘自:http://www.neoease.com/css-z-index-property-and-layering-tree/

CSS 中的 z-index 属性用于设置节点的堆叠顺序, 拥有更高堆叠顺序的节点将显示在堆叠顺序较低的节点前面, 这是我们对 z-index 属性普遍的认识. 与此同时, 我们总是对堆叠顺序捉摸不透, 将 z-index 的值设得很大也未必能将节点显示在最前面. 本文将通过一些例子对 z-index 的使用方法进行分析, 并且为各位带入 z-index 层级树的概念.

这个星期我们团队做了一次内部的技术分享, 南瓜小米粥为我们分享了他对 CSS z-index 的理解和引入层级树这个概念. 这个分享的现场效果很好, 所以我也将 z-index 和层级树话题搬到博客来谈一谈.

本文谈及多个影响节点显示层级的规则, 其中用到的所有例子全部罗列在《position 属性和 z-index 属性对页面节点层级影响的例子》中.

目录

顺序规则

如果不对节点设定 position 属性, 位于文档流后面的节点会遮盖前面的节点.

  <div id="a">A</div>  <div id="b">B</div>

CSS z-index 属性顺序规则的例子

定位规则

如果将 position 设为 static, 位于文档流后面的节点依然会遮盖前面的节点浮动, 所以 position:static 不会影响节点的遮盖关系.

  <div id="a" style="position:static;">A</div>  <div id="b">B</div>

CSS z-index 属性定位规则的例子, static

如果将 position 设为 relative (相对定位), absolute (绝对定位) 或者 fixed (固定定位), 这样的节点会覆盖没有设置 position 属性或者属性值为 static 的节点, 说明前者比后者的默认层级高.

  <div id="a" style="position:relative;">A</div>  <div id="b">B</div>

CSS z-index 属性定位规则的例子, relative | absolute | fixed

在没有 z-index 属性干扰的情况下, 根据这顺序规则和定位规则, 我们可以做出更加复杂的结构. 这里我们对 A 和 B 都不设定 position, 但对 A 的子节点 A-1 设定 position:relative. 根据顺序规则, B 会覆盖 A, 又根据定位规则 A' 会覆盖 B.

  <div id="a">  	<div id="a-1" style="position:relative;">A-1</div>  </div>  <div id="b">B</div>

CSS z-index 属性互相覆盖的例子

上面互相覆盖在什么时候用到这样的实现? 看起来偏门, 其实很常用, 比如说, 电子商务网站侧栏的类目展示列表就可以用这个技巧来实现.

下图是某网站的类目展示区域, 二级类目的悬浮层覆盖一级类目列表外框, 而一级类目的节点覆盖二级类目的悬浮层. 如果使用 CSS 实现展示效果, 一级类目的外框相当于上面例子中的 A, 一级类目的节点相当于 A-1, 二级类目的悬浮层相当于 B.

电子商务网站侧栏的类目展示列表

参与规则

我们尝试不用 position 属性, 但为节点加上 z-index 属性. 发现 z-index 对节点没起作用.

  <div id="a" style="z-index:2;">A</div>  <div id="b" style="z-index:1;">B</div>  <div id="c" style="z-index:0;">C</div>

CSS z-index 属性参与规则的例子, 没有明确定位的时候

W3C 对 z-index 属性的描述中提到 在 z-index 属性仅在节点的 position 属性为 relative, absolute 或者 fixed 时生效.

The z-index property specifies the stack order of an element. Only works on positioned elements(position: absolute;, position: relative; or position: fixed;).

  <div id="a" style="z-index:2;">A</div>  <div id="b" style="position:relative;z-index:1;">B</div>  <div id="c" style="position:relative;z-index:0;">C</div>

CSS z-index 属性参与规则的例子, 明确定位的节点才能使用 z-index 属性

默认值规则

如果所有节点都定义了 position:relative. z-index 为 0 的节点与没有定义 z-index 在同一层级内没有高低之分; 但 z-index 大于等于 1 的节点会遮盖没有定义 z-index 的节点; z-index 的值为负数的节点将被没有定义 z-index 的节点覆盖.

  <div id="a" style="position:relative;z-index:1;">A</div>  <div id="b" style="position:relative;z-index:0;">B</div>  <div id="c" style="position:relative;">C</div>  <div id="d" style="position:relative;z-index:0;">D</div>

CSS z-index 属性默认值规则的例子

通过检查我们还发现, 当 position 设为 relative, absolute 或者 fixed, 而没有设置 z-index 时, IE8 以上和 W3C 浏览器 (下文我们统称为 W3C 浏览器) 的 z-index 默认值是 auto, 但 IE6 和 IE7 是 0.

从父规则

如果 A, B 节点都定义了 position:relative, A 节点的 z-index 比 B 节点大, 那么 A 的子节点必定覆盖在 B 的子节点前面.

  <div id="a" style="position:relative;z-index:1;">  	<div id="a-1">A-1</div>  </div>     <div id="b" style="position:relative;z-index:0;">  	<div id="b-1">B-1</div>  </div>

CSS z-index 属性从父规则的例子, 子节点不设定层级

如果所有节点都定义了 position:relative, A 节点的 z-index 和 B 节点一样大, 但因为顺序规则, B 节点覆盖在 A 节点前面. 就算 A 的子节点 z-index 值比 B 的子节点大, B 的子节点还是会覆盖在 A 的子节点前面.

  <div id="a" style="position:relative;z-index:0;">  	<div id="a-1" style="position:relative;z-index:2;">A-1</div>  </div>     <div id="b" style="position:relative;z-index:0;">  	<div id="b-1" style="position:relative;z-index:1;">B-1</div>  </div>

CSS z-index 属性从父规则的例子, 不可逾越的层级

很多人将 z-index 设得很大, 9999 什么的都出来了, 如果不考虑父节点的影响, 设得再大也没用, 那是无法逾越的层级.

层级树规则

可能你会觉得在 DOM 结构中的兄弟节点会拎出来进行比较并确定层级, 其实不然.

  <div id="a" style="position:relative;z-index:2;">  	<div id="a-1" style="position:relative;z-index:0;">A-1</div>  </div>     <div id="b">  	<div id="b-1" style="position:relative;z-index:1;">B-1</div>  </div>

CSS z-index 属性层级树规则的例子

我们认为同时将 position 设为 relative, absolute 或者 fixed, 并且 z-index 经过整数赋值的节点, 会被放置到一个与 DOM 不一样的层级树里面, 并且在层级树中通过对比 z-index 决定显示的层级. 上面的例子如果用层级树来表示的话, 应该如下图所示.

CSS z-index 的层级树

图中虽然 A-1 (z-index:0) 的值比 B-1 (z-index:1) 小, 但因为在层级树里 A (z-index:2) 和 B-1 在一个层级, 而 A 的值比 B-1 大, 根据从父规则, A-1 显示在 B-1 前面.

参与规则 2

前面提到的参与规则认为只要节点的 position 属性为 relative, absolute 或者 fixed, 即可参与层级比较, 其实不准确. 如果所有节点都定义了 position:relative, 并且将 z-index 设为整数值, 根据从父规则, 父节点的层级决定了子节点所在层级.

  <div id="a" style="position:relative;z-index:0;">  	<div id="a-1" style="position:relative;z-index:100;">A-1</div>  </div>     <div id="b">  	<div id="b-1" style="position:relative;z-index:0;">  		<div id="b-1-1" style="position:relative;z-index:10;">B-1-1</div>  	</div>  </div>     <div id="c" style="position:relative;z-index:0;">  	<div id="c-1">  		<div id="c-1-1">  			<div id="c-1-1-1" style="position:relative;z-index:1;">C-1-1-1</div>  		</div>  	</div>  </div>

例子中 A, B-1, C-1-1 作为父节点, z-index 的值相同, 根据顺序规则, C-1-1 在 B-1 之前, B-1 在 A 之前; 又根据从父规则, 无论子节点的 z-index 值是什么, C-1-1-1 在 B-1-1 之前, B-1-1 在 A-1 之前.

CSS z-index 属性参与规则 2 的例子, 所有节点参与层级比较

如果我们将所有父节点的 z-index 属性去除, 诡异的事情发生了. IE6 和 IE7 浏览器显示效果不变, 而 W3C 浏览器的子节点不再从父, 而是根据自身的 z-index 确定层级.

  <div id="a" style="position:relative;">  	<div id="a-1" style="position:relative;z-index:100;">A-1</div>  </div>     <div id="b">  	<div id="b-1" style="position:relative;">  		<div id="b-1-1" style="position:relative;z-index:10;">B-1-1</div>  	</div>  </div>     <div id="c" style="position:relative;">  	<div id="c-1">  		<div id="c-1-1">  			<div id="c-1-1-1" style="position:relative;z-index:1;">C-1-1-1</div>  		</div>  	</div>  </div>

根据默认值规则, IE6 / IE7 和 W3C 浏览器上的元素存在 z-index 默认值的区别. 我们相信, 仅当 position 设为 relative, absolute 或者 fixed, 并且 z-index 赋整数值时, 节点被放置到层级树; 而 z-index 为默认值时, 只在 document 兄弟节点间比较层级. 在 W3C 浏览器中, A, B-1 和 C-1-1 的 z-index 均为 auto, 不参与层级比较.

CSS z-index 属性参与规则 2 的例子, z-index 为 auto 的节点不参与层级比较

而在 IE6 和 IE7 中, 因为 z-index 的默认值是 0, 所以也参与了层级比较.

CSS z-index 属性参与规则 2 的例子, IE6 和 IE7 中 z-index 默认为 0

设置了 position 而没有 z-index 的节点虽然不参与层级树的比较, 但还会在 DOM 中与兄弟节点进行层级比较.

  <div id="a" style="position:relative;">  	<div id="a-1" style="position:relative;z-index:100;">A-1</div>  </div>     <div id="b">  	<div id="b-1">  		<div id="b-1-1" style="position:relative;z-index:10;">B-1-1</div>  	</div>  </div>     <div id="c" style="position:relative;">  	<div id="c-1">  		<div id="c-1-1">  			<div id="c-1-1-1" style="position:relative;z-index:1;">C-1-1-1</div>  		</div>  	</div>  </div>

我们对上个例子改造一下, 将 B-1 的 position 属性删除后, W3C 浏览器显示如下图. 根据定位规则, A 和 C-1-1 会显示在 B-1 的前面; 而根据顺序规则, C-1-1 又显示在 A 前面.

CSS z-index 属性参与规则 2 的例子, position 为 auto 的节点不参与层级树比较, 但仍参与 DOM 兄弟节点间的层级比较, W3C 浏览器

在 IE6 和 IE7 中, 因为 A 和 C-1-1 设置了 position:relative, 而且 z-index 的默认值为 0, 所以也参与层级树比较, 所以有如下效果.

CSS z-index 属性参与规则 2 的例子, position 为 auto 的节点不参与层级树比较, 但仍参与 DOM 兄弟节点间的层级比较, IE6 和 IE7

总结

浏览器节点显示层级是一个费力的活, 今天你觉得 A 区块会永远置顶, 但明天因为需求变动, 突然出现 B 元素需要置顶. 一层一层往上堆砌, 某天回头一看, 发现很多区块交错在一起, 而且他们的值一个比一个大, 根本搞不清头绪. 我觉得在操刀干活之前, 最好先将 position, z-index 和层级的关系搞搞清楚, 以免后患无穷.

另外, 非情非得已, 切勿用 JavaScript 计算 z-index, 并将某个节点的 z-index 设置成所有节点中层级最高.

因为篇幅太长, 本文仅从节点属性角度进行讨论, 没有涉及 select 和 iframe 等特殊页面节点考虑, 如果有机会下次再为大家分享.

2012年11月23日星期五

【转】Apache中配置最大并发用户数 tcp连接设置方法

httpd_mpm.conf文件在你apache安装上当的\\conf\\extra中,还在就是在要apache httpd.conf中把#Include conf/extra/httpd-mpm.conf前面的#号去了哦。 

Apache在配置编译时可以自主的选择想要使用的MPM模块,使用./configure --with-mpm=MPM命令。我们主要了解prefork和worker这两种MPM模块。

Prefork
如果不用“--with-mpm”显式指定某种MPM,prefork就是Unix平台上缺省的MPM。它所采用的预派生子进程方式,用单独的子进程来处理不同的请求,进程之间彼此独立。在make编译和make install安装后,使用httpd -l来确定当前使用的
MPM是prefork.c。查看httpd-mpm.conf配置文件,里面包含如下默认的配置段:

 代码如下复制代码
<IfModule prefork.c>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxClients 150
MaxRequestsPerChild 0
</IfModule>

prefork控制进程在最初建立“StartServers”个子进程后,为了满足MinSpareServers设置的需要创建一个进程,等待一秒钟,继续创建两个,再等待一秒钟,继续创建四个……如此按指数级增加创建的进程数,最多达到每秒32个,直到满足MinSpareServers设置的值为止。这种模式可以不必在请求到来时再产生新的进程,从而减小了系统开销以增加性能。MaxSpareServers设置了最大的空闲进程数,如果空闲进程数大于这个值,Apache会自动kill掉一些多余进程。这个值不要设得过大,但如果设的值比MinSpareServers小,Apache会自动把其调整为MinSpareServers+1。如果站点负载较大,可考虑同时加大MinSpareServers和MaxSpareServers。MaxRequestsPerChild设置的是每个子进程可处理的请求数。每个子进程在处理了“MaxRequestsPerChild”个请求后将自动销毁。0意味着无限,即子进程永不销毁。虽然缺省设为0可以使每个子进程处理更多的请求,但如果设成非零值也有两点重要的好处:1、可防止意外的内存泄漏。2、在服务器负载下降的时侯会自动减少子进程数。因此,可根据服务器的负载来调整这个值。MaxClients是这些指令中最为重要的一个,设定的是Apache可以同时处理的请求,是对Apache性能影响最大的参数。其缺省值150是远远不够的,如果请求总数已达到这个值(可通过ps -ef|grep http|wc -l来确认),那么后面的请求就要排队,直到某个已处理请求完毕。这就是系统资源还剩下很多而HTTP访问却很慢的主要原因。虽然理论上这个值越大,可以处理的请求就越多,但Apache默认的限制不能大于256。ServerLimit指令无须重编译Apache就可以加大MaxClients。ServerLimt应该放在第一个位置,放在其他指令之间不起作用(不明白原因)。

 代码如下复制代码
<IfModule prefork.c>
ServerLimit  10000
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxClients 10000
MaxRequestsPerChild 0
</IfModule>

Worker
相对于prefork,worker全新的支持多线程和多进程混合模型的MPM。由于使用线程来处理,所以可以处理相对海量的请求,而系统资源的开销要小于基于进程的服务器。但是,worker也使用了多进程,每个进程又生成多个线程,以获得基于进程服务器的稳定性。在configure --with-mpm=worker后,进行make编译、make install安装。在缺省生成的httpd-mpm.conf中有以下默认配置段:

 代码如下复制代码
<IfModule worker.c>
StartServers 2
MaxClients 150
MinSpareThreads 25
MaxSpareThreads 75
ThreadsPerChild 25
MaxRequestsPerChild 0
</IfModule>

Worker由主控制进程生成“StartServers”个子进程,每个子进程中包含固定的ThreadsPerChild线程数,各个线程独立地处理请求。同样,为了不在请求到来时再生成线程,MinSpareThreads和MaxSpareThreads设置了最少和最多的空闲线程数;而MaxClients设置了同时连入的clients最大总数。如果现有子进程中的线程总数不能满足负载,控制进程将派生新的子进程。MinSpareThreads和MaxSpareThreads的最大缺省值分别是75和250。这两个参数对Apache的性能影响并不大,可以按照实际情况相应调节。ThreadsPerChild是worker MPM中与性能相关最密切的指令。ThreadsPerChild的最大缺省值是64,如果负载较大,64也是不够的。这时要显式使用ThreadLimit指令,它的最大缺省值是20000。Worker模式下所能同时处理的请求总数是由子进程总数乘以ThreadsPerChild值决定的,应该大于等于MaxClients。如果负载很大,现有的子进程数不能满足时,控制进程会派生新的子进程。默认最大的子进程总数是16,加大时也需要显式声明ServerLimit(最大值是20000)。需要注意的是,如果显式声明了ServerLimit,那么它乘以ThreadsPerChild的值必须大于等于MaxClients,而且MaxClients必须是ThreadsPerChild的整数倍,否则Apache将会自动调节到一个相应值。

 代码如下复制代码
<IfModule worker.c>
ServerLimit 25
ThreadLimit 200
StartServers 3
MaxClients 2000
MinSpareThreads 50
MaxSpareThreads 200
ThreadsPerChild 100
MaxRequestsPerChild 0
</IfModule>

下面是利用Apache自带的测试工具ab对Server进行测试的情况(设定请求的index页面为6bytes,Apache Server配置2cpu 2G memory),cpu%为cpu占用率,mem为内存使用量(M为单位),RequestsPerSecond为每秒处理的请求数。
1、Prefor方式
  (ServerLimit,StartServer,MinSpareServers,MaxSpareServers,MaxClients,MaxRequestPerChild)           

-n/-c(ab参数) Cpu% Mem Requestspersecond 
(-,5,5,10,150,0) 
100000/100 28.8 285 8434 
100000/200 29.2 304 8032 
100000/500 25.3 323 7348 
100000/1000 24.4 330 5886 
(10000,5,5,10,500,0) 
100000/100 28.7 371 8345 
100000/200 27.4 389 7929 
100000/500 24.9 417 7229 
100000/1000 23.4 437 6676 
(10000,5,5,10,1000,0) 
100000/100 28.8 408 8517 
100000/200 27.0 422 8045 
100000/500 24.2 455 7236 
100000/1000 22.5 470 6570 
(10000,5,5,10,1500,0) 
100000/100 29.6 330 8407 
100000/200 28.1 349 8014 
100000/500 26.4 380 7290 
100000/1000 24.0 400 6686 
 

2、Worker方式
(ServerLimt,Threadlimt,Startservers,MaxClients,MinspareThread,MaxspareThread,ThreadperChild,MaxRequestPerChild)
                  

-n/-c(ab参数) cpu% mem RequestsperSecond 
(50,500,5,10000,50,200,200,0) 
100000/100  18.6 188 6020 
100000/200 20.1 195 5892 
100000/500 19.8 209 5708 
100000/1000 22.2 218 6081 
(100,500,5,10000,50,200,100,0) 
100000/100  24.5 240 6919 
100000/200 23.6 247 6798 
100000/500 24.6 254 6827 
100000/1000 22.3 271 6114 
(200,500,5,10000,50,200,50,0) 
100000/100  27.3 301 7781 
100000/200 27.4 307 7789 
100000/500 26.0 320 7141 
100000/1000 21.8 344 6110

相对来说,prefork方式速度要稍高于worker,然而它需要的cpu和memory资源也稍多于woker。

下面贴出我的系统mpm文件

 

 代码如下复制代码

#
# Server-Pool Management (MPM specific)
#

#
# PidFile: The file in which the server should record its process
# identification number when it starts.
#
# Note that this is the default PidFile for most MPMs.
#
<IfModule !mpm_netware_module>
    PidFile "logs/httpd.pid"
</IfModule>

#
# The accept serialization lock file MUST BE STORED ON A LOCAL DISK.
#
<IfModule !mpm_winnt_module>
<IfModule !mpm_netware_module>
LockFile "logs/accept.lock"
</IfModule>
</IfModule>

#
# Only one of the below sections will be relevant on your
# installed httpd.  Use "apachectl -l" to find out the
# active mpm.
#

# prefork MPM
# StartServers: number of server processes to start
# MinSpareServers: minimum number of server processes which are kept spare
# MaxSpareServers: maximum number of server processes which are kept spare
# MaxClients: maximum number of server processes allowed to start
# MaxRequestsPerChild: maximum number of requests a server process serves
<IfModule mpm_prefork_module>
    StartServers          5
    MinSpareServers       5
    MaxSpareServers      10
    MaxClients          150
    MaxRequestsPerChild   0
</IfModule>

# worker MPM
# StartServers: initial number of server processes to start
# MaxClients: maximum number of simultaneous client connections
# MinSpareThreads: minimum number of worker threads which are kept spare
# MaxSpareThreads: maximum number of worker threads which are kept spare
# ThreadsPerChild: constant number of worker threads in each server process
# MaxRequestsPerChild: maximum number of requests a server process serves
<IfModule mpm_worker_module>
    StartServers          2
    MaxClients          150
    MinSpareThreads      25
    MaxSpareThreads      75 
    ThreadsPerChild      25
    MaxRequestsPerChild   0
</IfModule>

# BeOS MPM
# StartThreads: how many threads do we initially spawn?
# MaxClients:   max number of threads we can have (1 thread == 1 client)
# MaxRequestsPerThread: maximum number of requests each thread will process
<IfModule mpm_beos_module>
    StartThreads            10
    MaxClients              50
    MaxRequestsPerThread 10000
</IfModule>

# NetWare MPM
# ThreadStackSize: Stack size allocated for each worker thread
# StartThreads: Number of worker threads launched at server startup
# MinSpareThreads: Minimum number of idle threads, to handle request spikes
# MaxSpareThreads: Maximum number of idle threads
# MaxThreads: Maximum number of worker threads alive at the same time
# MaxRequestsPerChild: Maximum  number of requests a thread serves. It is 
#                      recommended that the default value of 0 be set for this
#                      directive on NetWare.  This will allow the thread to 
#                      continue to service requests indefinitely.                          
<IfModule mpm_netware_module>
    ThreadStackSize      65536
    StartThreads           250
    MinSpareThreads         25
    MaxSpareThreads        250
    MaxThreads            1000
    MaxRequestsPerChild      0
    MaxMemFree             100
</IfModule>

# OS/2 MPM
# StartServers: Number of server processes to maintain
# MinSpareThreads: Minimum number of idle threads per process, 
#                  to handle request spikes
# MaxSpareThreads: Maximum number of idle threads per process
# MaxRequestsPerChild: Maximum number of connections per server process
<IfModule mpm_mpmt_os2_module>
    StartServers           2
    MinSpareThreads        5
    MaxSpareThreads       10
    MaxRequestsPerChild    0
</IfModule>

# WinNT MPM
# ThreadsPerChild: constant number of worker threads in the server process
# MaxRequestsPerChild: maximum  number of requests a server process serves
<IfModule mpm_winnt_module>
    ThreadsPerChild      150
    MaxRequestsPerChild    0
</IfModule>

【转】windows下修改apache并发数

修改apache的最大连接数,方法如下:

步骤一
先修改 /path/apache/conf/httpd.conf文件。
# vi httpd.conf
将“#Include conf/extra/httpd-mpm.conf”前面的 “#” 去掉,保存。 

步骤二
再修改 /path/apache/conf/extra/httpd-mpm.conf文件。
# vi httpd-mpm.conf
找到 这一行

原:

StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxClients 150
MaxRequestsPerChild 0

修改后

ServerLimit 1000 #最大并发
StartServers 10 #默认启动线程数
MinSpareServers 5
MaxSpareServers 15
MaxClients 1000 #apache可以同时处理的请求
ThreadsPerChild 250 #每个进程的线程数,子进程在启动时建立这些线程后就不再建立新的线程了。 默认值是64, 最大值是1920
MaxRequestsPerChild 5000 #默认0 官方参考手册中不建议设置为0, 主要基于两点考虑: (1) 可以防止(偶然的)内存泄漏无限进行,从而耗尽内存; (2) 给进程一个有限寿命, #从而有助于当服务器负载减轻的时候减少活动进程的数量。



注意:
ServerLimit 该指令一定要放在第一行。

修改后,一定不要apachectl restart,而是先 apachectl stop 然后再 apachectl start才可以。

---------------------------------------------------------------------------------------------------------------------------------------

如果你按照上面做的话,你会发现好像没什么效果(至少我是这样),因为win2003下并不是使用mpm_prefork_module ,而是mpm_winnt_module ,ServerLimit 1000在是mpm_winnt_module下是无效的,找了一个下午没有找到增大window下apahce并发数的方法,猜测可能和最大并发数也就是服务器的最大连接数?

2011-8-27

今天又琢磨了一下,还是修改 conf/extra/httpd-mpm.conf 文件,既然windows下是用mpm_winnt_module ,那就修改这个试一试

我的默认是

<IfModule mpm_winnt_module>
ThreadsPerChild 150
MaxRequestsPerChild 0
</IfModule>

ab测试并发大概在250左右时就不行了。

尝试修改

ThreadsPerChild 250
MaxRequestsPerChild 5000

ab测试大概在 300多不行,修改 ThreadsPerChild 还是有效果的

好像最大并发数大概是ThreadsPerChild 的1.5倍(瞎猜的,你试试)

摘自:http://www.linuxso.com/windows/9110.html 

【转】apache并发数调整

摘自:http://blog.csdn.net/nich262/article/details/4069078

手头有一个网站在线人数增多,访问时很慢。初步认为是服务器资源不足了,但经反 复测试,一旦连接上,不断点击同一个页面上不同的链接,都能迅速打开,这种现象就是说明apache最大连接数已经满了,新的访客只能排队等待有空闲的链 接,而如果一旦连接上,在keeyalive 的存活时间内(KeepAliveTimeout,默认5秒)都不用重新打开连接,因此解决的方法就是加大apache的最大连接数。

1.在哪里设置?

服务器的为FreeBSD 6.2 ,apache 2.24,使用默认配置(FreeBSD 默认不加载自定义MPM配置),默认最大连接数是250

在/usr/local/etc/apache22/httpd.conf中加载MPM配置(去掉前面的注释):
# Server-pool management (MPM specific)
Include etc/apache22/extra/httpd-mpm.conf

可见的MPM配置在/usr/local/etc/apache22/extra/httpd-mpm.conf,但里面根据httpd的工作模式分了很多块,哪一部才是当前httpd的工作模式呢?可通过执行 apachectl -l 来查看:
Compiled in modules:
              core.c
              prefork.c
              http_core.c
              mod_so.c

看到prefork 字眼,因此可见当前httpd应该是工作在prefork模式,prefork模式的默认配置是:
<IfModule mpm_prefork_module>
                StartServers                      5
                MinSpareServers                   5
                MaxSpareServers                  10
                MaxClients                      150
                MaxRequestsPerChild               0
</IfModule>

2.要加到多少?

连接数理论上当然是支持越大越好,但要在服务器的能力范围内,这跟服务器的CPU、内存、带宽等都有关系。

查看当前的连接数可以用:
ps aux | grep httpd | wc -l

或:
pgrep httpd|wc -l

计算httpd占用内存的平均数:
ps aux|grep -v grep|awk '/httpd/{sum+=$6;n++};END{print sum/n}'

由于基本都是静态页面,CPU消耗很低,每进程占用内存也不算多,大约200K。

服务器内存有2G,除去常规启动的服务大约需要500M(保守估计),还剩1.5G可用,那么理论上可以支持1.5*1024*1024*1024/200000 = 8053.06368

约8K个进程,支持2W人同时访问应该是没有问题的(能保证其中8K的人访问很快,其他的可能需要等待1、2秒才能连上,而一旦连上就会很流畅)

控制最大连接数的MaxClients ,因此可以尝试配置为:
<IfModule mpm_prefork_module>
                StartServers                      5
                MinSpareServers                   5
                MaxSpareServers                  10
                ServerLimit                    5500
                MaxClients                     5000
                MaxRequestsPerChild               100
</IfModule>

注意,MaxClients默认最大为250,若要超过这个值就要显式设置ServerLimit,且ServerLimit要放在MaxClients之前,值要不小于MaxClients,不然重启httpd时会有提示。

重启httpd后,通过反复执行pgrep httpd|wc -l 来观察连接数,可以看到连接数在达到MaxClients的设值后不再增加,但此时访问网站也很流畅,那就不用贪心再设置更高的值了,不然以后如果网站访 问突增不小心就会耗光服务器内存,可根据以后访问压力趋势及内存的占用变化再逐渐调整,直到找到一个最优的设置值。

(MaxRequestsPerChild不能设置为0,可能会因内存泄露导致服务器崩溃)

更佳最大值计算的公式

apache_max_process_with_good_perfermance < (total_hardware_memory / apache_memory_per_process ) * 2
apache_max_process = apache_max_process_with_good_perfermance * 1.5

 

参考:

apache的参数设置

Apache 2.0性能优化—MPM的选择与配置

如何避免apache的httpd进程占用比较多的内存

对apache中并发控制参数prefork理解和调优

apache 并发链接数的设置

摘自: http://hi.baidu.com/zhangguanshi/item/266258de62d027df241f4020 

作者:老王

如何知道apache是工作在哪个模式下的,答案很简单:apachectl -l即可。

这里,我先以prefork模式为例来说明参数的设置,其缺省设置一般如下:

<IfModule mpm_prefork_module>
         StartServers               5
         MinSpareServers            5
         MaxSpareServers           10
         MaxClients               150
         MaxRequestsPerChild        0
</IfModule>

prefork 控制进程在最初建立“StartServers”个子进程后,为了满足MinSpareServers设置的需要创建一个进程,等待一秒钟,继续创建两个,再等待一秒钟,继续创建四个……如此按指数级增加创建的进程数,最多达到每秒32个,直到满足MinSpareServers设置的值为止。这种模式可以不必在请求到来时再产生新的进程,从而减小了系统开销以增加性能。MaxSpareServers设置了最大的空闲进程数,如果空闲进程数大于这个值,Apache会自动kill掉一些多余进程。这个值不要设得过大,但如果设的值比MinSpareServers小,Apache会自动把其调整为 MinSpareServers+1。如果站点负载较大,可考虑同时加大MinSpareServers和MaxSpareServers。 MaxRequestsPerChild设置的是每个子进程可处理的请求数。每个子进程在处理了“MaxRequestsPerChild”个请求后将自动销毁。0意味着无限,即子进程永不销毁。虽然缺省设为0可以使每个子进程处理更多的请求,但如果设成非零值也有两点重要的好处:1、可防止意外的内存泄漏。2、在服务器负载下降的时侯会自动减少子进程数。因此,可根据服务器的负载来调整这个值。MaxClients是这些指令中最为重要的一个,设定的是 Apache可以同时处理的请求,是对Apache性能影响最大的参数。其缺省值150是远远不够的,如果请求总数已达到这个值(可通过ps -ef|grep httpd|wc -l来确认),那么后面的请求就要排队,直到某个已处理请求完毕。这就是系统资源还剩下很多而HTTP访问却很慢的主要原因。虽然理论上这个值越大,可以处理的请求就越多,但Apache默认的限制不能大于256。ServerLimit指令无须重编译Apache就可以加大MaxClients。

注意,虽然通过设置ServerLimit,我们可以把MaxClients加得很大,但是往往会适得其反,系统耗光所有内存。以我手头的一台服务器为例:内存2G,每个apache进程消耗大约0.5%(可通过ps aux来确认)的内存,也就是10M,这样,理论上这台服务器最多跑200个apache进程就会耗光系统所有内存,所以,设置MaxClients要慎重。

模块安装应该取最小集合:http://hi.baidu.com/thinkinginlamp/blog/item/d677cffc1e083d83b901a016.html

再来看看work模式,缺省参数一般如下:

<IfModule mpm_worker_module>
         StartServers               2
         MaxClients               150
         MinSpareThreads           25
         MaxSpareThreads           75
         ThreadsPerChild           25
         MaxRequestsPerChild        0
</IfModule>

Worker 由主控制进程生成“StartServers”个子进程,每个子进程中包含固定的ThreadsPerChild线程数,各个线程独立地处理请求。同样,为了不在请求到来时再生成线程,MinSpareThreads和MaxSpareThreads设置了最少和最多的空闲线程数;而MaxClients 设置了同时连入的clients最大总数。如果现有子进程中的线程总数不能满足负载,控制进程将派生新的子进程。MinSpareThreads和 MaxSpareThreads的最大缺省值分别是75和250。这两个参数对Apache的性能影响并不大,可以按照实际情况相应调节。 ThreadsPerChild是worker MPM中与性能相关最密切的指令。ThreadsPerChild的最大缺省值是64,如果负载较大,64也是不够的。这时要显式使用 ThreadLimit指令,它的最大缺省值是20000。Worker模式下所能同时处理的请求总数是由子进程总数乘以ThreadsPerChild 值决定的,应该大于等于MaxClients。如果负载很大,现有的子进程数不能满足时,控制进程会派生新的子进程。默认最大的子进程总数是16,加大时也需要显式声明ServerLimit(最大值是20000)。需要注意的是,如果显式声明了ServerLimit,那么它乘以 ThreadsPerChild的值必须大于等于MaxClients,而且MaxClients必须是ThreadsPerChild的整数倍,否则 Apache将会自动调节到一个相应值。 

2012年11月21日星期三

【转】Delphi COM编程介绍

转自http://www.cnblogs.com/abchjb/articles/461725.html

        软件重用是业界追求的目标,人们一直希望能够像搭积木一样随意"装配"应用程序,组件对象就充当了积木的角色。所谓组件对象,实际上就是预定义好的、能完成一定功能的服务或接口。问题是,这些组件对象如何与应用程序、如何与其他组件对象共存并相互通信和交互?这就需要制定?个规范,让这些组件对象按统一的标准方式工作。 
COM是个二进制规范,它与源代码无关。这样,即使COM对象由不同的编程语言创建,运行在不同的进程空间和不同的操作系统平台,这些对象也能相互通信。COM既是规范,也是实现,它以COM库(OLE32.dll和贴OLEAut32.dll)的形式提供了访问COM对象核心功能的标准接口以及一组API函数,这些API函数用于创建和管理COM对象。COM本质上仍然是客户服务器模式。客户(通常是应用程序)请求创建COM对象并通过COM对象的接口操纵COM对象。服务器根据客户的请求创建并管理COM对象。客户和服务器这两种角色并不是绝对的。 
组件对象与一般意义上的对象既相似也有区别。一般意义上的对象是一种把数据和操纵数据的方法封装在一起的数据类型的实例,而组件对象则使用接口(Interface)而不是方法来描述自己并提供服务。所谓接口,其精确定义是"基于对象的一组语义上相关的功能",实际上是一个纯虚类,真正实现接口的是接口对象)(Interface Object)。一个COM对象可以只有一个接口,例如Wndows 95/98外壳扩展;也可以有许多接口,例如ActiveX控件一般就有多个接口,客户可以从很多方面来操纵ActiveX控件。接口是客户与服务器通信的唯一途径。如果一个组件对象有多个接口,则通过一个接口不能直接访问其他接口。但是,COM允许客户调用COM库中的QueryInterface()去查询组件对象所支持的其他接口。从这个意义上讲,组件对象有点像接口对象的经纪人。 
在调用QueryInterface()后,如果组件对象正好支持要查询的接口,则QueryInterface()将返回该接口的指针。如果组件对象不支持该接口,则QueryInterface()将返回一个出错信息。 
所以,QueryInterface()是很有用的,它可以动态了解组件对象所支持的接口。接口是团向对象编程思想的一种体现,它隐藏了COM对象实现服务的细节。COM对象可以完全独立于访问它的客户,只要接口本身保持不变即可。如果需要更新接口,则可以重新定义一个新的接口,对于使用老接口的客户来说,代码得到了最大程度的保护。

Delphi通过向导可以非常迅速和方便的直接建立实现COM对象的代码,但是整个COM实现的过程被完全的封装,甚至没有VCL那么结构清晰可见。

一个没有C++下COM开发经验甚至没有接触过COM开发的Delphi程序员,也能够很容易的按照教程设计一个接口, 
但是,恐怕深入一想,连生成的代码代表何种意义,哪些能够定制都不清楚。前几期 "DELPHI下的COM编程技术"一文已经初步介绍了COM的一些基本概念,我则想谈一些个人的理解,希望能给对Delphi下COM编程有疑惑的朋友带来帮助。

COM (组件对象模型 Component Object Model)是一个很庞大的体系。简单来说,COM定义了一组API与一个二进制的标准,让来自不同平台、不同开发语言的独立对象之间进行通信。COM对象只有方法和属性,并包含一个或多个接口。这些接口实现了COM对象的功能,通过调用注册的COM对象的接口,能够在不同平台间传递数据。

COM光标准和细节就可以出几本大书。这里避重就轻,仅仅初步的解释Delphi如何进行COM的封装及实现。对于上述COM技术经验不足的Delphi程序开发者来说, 
Delphi通过模版生成的代码就像是给你一幅抽象画照着画一样,画出来了却不一定知道画的究竟是什么,也不知该如何下手画自己的东西。本文能够帮助你解决这类疑惑。

再次讲解一些概念 
"DELPHI下的COM编程技术"一文已经介绍了不少COM的概念,比如GUID、CLSID、IID,引用计数,IUnKnown接口等,下面再补充一些相关内容:

COM与DCOM、COM+、OLE、ActiveX的关系

DCOM(分布式COM)提供一种网络上访问其他机器的手段,是COM的网络化扩展,可以远程创建及调用。COM+是Microsoft对COM进行了重要的更新后推出的技术,但它不简单等于COM的升级,COM+是向后兼容的,但在某些程度上具有和COM不同的特性,比如无状态的、事务控制、安全控制等等。

以前的OLE是用来描述建立在COM体系结构基础上的一整套技术,现在OLE仅仅是指与对象连接及嵌入有关的技术;ActiveX则用来描述建立在COM基础上的非COM技术,它的重要内容是自动化(Automation),自动化允许一个应用程序(称为自动化控制器)操纵另一个应用程序或库(称为

自动化服务器)的对象,或者把应用程序元素暴露出来。

由此可见COM与以上的几种技术的关系,并且它们都是为了让对象能够跨开发工具跨平台甚至跨网络的被使用。

Delphi下的接口

Delphi中的接口概念类似C++中的纯虚类,又由于Delphi的类是单继承模式(C++是多继承的),即一个类只能有一个父类。接口在某种程度上

可以实现多继承。接口类的声明与一般类声明的不同是,它可以象多重继承那样,类名 = class (接口类1,接口类2… ),然后被声明的接口类则重载继承类的虚方法,来实现接口的功能。

以下是IInterface、IUnknown、IDispatch的声明,大家看出这几个重要接口之间是什么样的联系了吗?任何一个COM对象的接口,最终都是从IUnknown继承的,而Automation对象,则还要包含IDispatch,后面DCOM部分我们会看到它的作用。

 IInterface = interface   ['{00000000-0000-0000-C000-000000000046}']   function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;   function _AddRef: Integer; stdcall;   function _Release: Integer; stdcall; end;  IUnknown = IInterface;  IDispatch = interface(IUnknown)   ['{00020400-0000-0000-C000-000000000046}']   function GetTypeInfoCount(out Count: Integer): HResult; stdcall;   function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;   function GetIDsOfNames(const IID: TGUID; Names: Pointer;     NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;   function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;     Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall; end;

对照"DELPHI下的COM编程技术"一文,可以明白IInterface中的定义,即接口查询及引用记数,这也是访问和调用一个接口所必须的。

QueryInterface可以得到接口句柄,而AddRef与Release则负责登记调用次数。

COM和接口的关系又是什么呢?COM通过接口进行组件、应用程序、客户和服务器之间的通信。COM对象需要注册,而一个GUID则是作为识别接口的唯一名字。

假如你创建了一个COM对象,它的声明类似 Txxxx= class(TComObject, Ixxxx),前面是COM对象的基类,后面这个接口的声明则是: Ixxxx = interface(IUnknown)                    。所以说IUnknown是Delphi中COM对象接口类的祖先。到这一步,我想大家对接口类的来历已经有初步了解了。

聚合

接口是COM实现的基础,接口也是可继承的,但是接口并没有实现自己,仅仅只有声明。那么怎么使COM对象对接口的实现得到重用呢?答案就是聚合。聚合就是一个包含对象(外部对象)创建一个被包含对象(内部对象),这样内部对象的接口就暴露给外部对象。

简单来说,COM对象被注册后,可以找到并调用接口。但接口不是仅仅有个定义吗,它必然通过某种方式找到这个定义的实现,即接口的"实现类"的方法,这样才最终通过外部的接口转入进行具体的操作,并通过接口返回执行结果。

进程内与进程外(In-Process, Out-Process)

进程内的接口的实现基础是一个DLL,进程外的接口则是建立在应用程序(EXE)上的。通常我们建立进程外接口的目的主要是为了方便调试(跟踪DLL是件很麻烦的事),然后在将代码改为进程内发布。因为进程内比进程外的执行效率会高一些。(也就是先建立进程内的接口,再将其改为进程内发布。)

COM对象创建在服务器的进程空间。如果是EXE型服务器,那么服务器和客户端不在同一进程;如果是DLL型服务器,则服务器和客户端就是一个进程。所以进程内还能节省内存空间,并且减少创建实例的时间。

StdCall与SafeCall

Delphi生成的COM接口默认的方法函数调用方式是stdcall而不是缺省的Register。这是为了保证不同语言编译器的接口兼容。

双重接口(在后面讲解自动化时会提到双重接口)中则默认的是SafeCall。它的意义除了按SafeCall约定方式调用外,还将封装方法以便向调用者返回HResult值。SafeCall的好处是能够捕获所有异常,即使是方法中未被代码处理的异常,也可以被外套处理并通过HResult返回给调用者。

WideString等一些有差异的类型

接口定义中缺省的字符参数或返回值将不再是String而是WideString。WideString 是Delphi中符合OLE 32-bit版本的Unicode类型,当是字符

时,WideString与String几乎等同,当处理Unicode字符时,则会有很大差别。联想到COM本身是为了跨平台使用,可以很容易的理解为什么数据通信时需要使用WideString类型。

同样的道理,integer类型将变成SYSINT或者Int64、SmallInt或者Shortint,这些细微的变化都是为了符合规范。

通过向导生成基础代码

打开创建新工程向导(菜单"File-New-Other"或"New Items按钮"),选择ActiveX页。先建立一个ActiveX Library。编译后即是个DLL文件(进程内)。然后在同样的页面再建立一个COM Object

接着你将看到如下向导,除了填写类名外(接口名会自动根据类名填充),创建有 
实例模式(Instancing)和线程模式(Threading Model)的选项。

实例模式->>决定客户端请求后,COM对象如何创建实例:

Internal:供COM对象内部使用,不会响应客户端请求,只能通过COM对象内部的其他方法来建立;

Single Instance:不论当前系统内部是否存在相同COM对象,都会建立一个新的程序及独立的对象实例;

Mulitple Instance:如果有多个相同的COM对象,只会建立一个程序,多个COM对象的实例共享公共代码,并拥有自己的数据空间。

Single/ Mulitple Instance有各自的优点,Mulitple虽然节省了内存但更加费时。即Single模式需要更多的内存资源,而Mulitple模式需要更多的CPU资源,且Single的实例响应请求的负荷较为平均。该参数应根据服务器的实际需求来考虑。

线程模式有五种:

Single:仅单线程,处理简单,吞吐量最低;

Apartment:COM程序多线程,COM对象处理请求单线程;

Free:一个COM对象的多个实例可以同时运行。吞吐量提高的同时,也要求对COM对象进行必要的保护,以避免多个实例冲突;

Both:同时支持Aartment和Free两种线程模式。

Neutral:只能在COM+下使用。

虽然Free和Both的效率得到提高,但是要求较高的技巧以避免冲突(这是很不容易调试的),所以一般建议使用Delphi的缺省方式。

类型库编辑器(Type Library)

假设我们建立一个叫做TSample的类和ISample的接口(如图),然后使用类型库编辑器创建一个方法GetCOMInfo(在右边树部分点击右键弹出

菜单选择New-Method或者点击上方按钮),并于左边Parameters页面建立两个参数(ValInt : Integer , ValStr : String),返回值为BSTR。如图:

可以看到,除了常用类型外,参数和返回值还可以支持很多指针、OLE对象、接口类型。建立普通的COM对象,其Returen Type是可以任意的,

这是和DCOM的一个区别。

双击Modifier列弹出窗口,可以选择参数的方式:in、out分别对应const、out定义,选择Has Default Value可设置参数缺省值。

Delphi生成代码详解

点击刷新按钮刷新后,上面类型库编辑器对应的Delphi自动生成的代码如下:

unit uCOM;  {$WARN SYMBOL_PLATFORM OFF} interface uses   Windows, ActiveX, Classes, ComObj, pCOM_TLB, StdVcl;  type   TSample = class(TTypedComObject, ISample)   protected     function GetCOMInfo(ValInt: SYSINT; const ValStr: WideString): WideString;  stdcall;   end;  implementation  uses ComServ;  function TSample.GetCOMInfo(ValInt: SYSINT; const ValStr: WideString): WideString; begin  end;  initialization   TTypedComObjectFactory.Create(ComServer, TSample, Class_Sample, ciMultiInstance, tmApartment);  end.

引用单元

有三个特殊的单元被引用:ComObj,ComServ和pCOM_TLB。ComObj里定义了COM接口类的父类TTypedComObject

类工厂类 TTypedComObjectFactory(分别从TComObject和TComObjectFactory继承[加了Typed],早期版本如Delphi4建立的COM,就直接从TcomObject继承和使用TComObjectFactory了); ComServ单元里面定义了全局变量ComServer: TComServer[真的有这个],它是从TComServerObject继承的,关于这个变量的作用,后面将会提到。

这几个类都是delphi实现COM对象的比较基础的类,TComObject(COM对象类)和TComObjectFactory(COM对象类工厂类)本身就是IUnknown的

两个实现类,包含了一个COM对象的建立、查询、登记、注册等方面的代码。TComServerObject则用来注册一个COM对象的服务信息。

接口定义说明

再看接口类定义TSample = class(TTypedComObject, ISample)。到这里,已经可以通过涉及的父类的作用大致猜测到TSample是如何创建并注

册为一个标准的COM对象的了。那么接口ISample又是怎么来的呢?pCOM_TLB单元是系统自动建立的,其名称加上了_TLB,它里面包含了ISample

= interface(IUnknown)的接口定义。前面提到过,所有COM接口都是从IUnknown继承的。

在这个单元里我们还可以看到三种ID(类型库ID、IID及COM注册所必须的CLSID)的定义:LIBID_pCOM,IID_ISample和CLASS_Sample。关键是

这时接口本身仅仅只有定义代码而没有任何的实现代码,那接口创建又是在何处执行的?_TLB单元里还有这样的代码:

 CoSample = class   class function Create: ISample;   class function CreateRemote(const MachineName: string): ISample; end;  class function CoSample.Create: ISample; begin   Result := CreateComObject(CLASS_Sample) as ISample; end;  class function CoSample.CreateRemote(const MachineName: string): ISample; begin   Result := CreateRemoteComObject(MachineName, CLASS_Sample) as ISample; end;

由Delphi的向导和类型编辑器帮助生成的接口定义代码,都会绑定一个"Co+类名"的类,它实现了创建接口实例的代码。

CreateComObject和CreateRemoteComObject 
函数在ComObj单元定义,它们就是使用CLSID创建COM/DCOM对象的函数

初始化:注册COM对象的类工厂

类工厂负责接口类的统一管理——实际上是由支持IClassFactory接口的对象来管理的。类工厂类的继承关系如下:

IClassFactory = interface(IUnknown)

TComObjectFactory=class(TObject,IUnknown,IClassFactory,IClassFactory2) TTypedComObjectFactory = class(TComObjectFactory)

我们知道了接口ISample是怎样被创建的,接口实现类TSample又是如何被定义为COM对象的实现类。现在解释它是怎么被注册,以及何时创建的。

这一切的小把戏都在最后Delphi单元里的initialization的部分,这里有一条类工厂建立的语句。

Initialization是Delphi用于初始化的特殊部分,此部分的代码将在整个程序启动的时候首先执行。回顾前面的内容并观察一下TTypedComObjectFactory的参数:

【ComServer是用于注册/撤消注册COM服务的对象,TSample是接口实现类,Class_Sample是接口唯一对应的GUID,ciMultiInstance是实例模式,tmApartment是线程模式。一个COM对象应该具备的特征和要素都包含在了里面!】

那么COM对象的管理又是怎么实现的呢?在ComObj单元里面可以见到一条定义function ComClassManager: TComClassManager;

这里TComClassManager顾名思义就是COM对象的管理类。任何一个祖先类为TComObjectFactory的对象被建立时,其Create里面会执行这样一句:

ComClassManager.AddObjectFactory(Self);

AddObjectFactory方法的原形为procedure TComClassManager.AddObjectFactory(Factory: TComObjectFactory);相对应的还有

RemoveObjectFactory方法。具体的代码我就不贴出来了,相信大家已经猜测到了它的作用——将当前对象(self)加入到ComClassManager管理的对象链(FFactoryList)中。

封装的秘密

读者应该还有最后一个疑问:假如服务器通过类工厂的注册以及GUID确定一个COM对象,那当客户端调用的时候,服务器是如何启动包含COM对象的程序的呢?

当你建立ActiveX Library的工程的时候,将发现一个和普通DLL模版不同的地方——它定义了四个输出例程:

exports

             DllGetClassObject,

             DllCanUnloadNow,

             DllRegisterServer,

             DllUnregisterServer;

这四个例程并不是我们编写的,它们都在ComServ单元例实现。单元还定义了类TComServer,并且在初始化部分创建了类的实例,即前面提到过的全局变量ComServer。

例程DllGetClassObject通过CLSID得到支持IClassFactory接口的对象;例程DllCanUnloadNow判断DLL是否可从内存卸载;DllRegisterServer

和DllUnregisterServer负责DLL的注册和解除注册,其具体的功能由ComServer实现。

接口类的具体实现

好了,现在自动生成代码的来龙去脉已经解释清楚了,下一步就是由我们来添加接口方法的实现代码。在function TSample.GetCOMInfo的部分

添加如下代码。我写的例子很简单,仅仅是根据传递的参数组织一条字符串并返回。以此证明接口正确调用并执行了该代码:

function TSample.GetCOMInfo(ValInt: SYSINT; const ValStr: WideString): WideString; const   Server1 = 1;   Server2 = 2;   Server3 = 3; var   s: string; begin   s := ' This is COM server: ';   case ValInt of     Server1: s := s + ' Server1 ';     Server2: s := s + ' Server2 ';     Server3: s := s + ' Server3 ';   end;   s := s + #13 + #10 + ' Execute client is ' + ValStr;   Result := s; end;

注册、创建COM对象及调用接口

随便建立一个Application用于测试上面的COM。必要的代码很少,创建一个接口的实例然后执行它的方法。当然我们得先行注册COM,否则调用

根据CLSID找不接口的话,将报告"无法向注册表写入项"。如果接口定义不一致,则会报告"Interface not supported"。

编译上面的这个COM工程,然后选择菜单"Run – Register ActiveX Server",或者通过Windows下system/system32目录中的regsvr32.exe程序注册编译好的DLL文件。regsvr32的具体参数可以通过regsvr32/?来获得。对于进程外(EXE型)的COM对象,执行一次应用程序就注册了。

提示DLL注册成功后,就应该可以正确执行下列客户端程序了:

 uses ComObj, pCOM_TLB;  procedure Ttest.Button1Click(Sender: TObject); var   COMSvr: ISample;   retStr: string; begin   COMSvr := CreateComObject(CLASS_Sample) as ISample;   if COMSvr <> nil then   begin     retStr := COMSvr.GetCOMInfo(2, ' client 2 ');     showmessage(retStr);     COMSvr := nil;   end else     showmessage(' ?¡§??¡§2??????¡ì2?3¡§|1| '); end;

最终值是从当前程序外的一个"接口"返回的,我们甚至可以不知道这个接口的实现!第一次接触COM的人,成功执行此程序并弹出对话框后,

也许会体会到一种技术如斯奇妙的感觉,因为你仅仅调用了"接口",就可以完成你猜测中的东西。

创建一个分布式DCOM(自动化接口)

IDispatch

在delphi6之前的版本中,所有接口的祖先都是IUnknown,后来为了避免跨平台操作中接口概念的模糊,又引入了IInterface接口。

使用向导生成DCOM的步骤和COM几乎一致。而生成的代码仅将接口类的父类换为TAutoObject,类工厂类换为TAutoObjectFactory。这其实没有

太大的不同,因为TAutoObject等于是一个标准COM外加IDispatch接口,而TAutoObjectFactory是从TTypedComObjectFactory直接继承的:

TAutoObject = class(TTypedComObject, IDispatch)

TAutoObjectFactory = class(TTypedComObjectFactory)

自动化服务器支持双重接口,而且必须实现IDispatch。因讨论范畴限制,本文只能简单提出,IDispatch是DCOM和COM技术实现上的一个重要区

别。打开_TLB.pas单元,可以找到Ixxx = interface(IDispatch)和Ixxx = dispinterface的定义,这在前面COM的例子里面是没有的。

创建过程中的差异

使用类型库编辑器的时候,有两处和COM不同的地方。首先Return Type必须选择HRESULT,否则会提示错误,这是为了满足双重接口的需要。当

Return Type选择HRESULT后,你会发现方法定义将变成procedure(过程)而不是预想中的function(函数)。

怎么才能让方法有返回值呢?还需要在Parameters最后多添加一个参数,然后将该参数改名与方法名一致,设置参数类型为指针(如果找不到

某种类型的指针类型,可以直接在类型后面加*,如图,BSTR*是BSTR的指针类型)。最后在Modifier列设置Parameter Flags为RetVal,同时

Out将被自动选中,而In将被取消。

刷新后,得到下列代码。添加方法的具体实现,大功告成:

             TSampleAuto = class(TAutoObject, ISampleAuto)

             protected

               function GetAutoSerInfo(ValInt: SYSINT;const ValStr: WideString): WideString; safecall;

             end;

远程接口调用

远程接口的调用需要使用CreateRemoteComObject函数,其它如接口的声明等等与COM接口调用相同。CreateRemoteComObject函数比

CreateComObject 多了一个参数,即服务器的计算机名称,这样就比COM多出了远程调用的查询能力。前面"接口定义说明"一节的代码可以对

照CreateComObject、CreateRemoteComObject的区别。

自定义COM的对象

接口一个重要的好处是:发布一个接口,可以不断更新其功能而不用升级客户端。因为不论应用升级还是业务改变,客户端的调用方式都是一

致的。

既然我们已经弄清楚Delphi是怎样实现一个接口的,那能否不使用向导,自己定义接口呢?这样做可以用一个接口继承出不同的接口实现类,

来完成不同的功能。同时也方便了小组开发、客户端开发、进程内/外同步编译以及调试。

接口单元:xxx_TLB.pas

前面略讲了接口的定义需要注意的方面。接口除了没有实例化外,它与普通类还有以下区别:接口中不能定义字段,所有属性的读写必须由方

法实现;接口没有构造和析构函数,所有成员都是public;接口内的方法不能定义为virtual,dynamic,abstract,override。

首先我们要建立一个接口。前面讲过接口的定义只存在于一个地方,即xxx_TLB.pas单元里面。使用类型库编辑器可以产生这样一个单元。还是

在新建项目的ActiveX页,选择最后一个图标(Type Library)打开类型库编辑器,按F12键就可以看到TLB文件(保存为.tlb)了。没有定义任

何接口的时候,TLB文件里除了一大段注释外只定义了LIBID(类型库的GUID)。假如关闭了类型库编辑器也没有关系,可以随时通过菜单View

– Type Library打开它。

先建立一个新接口(使用向导的话这步已经自动完成了),然后如前面操作一样建立方法、属性…生成的TLB文件内容与向导生成_TLB单元大致

相同,但仅有定义,缺乏"co+类名"之类的接口创建代码。

再观察代码,将发现接口是从IDispatch继承的,必须将这里的IDispatch改为IUnknown。保存将会得到.tlb文件,而我们想要的是一个单元

(.pas)文件,仅仅为了声明接口,所以把代码拷贝复制并保存到一个新的Unit。

自定义CLSID

从注册和调用部分可以看出CLSID的重要作用。CLSID是一个GUID(全局唯一接口表示符),用来标识对象。GUID是一个16个字节长的128位二进

制数据。Delphi声明一个GUID常量的语法是:

             Class_XXXXX : TGUID = ''''{xxxxxxxx-xxxxx-xxxxx-xxxxx-xxxxxxxx}'''';

在Delphi的编辑界面按Ctrl+Shift+G键可以自动生成等号后的数据串。GUID的声明并不一定在_TLB单元里面,任何地方都可以声明并引用它。

接口类声明与实现

新建一个ActiveX Library工程,加入刚才定义的TLB单元,再新建一个Unit。我的TLB单元取名为MyDef_TLB.pas,定义了一个接口

IMyInterface = interface(IUnknown),以及一个方法function SampleMethod(val: Smallint): SYSINT; safecall;现在让我们看看全部接口

类声明及实现的代码:

 unit uMyDefCOM;  interface  uses ComObj, Comserv, ActiveX, MyDef_TLB;  const   Class_MySvr: TGUID = '{1C0E5D5A-B824-44A4-AF6C-478363581D43}';  type   TMyIClass = class(TComObject, IMyInterface)     procedure Initialize; override;     destructor Destroy; override;   private     FInitVal: word;   public     function SampleMethod(val: Smallint): SYSINT; safecall;   end;    TMySvrFactory = class(TComObjectFactory)     procedure UpdateRegistry(Register: Boolean); override;   end;  implementation  procedure TMyIClass.Initialize; begin   inherited;   FInitVal := 100; end;  destructor TMyIClass.Destroy; begin   inherited; end;  function TMyIClass.SampleMethod(val: Smallint): SYSINT; begin   Result := val + FInitVal; end;  procedure TMySvrFactory.UpdateRegistry(Register: Boolean); begin   inherited;   if Register then   begin     CreateRegKey('MyApp\' + ClassName, 'GUID', GUIDToString(Class_MySvr));   end  else   begin     DeleteRegKey('MyApp\' + ClassName);   end; end;  initialization   TMySvrFactory.Create(ComServer, TMyIClass, Class_MySvr,  'MySvr', '', ciMultiInstance, tmApartment);  end.

Class_MySvr是自定义的CLSID,TMyIClass是接口实现类,TMySvrFactory是类工厂类。

COM对象的初始化

procedure Initialize是接口的初始化过程,而不是常见的Create方法。当客户端创建接口后,将首先执行里面的代码,与Create的作用一样

。一个COM对象的生存周期内,难免需要初始化类成员或者设置变量的初值,所以经常需要重载这个过程。

相对应的,destructor Destroy则和类的标准析构过程一样,作用也相同。

类工厂注册

在代码的最后部分,假如使用TComObjectFactory来注册,就和前面所讲的完全一样了。我在这里刻意用类TMySvrFactory继承了一次,并且重

载了UpdateRegistry 方法,以便向注册表中写入额外的内容。这是种小技巧,希望大家根据本文的思路,摸清COM/DCOM对象的Delphi实现结构

后,可以举一反三。毕竟随心所欲的控制COM对象,能提供的功能远不如此。

摘自:http://www.cnblogs.com/railgunman/archive/2010/11/27/1889701.html