﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-阿π-随笔分类-服务器开发</title><link>http://www.cppblog.com/lapcca/category/13741.html</link><description>专注于网络协议,系统底层,服务器软件</description><language>zh-cn</language><lastBuildDate>Sun, 06 Jun 2010 05:22:53 GMT</lastBuildDate><pubDate>Sun, 06 Jun 2010 05:22:53 GMT</pubDate><ttl>60</ttl><item><title>Winsock服务器内存资源管理 </title><link>http://www.cppblog.com/lapcca/archive/2010/06/01/116873.html</link><dc:creator>阿π</dc:creator><author>阿π</author><pubDate>Tue, 01 Jun 2010 03:31:00 GMT</pubDate><guid>http://www.cppblog.com/lapcca/archive/2010/06/01/116873.html</guid><wfw:comment>http://www.cppblog.com/lapcca/comments/116873.html</wfw:comment><comments>http://www.cppblog.com/lapcca/archive/2010/06/01/116873.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/lapcca/comments/commentRss/116873.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/lapcca/services/trackbacks/116873.html</trackback:ping><description><![CDATA[一般来讲， 在服务器上，如果有足够的资源，Winsock server，理论上可以支持成千的并发连接。而现实是，我们没有足够的资源可供使用，分配。本文主要来讨论一下内存资源之于Winsock server开发的重要性。<br /><strong><u>一）基本概念。</u></strong><br /><em><u><strong>-&gt; Pages,Locked Pages.</strong></u></em><br />        在现代操作系统中，内存管理会把主存(RAM)分成Pages来管理。 Paging(或者swapping）指的是主存与外存之间以Page为单位进行数据的交换。Locked Pages指的是被锁定在主存中的内存页，以保证一些内核组件,driver可以访问到它们。windows一定会保证一定数量的可交换的内存空间，防止一些非法程序锁定所有的物理内存，而致使系统崩溃。在windows NT, windows 2000上，可锁定的内存总的大小上限大概是物理内存的1/8(当然对于程序的开发人员，不应该对这个值进行任何的假设，这个值可能会随着操作系统本版的变化而变化)。在Winsock应用开发过程中，以overlapped方式读写IO操作，将会导致内存被锁定。<br /><u><em><strong>-&gt; working set</strong></em></u><br />        在程序开始运行，并达到其稳定的运行状态（主要指的是其对内存的使用），在这个状态下，程序使用内存的数量一般小于其需要使用内存的总量。这样一个稳定的运行状态，我们可以称为working set: 被该程序频繁访问的内存页的集合。在windows上，你可以使用SetWorkingSetSize Win32 API来增加程序使用物理内存的数量。<br /><u><em><strong>-&gt; non-paged pool</strong></em></u><br />       不可交换的内存。这主要指以non-paged的方式分配的内存，这些内存就像locked pages一样，是从来不会被交换出去的，用来存放一些由内核组件,driver访问的信息。 在Winsock应用开发过程中，以下的操作可能导致分配non-paged内存。<br />1) 调用系统一些系统的API，例如打开文件，create socket，等，都会导致从non-paged pool分配内存。<br />2) 一些driver可以显式地从该区域分配内存。<br /><strong><u>二) Winsock server上Locked Pages使用。<br /></u></strong>        我们提到过，任何的overlapped IO操作，都会导致锁定内存页。这些内存页一旦被locked,就不会被交换出去。我们知道，windows操作系统对最大的可锁定内存页做了一个上限，如果超出这个上限，overlapped IO调用将会导致WSAENOBUFS错误。<br />        考虑下面的情况，如果server在每个连接上会发出很多的overlapped receives操作，那么，随着连接数目的增多，很明显，被锁定的内存数量很有可能达到上限而导致WSAENOBUFS错误。在这种情况下，如果服务器预期会处理大数量的客户端连接，则需要服务器在每个连接上发出zero-byte buffer的overlapped接收请求(这种情况下，因为the size of buffer is zero,所以没有任何内存被锁定)，一旦overlapped接收操作完成，server可以以non-blocking方式执行receive操作，以取得所有缓存在so_rcvbuf中的数据，直到返回WSAEWOULDBLOCK为止。<br />        另外需要注意的是，windows在page的边界上对内存进行锁定，在x86平台上，它是4kb的整数倍。所以，假如你post了一个1 KB buffer，而系统真实锁定的是4 KB 的大小，为了避免这样的浪费，尽量用4kb的整数做overlapped  IO操作。<br /><strong><u>三) Winsock server上non-paged pool使用。<br /></u></strong>        同Locked Pages限制一样，windows对non-paged pool也有一个最大的限制。并且，当你的应用出现这个问题的时候，超出它的最大限制数，情况要远比Locked Pages复杂。这种情况下，后果是不确定的，有可能你的Winsock调用返回WSAENOBUFS错误，也有可能，在系统中，一个和你的应用毫无关联的driver由于申请不到non-paged内存而致使system crash。而这样的灾难，是没法恢复的。<br />        考虑一个具体的例子：我们假设在windows2000上,系统有1 GB内存。这样的配置下，windows大概会预留1/4的空间用作non-paged pool(同样，对于程序的开发人员，不应该对这个值进行任何的假设)，即：256MB。这样的配置下，保守估计，我们的Winsock server能够处理到大概50,000连接，或者更多。(每个accepted socket大概消耗1.5kb，每个连接上post一个overlapped操作，分配一个IRP,大概需要500 byte, 总计：(1500+500)*50,000 = 100 Mb) 。<br />       无论是对于Locked Pages，还是对于non-paged pool使用，一旦超出了上限，Winsock调用仅仅会返回一般的WSAENOBUFS 或者ERROR_INSUFFICIENT_RESOURCES错误。为了处理这些错误，你可以试试以下的方法：<br /><strong><em>1） 需要首先调用SetWorkingSetSize,增加应用的可支配资源数，看能否解决。<br />2)     确信你的应用没有做出太多的overlapped  IO操作。<br />3） 关闭一些连接数。<br /></em><u>四) SOCKET的缓冲区设置问题。</u></strong><br />         Winsock在默认的情况下，每个socket都会与一个send和receive buffer相关联。你可以通过调用setsockopt来设置buffer的大小。<br />        在缓冲区没有被关闭的情况下，我们看看overlapped send和revc是怎么工作的。<br />        当上层的应用做出了send调用，而这时如果send buffer还有剩余的空间，那么数据将会从用户提交的buffer复制到send buffer中，然后调用返回成功。否则，假如这时send buffer已满，用户提交的buffer将会被锁定，并且调用返回WSA_IO_PENDING。当send buffer的数据被下层的tcp处理完成，winsock将直接处理用户提交的buffer里的数据，而不需要再复制。<br />        同样，对于recv操作，如果数据已经被缓存在socket的receive buffer里，当发生recv调用的时候，数据将直接从socket的receive buffer复制到用户的buffer里，recv调用返回成功。否则，假如发生调用时receive buffer里没有数据，用户提交的buffer将会被锁定，recv调用返回WSA_IO_PENDING。当数据到达当前连接，将会被直接复制到用户提交的buffer里。<br />        一个应用程序通过设定send buffer为0，把缓冲区关闭，然后发出一个阻塞send()调用。在这样的情况下，系统内核会把应用程序的缓冲区锁定，直到接收方确认收到了整个缓冲区后send()调用才返回。似乎这是一种判定你的数据是否已经为对方全部收到的简洁的方法，实际上却并非如此。想想看，即使远端tcp通知数据已经收到，其实也根本不代表数据已经成功送给客户端应用程序，比如对方可能发生资源不足的情况，导致afd.sys不能把数据拷贝给应用程序。另一个更要紧的问题是，在每个线程中每次只能进行一次发送调用，效率极其低下。 <br />        另外，希望通过关闭Winsock缓冲区，从而避免数据复制，达到优化性能的目的，也是不可取的。从上面，我们看到：只要应用保证适量的，足够的send, recv调用，这样的复制是完全可以避免的。<br />        高性能的服务器应用程序可以关闭发送缓冲区，同时不会损失性能。不过，这样的应用程序必须十分小心，保证它总是发出多个重叠发送调用，而不是等待某个重叠发送结束了才发出下一个。如果应用程序是按一个发完再发下一个的顺序来操作，那浪费掉两次发送中间的空档时间，总之是要保证传输驱动程序在发送完一个缓冲区后，立刻可以转向另一个缓冲区。<br />        如果关闭了recv buffer，在你的应用没有保证足够的recv操作前提下，任何进来数据，必须在TCP层进行缓存，最大缓存的数量将取决于tcp windows的大小(17Kb)。而最为严重的是这些缓存是从non-paged pool分配而来。如上所述，non-paged pool是非常珍贵，稀缺的内存。所以，从这个意义上来讲，关闭了recv buffer操作是不可取的。<img src ="http://www.cppblog.com/lapcca/aggbug/116873.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/lapcca/" target="_blank">阿π</a> 2010-06-01 11:31 <a href="http://www.cppblog.com/lapcca/archive/2010/06/01/116873.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>协作半驻留式服务器程序开发框架 --- 基于 Postfix 服务器框架改造</title><link>http://www.cppblog.com/lapcca/archive/2010/05/06/114573.html</link><dc:creator>阿π</dc:creator><author>阿π</author><pubDate>Thu, 06 May 2010 03:14:00 GMT</pubDate><guid>http://www.cppblog.com/lapcca/archive/2010/05/06/114573.html</guid><wfw:comment>http://www.cppblog.com/lapcca/comments/114573.html</wfw:comment><comments>http://www.cppblog.com/lapcca/archive/2010/05/06/114573.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/lapcca/comments/commentRss/114573.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/lapcca/services/trackbacks/114573.html</trackback:ping><description><![CDATA[一、概述 
<p></p><p>　　　现在大家在和Java, PHP, .net写应用程序时，都会用到一些成熟的服务框架，所以开发效率是比较高的。而在用C/C++写服务器程序时，用的就五花八门了，有些人用ACE, 有些人用ICE（号称比ACE强许多），等等，这类服务器框架及库比较丰富，但入门门槛比较高，所以更多的人是自己直接写服务器程序，初始写时觉得比较简单，可时间久了，便会觉得难以扩展，性能低，容易出错。其实，Postfix 作者为我们提供了一个高效、稳定、安全的服务器框架模型，虽然Postfix主要用作邮件系统的 mta，但其框架设计却非常具有通用性。ACL(http://acl.sourceforge.net/) 的作者将Postfix的服务器框架模型抽取出来，形成了更加通用的服务器程序开发框架，使程序员在编写服务器程序时可以达到事半功倍的效果。本文主要介绍了ACL中acl_master服务器程序（基于Postifx服务器程序框架）的设计及功能。</p><p>　　二、框架设计图</p><p>　　如下图所示：</p><p><img onclick="get_larger(this)" height="354" alt="协作半驻留式服务器程序开发框架 --- 基于 Postfix 服务器框架改造" hspace="0" src="http://www.cppblog.com/images/cppblog_com/lapcca/13342/o_1.jpg" width="550" /></p><p>　　</p><p>　　图1--框架图</p><p>　　　　 master主进程为控制进程，刚启动时其负责监听所有端口服务，当有新的客户端连接到达时，master便会启动子进程进行服务，而自己依然监控服务端口，同时监控子进程的工作状态；而提供对外服务的子进程在master启动时，若没有请求任务则不会被启动，只有当有连接或任务到达时才会被master 启动，当该服务子进程处理完某个连接服务后并不立即退出，而是驻留在系统一段时间，等待可能的新连接到达，这样当有新的连接到达时master就不会启动新的子进程，因为已经有处于空闲的子进程在等待下一个连接请求；当服务子进程空闲时间达一定阀值后，就会选择退出，将资源全部归还操作系统（当然，也可以配置成服务子进程永不退出的模式）。因此，可以称这种服务器框架为协作式半驻留式服务器框架，下面将会对协作式和半驻留作进一步介绍。</p><div class="content_250x250"></div><p>　　三、协作方式</p><p>　　Postfix服务器框架设计的非常巧妙，因为master毕竟属于用户空间进程，不能象操作系统那样可以控制每个进程的运行时间片，所以master主进程必须与其服务子进程之间协作好，以处理好以下几个过程：</p><p>　　1）新连接到达时，master是该启动新的子进程接管该连接还是由空闲子进程直接接管</p><p>　　2）master何时应该启动新的子进程</p><p>　　3）新连接到达，空闲子进程池中的子进程如何竞争接管该连接</p><p>　　4）子进程异常退出时，master如何处理新连接</p><p>　　5）空闲子进程如何选择退出时间（空闲时间或服务次数应决定子进程的退出）</p><p>　　6）master如何知道各个子进程的工作状态（是死了还是活着？）</p><p>　　7）在不停止服务的前提下，服务子进程程序如果在线更新、如何添加新的服务、如何在线更新子进程配置</p><p>　　8）如何减少所有子进程与master之间的通讯次数从而降低master的负载</p><p>　　四、流程图</p><p>　　1)　 master主进程流程图</p><p><img onclick="get_larger(this)" height="329" alt="协作半驻留式服务器程序开发框架 --- 基于 Postfix 服务器框架改造" hspace="0" src="http://www.cppblog.com/images/cppblog_com/lapcca/13342/o_2.jpg" width="550" /></p><p>　　</p><p>　　图2--master进程流程图</p><p>　　Postifx 中的 master 主进程与各个子进程之间的IPC通讯方式为管道，所以管道的个数与子进程数是成正比的。如果管道中断，则 master 认为该管道所对应的子进程已经退出，如果是异常退出，master还需要标记该服务类子进程池以防止该类子进程异常退出频繁而启动也异常频繁（如果子进程启动过于频繁则会给操作系统造成巨大负载）；另外，如果某类服务的子进程在服务第一个连接时就异常退出，则master认为该服务有可能是不可用的，所以当有新的连接再到达时就会延迟启动该服务子进程。</p><div class="content_250x250"></div><p>　　当服务子进程池中有空闲子进程时，master便会把该服务端口的监听权让出，从而该服务的空闲子进程在该服务端口上接收新的连接。当某个子进程获得新的连接后便会立即通知master其已经处于忙状态，master便立即查找该服务的子进程进程池还有无空闲子进程，如果有则master依然不会接管该服务端口的监听任务；如果没有了，则master立即接管该服务端口的监听任务，当有新的连接到达时，master先检查有没有该服务的空闲进程，若有便让出该服务端口的监听权，若没有便会启动新的子进程，然后让出监听权。</p><p>　　2)　 服务子进程流程图</p><p><img onclick="get_larger(this)" height="355" alt="协作半驻留式服务器程序开发框架 --- 基于 Postfix 服务器框架改造" hspace="0" src="http://www.cppblog.com/images/cppblog_com/lapcca/13342/o_3.jpg" width="550" /></p><p>　　</p><p>　　图3--子进程流程图</p><p>　　在master主进程刚启动时，因为没有任何服务请求，所以子进程是不随master一起启动的，此时所有服务端口的监控工作是由master统一负责，当有客户端连接到达时，服务子进程才由master启动，进而接收该新连接，在进一步处理客户端请求前，子进程必须让master进程知道它已经开始" 忙"了，好由master来决定是否再次接管该服务端口的监控任务，所以子进程首先向master发送“忙”消息，然后才开始接收并处理该客户端请求，当子进程完成了对该客户端的请求任务后，需向master发送“空闲”消息，以表明自己又可以继续处理新的客户端连接任务了。这一“忙”一“闲”两个消息，便体现了服务子进程与master主进程的协作特点。</p><div class="content_250x250"></div><p>　　当然，服务子进程可以选择合适的退出时机：如果自己的服务次数达到配置的阀值，或自己空闲时间达到阀值，或与master主进程之间的IPC管道中断(一般是由master停止服务要求所有服务子进程退出时或master要求所有服务子进程重读配置时而引起的)，则服务子进程便应该结束运行了。这个停止过程，一方面体现了子进程与master主进程之间的协作特点，另一方面也体现了子进程半驻留的特点，从而体现子进程进程池的半驻留特性，这一特性的最大好处就是：按需分配，当请求连接比较多时，所启动运行的子进程就多，当请求连接任务较少时，只有少数的子进程在运行，其它的都退出了。</p><p>　　3) 小结</p><p>　　以上从整体上介绍了Postfix服务器框架模型中master主进程与服务子进程的逻辑流程图，当然，其内部实现要点与细节远比上面介绍的要复杂，只是这些复杂层面别人已经帮我们屏蔽了，我们所要做的是在此基础上写出自己的应用服务来，下面简要介绍了基于Postfix的服务器框架改造抽象的ACL库中服务器程序的开发方式，以使大家比较容易上手。</p><p>　　五、五种服务器框架模板简介</p><p>　　要想使用ACL服务器框架库开发服务器程序，首先介绍两个个小名词：acl_master服务器主进程与服务器模板。acl_master服务器主进程与 Postfix中的master主进程功能相似，它的主要作用是启动并控制所有的服务子进程，这个程序不用我们写，是ACL服务器框架中已经写好的；所谓服务器模板，就是我们需要根据自己的需要从ACL服务器框架中选择一种服务器模型(即服务器模板)，然后在编写服务器时按该服务器模板的方式写即可。为什么还要选择合适的服务器框架模板？这是因为Postfix本身就提供了三类服务器应用形式(单进程单连接进程池、单进程多连接进程池、触发器进程池)，这三类应用形式便分别使用了Postfix中的三种服务器模板，此外，ACL中又增加了两种服务器应用形式(多线程进程池、单进程非阻塞进程池)。下面分别就这五种服务器框架模板一一做简介：</p><div class="content_250x250"></div><p>　　1) 单进程单连接进程池：由 acl_master 主进程启动多个进程组成进程池提供某类服务，但每个进程每次只能处理一个客户端连接请求</p><p>　　2) 单进程多连接进程池：由 acl_master 主进程启动多个进程组成进程池提供某类服务，而每个进程可以同时处理多个客户端连接请求</p><p>　　3) 触发器进程池：由 acl_master 主进程启动多个进程组成进程池提供定时器类服务(类似于UNIX中的cron)，当某个定时器时间到达时，便由一个进程开始运行处理任务</p><p>　　4) 多线程进程池：由 acl_master 主进程启动多个进程组成进程池提供某类服务，而每个进程是由多个线程组成，每个线程处理一个客户端连接请求</p><p>　　5) 单进程非阻塞进程池：由 acl_master 主进程启动多个进程组成进程池提供某类服务，每个进程可以并发处理多个连接(类似于Nginx, Lighttpd, Squid, Ircd)，由于采用非阻塞技术，该模型服务器的并发处理能力大大提高，同时系统资源消耗也最小；当然，该模型与单进程多连接进程池采用的技术都是非阻塞技术，但该模型进行更多的应用封装与高级处理，使编写非阻塞程序更加容易</p><p>　　以上五种服务器方式中，由于可以根据需要配置成多个进程实例，所以可以充分地利用多核的系统。其中，第5种的运行效率是最高的，当然其编程的复杂度要比其它的高，而第1种是执行效率最低的，其实它也是最安全的(在Postfix中的smtpd/smtp 等进程就属于这一类)，而相对来说，第4种在运行效率与编写复杂度方面是一个比较好的折衷，所以在写一般性服务器时，该服务器模型是作者推荐的方案，此外，第4种方案还有一个好处，可以做到对于空连接不必占用线程，这样也大大提供了并发度(即线程数可以远小于连接数)。</p><p>　　六、使用简介</p><p>　　本节暂不介绍具体的编程过程，只是介绍一些配置使用过程。假设已经写好了服务器程序：echo_server, 该程序可以采用上面的 1), 2), 4), 5) 中的任一服务器模型来写，假设采用了第4)种；另外，还假设：acl_master等所有文件安装的根目录为 /opt/acl/, 主进程程序 acl_master 及 echo_server 安装在 /opt/acl/libexec/,　 acl_master 主程序配置文件安装在 /opt/acl/conf/，echo_server 配置文件安装在 /opt/acl/conf/service/, 日志文件目录为 /opt/acl/var/log/, 进程号文件目录为 /opt/acl/var/pid/。比如，你让 echo_server 的服务端口为 6601，服务地址为 127.0.0.1, 它只是提供简单的 echo 服务。</p><p></p><p>　　你可以运行 /opt/acl/sh/start.sh 脚本来启动 acl_master 主进程(用 ps -ef|grep acl_master 会看到 acl_master 进程存在 ，但 ps -ef|grep echo_server 却没有发现它的存在)，然后你在一个Unix终端上 telnet 127.0.0.1 6601, 在另一个终端上 ps -ef|grep echo_server 就会发现有一个 echo_server子进程了，然后在 telnet 6601 的终端上随便输入一些字符串，便会立即得到回复，关闭该 telnet 连接，用 ps -ef|grep echo_server 会发现该进程依然存在，当再次 telnet 127.0.0.1 6601 时，该echo_server进程又继续为新连接提供服务了。可以试着多开几个终端同时 telnet 127.0.0.1 6601，看看运行效果如何？</p><p>　　注意，服务子进程的配置文件格式需要与其所采用的模板类型一致；进程池中最大进程个数、进程中线程池最大线程个数、进程最大服务次数、空闲时间等都是可以以配置文件中指定的。</p><img src ="http://www.cppblog.com/lapcca/aggbug/114573.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/lapcca/" target="_blank">阿π</a> 2010-05-06 11:14 <a href="http://www.cppblog.com/lapcca/archive/2010/05/06/114573.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> postfix简介</title><link>http://www.cppblog.com/lapcca/archive/2010/05/06/114572.html</link><dc:creator>阿π</dc:creator><author>阿π</author><pubDate>Thu, 06 May 2010 03:09:00 GMT</pubDate><guid>http://www.cppblog.com/lapcca/archive/2010/05/06/114572.html</guid><wfw:comment>http://www.cppblog.com/lapcca/comments/114572.html</wfw:comment><comments>http://www.cppblog.com/lapcca/archive/2010/05/06/114572.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/lapcca/comments/commentRss/114572.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/lapcca/services/trackbacks/114572.html</trackback:ping><description><![CDATA[<p>一、 postfix概述</p>
		<p>　　postfix是Wietse Venema在IBM的GPL协议之下开发的MTA（邮件传输代理）软件。下面一段话摘自postfix的官方站点（<a href="http://www.postfix.org">http://www.postfix.org</a>）：“postfix是Wietse Venema想要为使用最广泛的sendmail提供替代品的一个尝试。在Internet世界中，大部分的电子邮件都是通过sendmail来投递的，大约有100万用户使用sendmail，每天投递上亿封邮件。这真实一个让人吃惊的数字。Postfix试图更快、更容易管理、更安全，同时还与sendmail保持足够的兼容性。”</p>
		<p>1.1 postfix的特点</p>
		<p>1. postfix是免费的：</p>
		<p>　　postfix想要作用的范围是广大的Internet用户，试图影响大多数的Internet上的电子邮件系统，因此它是免费的。</p>
		<p>2. 更快：</p>
		<p>　　postfix在性能上大约比sendmail快三倍。一部运行postfix的台式PC每天可以收发上百万封邮件。</p>
		<p>3. 兼容性好:</p>
		<p>　　postfix是sendmail兼容的，从而使sendmail用户可以很方便地迁移到postfix。Postfix支持/var[/spool]/mail、/etc/aliases、 NIS、和 ~/.forward 文件。</p>
		<p>4. 更健壮：</p>
		<p>　　postfix被设计成在重负荷之下仍然可以正常工作。当系统运行超出了可用的内存或磁盘空间时，postfix会自动减少运行进程的数目。当处理的邮件数目增长时，postfix运行的进程不会跟着增加。</p>
		<p>5. 更灵活：</p>
		<p>　　postfix是由超过一打的小程序组成的，每个程序完成特定的功能。你可以通过配置文件设置每个程序的运行参数。</p>
		<p>6. 安全性</p>
		<p>　　postfix具有多层防御结构，可以有效地抵御恶意入侵者。如大多数的postfix程序可以运行在较低的权限之下，不可以通过网络访问安全性相关的本地投递程序等等。</p>
		<p>1.2 postfix的总体结构</p>
		<p>　　postfix由十几个具有不同功能的半驻留进程组成，并且在这些进程中并无特定的进程间父子关系。某一个特定的进程可以为其他进程提供特定的服务。</p>
		<p>　　大多数的postfix进程由一个进程统一进行管理，该进程负责在需要的时候调用其他进程，这个管理进程就是master进程。该进程也是一个后台程序。</p>
		<p>　　这些postfix进程是可以配置的，我们可以配置每个进程运行的数目，可重用的次数，生存的时间等等。通过灵活的配置特性可以使整个系统的运行成本大大降低。</p>
		<p>1.2.1 postfix的邮件队列（mail queues）</p>
		<p>　　postfix有四种不同的邮件队列，并且由队列管理进程统一进行管理：</p>
		<p>1． maildrop：本地邮件放置在maildrop中，同时也被拷贝到incoming中。</p>
		<p>2． incoming：放置正在到达或队列管理进程尚未发现的邮件。</p>
		<p>3． active：放置队列管理进程已经打开了并正准备投递的邮件，该队列有长度的限制。</p>
		<p>4． deferred：放置不能被投递的邮件。</p>
		<p>　　队列管理进程仅仅在内存中保留active队列，并且对该队列的长度进行限制，这样做的目的是为了避免进程运行内存超过系统的可用内存。</p>
		<p>1.2.2 postfix对邮件风暴的处理</p>
		<p>　　当有新的邮件到达时，postfix进行初始化，初始化时postfix同时只接受两个并发的连接请求。当邮件投递成功后，可以同时接受的并发连接的数目就会缓慢地增长至一个可以配置的值。当然，如果这时系统的消耗已到达系统不能承受的负载就会停止增长。还有一种情况时，如果postfix在处理邮件过程中遇到了问题，则该值会开始降低。</p>
		<p>　　当接收到的新邮件的数量超过postfix的投递能力时，postfix会暂时停止投递deferred队列中的邮件而去处理新接收到的邮件。这是因为处理新邮件的延迟要小于处理deferred队列中的邮件。Postfix会在空闲时处理deferred中的邮件。</p>
		<p>1.2.3 postfix对无法投递的邮件的处理</p>
		<p>　　当一封邮件第一次不能成功投递时，postfix会给该邮件贴上一个将来的时间邮票。邮件队列管理程序会忽略贴有将来时间邮票的邮件。时间邮票到期时，postfix会尝试再对该邮件进行一次投递，如果这次投递再次失败，postfix就给该邮件贴上一个两倍于上次时间邮票的时间邮票，等时间邮票到期时再次进行投递，依此类推。当然，经过一定次数的尝试之后，postfix会放弃对该邮件的投递，返回一个错误信息给该邮件的发件人。</p>
		<p>1.2.4 postfix对不可到达的目的地邮件的处理</p>
		<p>　　postfix会在内存中保存一个有长度限制的当前不可到达的地址列表。这样就避免了对那些目的地为当前不可到达地址的邮件的投递尝试。从而大大提高了系统的性能。</p>
		<p>1.2.5 postfix的安全性</p>
		<p>　　postfix通过一系列的措施来提高系统的安全性，这些措施包括：</p>
		<p>1． 动态分配内存，从而防止系统缓冲区溢出；</p>
		<p>2． 把大邮件分割成几块进行处理，投递时再重组；</p>
		<p>3． Postfix的各种进程不在其他用户进程的控制之下运行，而是运行在驻留主进程master的控制之下，与其他用户进程无父子关系，所有有很好的绝缘性。</p>
		<p>4． Postfix的队列文件有其特殊的格式，只能被postfix本身识别；</p>
		<p>二、 postfix对邮件的处理过程</p>
		<p>2.1 接收邮件的过程<br />　　<br />　　当postfix接收到一封新邮件时，新邮件首选在incoming队列处停留，然后针对不同的情况进行不同的处理：</p>
		<p>1．对于来自于本地的邮件：sendmail进程负责接收来自本地的邮件放在maildrop队列中，然后pickup进程对maildrop中的邮件进行完整性检测。maildrop目录的权限必须设置为某一用户不能删除其他用户的邮件。</p>
		<p>2．对于来自于网络的邮件：smtpd进程负责接收来自于网络的邮件，并且进行安全性检测。可以通过UCE（unsolicited commercial email）控制smtpd的行为。</p>
		<p>3．由postfix进程产生的邮件：这是为了将不可投递的信息返回给发件人。这些邮件是由bounce后台程序产生的。</p>
		<p>5． 由postfix自己产生的邮件：提示postmaster（也即postfix管理员）postfix运行过程中出现的问题。（如SMTP协议问题，违反UCE规则的记录等等。）</p>
		<p>　　关于cleanup后台程序的说明：cleanup是对新邮件进行处理的最后一道工序，它对新邮件进行以下的处理：添加信头中丢失的Form信息；为将地址重写成标准的<a href="mailto:user@fully.qualified.domain">user@fully.qualified.domain</a>格式进行排列；重信头中抽出收件人的地址；将邮件投入incoming队列中，并请求邮件队列管理进程处理该邮件；请求trivial-rewrite进程将地址转换成标准的<a href="mailto:user@fully.qualified.domain">user@fully.qualified.domain</a>格式。</p>
		<p>2.2 投递邮件的过程</p>
		<p>　　新邮件一旦到达incoming队列，下一步就是开始投递邮件，postfix投递邮件时的处理过程如图三所示。相关的说明如下：</p>
		<p>　　邮件队列管理进程是整个postfix邮件系统的心脏。它和local、smtp、pipe等投递代理相联系，将包含有队列文件路径信息、邮件发件人地址、邮件收件人地址的投递请求发送给投递代理。队列管理进程维护着一个deferred队列，那些无法投递的邮件被投递到该队列中。除此之外，队列管理进程还维护着一个active队列，该队列中的邮件数目是有限制的，这是为了防止在负载太大时内存溢出。邮件队列管理程序还负责将收件人地址在relocated表中列出的邮件返回给发件人，该表包含无效的收件人地址。</p>
		<p>　　如果邮件队列管理进程请求，rewrite后台程序对收件人地址进行解析。但是缺省地，rewrite只对邮件收件人是本地的还是远程的进行区别。</p>
		<p>　　如果邮件对你管理进程请求，bounce后台程序可以生成一个邮件不可投递的报告。</p>
		<p>　　本地投递代理local进程可以理解类似UNIX风格的邮箱，sendmail风格的系统别名数据库和sendmail风格的.forward文件。可以同时运行多个local进程，但是对同一个用户的并发投递进程数目是有限制的。你可以配置local将邮件投递到用户的宿主目录，也可以配置local将邮件发送给一个外部命令，如流行的本地投递代理procmail。在流行的linux发行版本RedHat中，我们就使用procmail作为最终的本地投递代理。</p>
		<p>　　远程投递代理SMTP进程根据收件人地址查询一个SMTP服务器列表，按照顺序连接每一个SMTP服务器，根据性能对该表进行排序。在系统负载太大时，可以有数个并发的SMTP进程同时运行。</p>
		<p>　　pipe是用于UUCP协议的投递代理。 </p>
		<p>
				<a href="http://blog.csdn.net/hwz119/archive/2007/06/17/1655400.aspx">
				</a> </p><img src ="http://www.cppblog.com/lapcca/aggbug/114572.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/lapcca/" target="_blank">阿π</a> 2010-05-06 11:09 <a href="http://www.cppblog.com/lapcca/archive/2010/05/06/114572.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>