« May 2005 | Main | July 2005 »

June 28, 2005

qmail sendmail postfix - 三种MTA的比较

关于sendmail/qmail/postfix孰优孰劣,以及部署邮件系统的时候该选哪一个的讨论已经重复了千百次了。但事实往往并不是A好B坏,或B好A坏,必须根据场合和应用的要求来定。但虽然如此,大多数人还是需要一个相对公平的评价,以引导邮件系统的部署。

自己一直很慎重于回答这类问题,以免引发不必要的争论甚至矛盾,但还是必须面对这个问题做一定的分析和比较的,否则很多朋友经常会问“到底用哪个好?”,却拿不出完整的答案。

首先看看三个MTA的历史...

MTAs的发展历史

Sendmail
毫无疑问,sendmail是最古老的MTA之一。它比qmail和postfix要古老得多。最早它诞生的时候,Internet还没有被标准化,当时主机之间使用的是UUCP技术来交换邮件。

它被设计得比较灵活,便于配置和运行于各种类型的机器。

Qmail
qmail是新生一代的MTA代表,它以速度快、体积小、易配置安装等特性而著称。作者D. J. Bernstein(djb)是一个数学教授,富有传奇色彩。djb于1995年开发qmail,1996年发布0.70版,并使用了多种当时比较先进的技术,包括Maildir,与sendmail单个binary不同的模块化设计,权限分离,以及使用了大量由djb编写的配套工具,如daemontools,ucsip-tcp等。

qmail迅速成为了Internet上最有名的MTA,使用者众。

Postfix
Postfix作者是Wietse Venema,一名著名的安全专家。最早postfix起源于1996年,当时venema 在美国IBM研究中心负责研究更安全的邮件系统,当时称为Vmailer。后因为商标问题于1998年11月正式更名为Postfix

Postfix以替代sendmail为目的,并提供了一个更安全、更高性能的灵活的体系。它同样也采用模块化设计,使用了大量优秀的技术,以达到安全的目的。由于作者的设计理念独到,经过7,8年时间,Postfix现今已发展成为功能非常丰富,扩展性和安全性强的优秀MTA。

概括的比较

以下的分析主要基于我在CASA上发的一个小文章,对sendmail/qmail/postfix做了一个概括性的比较。

sendmail
sendmai功能非常强大,很多先进功能在sendmail上都最先有实现。sendmail里的Milter技术是一个非常好的框架,目前postfix及qmail仍然没有官方发布的方案比milter要好。

但sendmail也有典型的历史问题,只有一个binary程序,需要sid权限,m4配置文件复杂难懂。这些是是阻碍sendmail更好发展的一些客观问题。客观来说,调教得好的sendmail,其性能也是相当不俗的,据一个国外的Unix杂志称,在solaris+内存文件系统+带电池的raid系统下,sendmail能达到惊人的287封/秒的注入速度!

目前sendmail比较适合那些老用户,因为他们习惯了sendmail的应用环境和配置。

qmail
qmail体积非常小巧,source的gz包大概只有260多K,是三大MTA中最小的!模块化设计,避免了sid问题,基本功能齐全。配置相对sendmail而言,简单了很多,而且用户非常广泛。而且补丁和插件非常多,例如著名的vpopmail,netqmail,以及qmail-ldap等。

但qmail有几个问题,一是djb已经5,6年没有继续开发了,补丁的良莠不齐及版本依赖是非常麻烦的事,这对初学者极为不利。二是功能扩充需要补丁来完成,扩展能力不足。

总体上qmail依然是个非常不错的选择。对于希望了解mta原理,或希望修改mta代码的爱好者,qmail是值得推荐的。对于需要建立中小型邮件系统的用户也同样适合。而对于需要丰富功能却不想面对补丁困难,或者需要建立大型的系统,qmail不太合适,需要更丰富的经验和技术。

postfix
postfix如今已经独树一帜,流水线、模块化的设计,兼顾了效率和功能。灵活的配置和扩展,使得配置postfix变得富有趣味。其主要的特点是速度快、稳定,而且配置/功能非常强大,并和sendmail类似,提供了与外部程序对接的API/protocol。尤其是配置部分,可以说是一扫qmail和sendmail的各自缺点。

但postfix管理及配置的入门依然需要一定的工夫,必须仔细阅读官方文档。postfix另一个优势是至今依然保持活跃的开发工作,而且稳步发展,适合高流量大负载的系统,扩充能力较强。

大规模应用例子
国内若干个大型email ISP(如163.net/tom.com/163.com及sohu等)过去都使用qmail,后来全部更换成postfix。

新浪使用qmail,yahoo使用qmail。但这些已经不是普通的qmail了。

技术层面的分析

这里仅探讨一些典型的技术特点,从这些特点可以看出每个MTA设计的异同,主要讨论的焦点是qmail和postfix。

磁盘I/O
从队列文件的读写来看,qmail处理每一封邮件时,都至少需要建立3个文件,mess, intd, info等。而Postfix使用的是单队列文件设计,因此磁盘I/O的开销要比qmail小得多,如果仅仅从这个方面考虑,postfix的队列是qmail的2-4倍那么快。

从我过去的一个qmail vs postfix对比测试中,也可以发现这个问题。

数据同步
如果从MTA对待操作系统的文件是否安全写入磁盘的策略来看,qmail和postfix也是不同的。Postfix使用的是随机写,并且需要写入完成并安全同步到磁盘后才算完成。而qmail的写入则是即刻执行的,因此它将等待数据安全写入磁盘后才返回。对于高流量的系统而言,这将导致性能问题。

此外,Postfix的队列对于FreeBSD的softupdate是安全的,而qmail则是不安全的,qmail作者明确警告用户不要使用softupdate,除非是有磁盘后写电池。

扩充能力
sendmail有着非常好的扩充能力,支持众多的特性,功能可谓豪华。包括频率控制到集群支持应有尽有。而milter API则更加使sendmail的灵活性发挥至极,通过milter,用户可以对邮件几乎所有的参数进行控制!但是在存储方面,由于只支持mbox,会有一定的问题。

qmail在系统容量扩展上有着独到的设计,配合qmail-ldap补丁,可以充分利用qmqp及分布存储的优势。现今已有各式各样的qmail扩展方案,最著名的是qmail-ldap。但qmail缺乏类似milter的设计,功能扩展需要各种补丁,而补丁的设计水平参差不齐,配置能力有限。实施起来相对是最复杂的。

Postfix同样有着非常好的容量扩充能力,利用LMTP或transport的/alias的方法,可以分布式的存储邮件,扩充容量。同时postfix的功能扩展也非常强,通过灵活的配置即可实现复杂的功能,这是其最突出的优点之一,是qmail望尘莫及的。此外,类似sendmail的milter,postfix拥有content_filter和policy 两个与外部程序/应用对接的接口,但不如milter那样功能集中和灵活,也没有完整实现qmail的qmqp及类似qmail-ldap的机制。

可配置性
sendmail 使用m4语法,单一的主配置文件(sendmail.cf)是三个mta中最难使用的,但是如果熟悉使用的话却能实现复杂的功能。

qmail使用的是大量小配置文本,格式最简单,每个配置一个文件,存放在/var/qmail/control目录里。

postfix也使用单一的主配置文件(main.cf),同时还有对应master主服务进程的配置文件master.cf,但使用的是简明易懂的key = value 格式。

总体而言,qmail的配置文件较易管理(格式最简单)但配置文件多(10个以上),而postfix的格式简单只有2个配置文件,并配备强大的postconf工具,sendmail的配置文件最复杂。

数据库支持
sendmail通过一些插件/补丁,可以支持mysql/pgsql/oracle等,ldap及小型的dbm/cdb等数据存储格式。

qmail默认只支持cdb,需通过补丁才可支持ldap,mysql,pgsql及oracle等。

postfix可以支持的数据库应该是最多的,默认就包括了mysql/pgsql/ldap及dbm/cdb和cidr/nis*/btree等一堆。还支持特殊的tcp_table(仅在snapshot里支持)

稳定性/负载能力
sendmail, qmail, postfix都比较稳定。在高负载下,配置不佳或没有打足够补丁的qmail容易被DOS攻击打跨,而postfix在遇到超过配置的限制时会降低处理能力,但系统依然有一定资源可用。

作者介绍
sendmail - Eric Allman Unix专家、学者
qmail - DJB 数学教授,科学家
Postfix - wietse venema 安全专家 学者

Recommentaion - 建议

我建议在使用Postfix MTA,无论是小型系统,还是大中型系统,能带来最高的性价比。

一些有用的link

在足够好的硬件条件下Postfix比qmail更快的原因分析
benchmark,无聊还是骗局?
qmail/postfix/sendmail 比较
Sendmail 历史
有关mta benchmark
Sendmail性能调整
qmail可靠性FAQ

Posted by hzqbbc at 12:15 PM | Comments (3)

June 27, 2005

Mbox vs Maildir - 两者原理和区别

在开放源代码的世界里,电子邮件服务器最主流的目前有三种,分别是sendmail, qmail, postfix。而存储格式最流行的有两种,Mbox和Maildir,它们都是开放的存储格式,因此兼容性比较好。

而mbox和maildir的历史,却不为大多数用户所了解。今天和一个朋友聊到mbox和maildir哪个好,顺便搜集一下mbox和maildir的资料,谈谈这两者的原理和区别,以及应该如何做选择。

Mbox的历史较Maildir悠久,sendmail支持mbox,qmail和postfix都支持mbox,其主要特点就是“所有邮件都存放到一个文件里”。每个邮件之间以特定的标记分割。

Maildir则相反,每一封邮件保存成一个文件,每个文件名称一般有一定的规律,例如会包含时间戳、pid及inode节点号等。

除了mbox和maildir格式外,还有一个叫mbx的格式,它是对mbox的改进版本。主要用在UW-IMAP server里。它最大的特点是有一个针对mbox文件的索引,能改善读/写性能。但依然需要file lock。

随着qmail/postfix的普及,以及mbox的一些问题暴露,maildir得到了越来越多的应用。其中mbox的最主要问题是文件缩定(file lock),其次是大多数update操作的效能问题。

以下是引自courier-mta.org上的mbox和maildir的测试对比文章,介绍了mbox和maildir:

mbox mail storage format

This is the traditional way to store mail on UNIX-based mail servers.
Individual messages are simply concatenated together, and saved in a
single file. A special marker is placed where one message ends and the
next message begins. Only one process can access the mbox file in
read/write mode. Concurrent access requires a locking mechanism. Anytime
someone needs to update the mbox file, everyone else must wait for the
update to complete.

maildir mail storage format

Maildirs were originally implemented in the Qmail mail server, supposedly
to address the inadequacies of mbox files. Individual messages are saved
in separate files, one file per message. There is a defined method for
naming each file. There's a defined procedure for adding new messages to
the maildir. No locking is required. Multiple processes can use maildirs
at the same time.

mbx mail storage format

This is a slightly modified version of the original mbox format that's offered by the UW-IMAP server. mbx mailboxes still require locking. The
main difference from the mbox format is that each message in the file is
preceded by a record that carries some message-specific metadata. As
such, certain operations that used to require the entire mbox file to be
rewritten can now be implemented by updating the fixed-size header
record.

This benchmark focuses mainly on the mbox and maildirs formats. In March of 2003 an unrelated party conducted a similar benchmark for mbx
formats. See http://www.decisionsoft.com/pdw/mailbench.html
for more details.

Mbox vs Maildir之优缺点比较

这里给出一个基本的特性对比,读者很容易就清楚根据自己的应用到底应该选什么存储格式:

可靠性
优选是Maildir,因为mbox只有一个文件,一旦出问题之后,所有邮件都将损毁。

更新速度
这里主要指的是删除/增加邮件的能力,无疑Maildir完胜Mbox

搜索速度
这点Mbox因为是单文件,因此搜索的能力要强于maildir

并发访问能力
对于繁忙的邮件系统,多个进程同时访问同一封邮件是可能的事情,Mbox需要flock()的支持,而且如果某一个进程操作时间长,则其他所有进程都堵塞了。Maildir没有这个问题。在NFS等网络文件系统上,Maildir相对安全,Mbox不能用于此类型环境

扩充能力
现在的邮箱已经不是5年前甚至10年前的1MB,2MB而是100,200甚至1G/2G,Mbox应付那么大的容量已力不从心,无疑Maildir是比较适合的。

文件系统依赖
Maildir较依赖文件系统,尤其是依赖对目录的索引能力,用ReiserFS会比较快,对于超大型的maildir,读写性能将受到考验。相对而言Mbox则不存在这个问题。

综合结论

推荐使用Maildir格式,安全可靠,绝大部分操作都远快于Mbox。而且现今支持Maildir的软件越来越多,qmail/Postfix 都支持。

一些有用的链接

原理标准
http://cr.yp.to/proto/maildir.html
http://www.qmail.org/qmail-manual-html/man5/mbox.html

Benchmark/比较
http://www.courier-mta.org/mbox-vs-maildir/
http://www.decisionsoft.com/pdw/mailbench.html

转换工具
http://www.qmail.org/mbox2maildir
http://batleth.sapienti-sat.org/projects/mb2md/

Posted by hzqbbc at 10:45 PM | Comments (2)

awstats auto update的方法及perl脚本

awstats是一个功能强大的日志分析工具,对于一个简单的网站而言,只需要根据awstats官方网站的帮助来安装和配置即可使用。但是如果需要服务多个网站,例如提供虚拟主机的ISP,需要统计大量的网站的话,常规的方法有点麻烦。

实现自动统计更新是一个必然的需求。实现起来也比较简单,一些shell脚本+perl脚本,配置一下crontab就差不多了。

1. 配置Apache调整日志存储方式
为每一个虚拟主机单独保存日志,这样便于统计。注意,配置的命令必须在每一个虚拟主机的配置里面,即里,简单的配置如下:(以www.hzqbbc.com为例)

CustomLog /var/log/httpd/www.hzqbbc.com_log combined

2. 配置awstats 的per Host config
为每个要进行统计的虚拟主机单独配置一个配置文件,在我的awstat安装中配置文件的目录放在/etc/awstats下,每个虚拟主机的配置文件名都有如下格式(以hzqbbc.com为例):

awstats.www.hzqbbc.com.conf

每个配置文件里只需要指定几个主要参数即可。其中如下几个参数必须指定:

LogFile="/var/log/httpd/www.hzqbbc.com_log"
SiteDomain="hzqbbc.com"
HostAliases="www.hzqbbc.com 127.0.0.1 localhost"

3. 自动更新脚本
以下是自动更新脚本,命名为cron.pl


#!/usr/bin/perl -w
use strict;
my @list = glob("/etc/awstats/*");

for(0...scalar @list-1) {
         $list[$_]=~s#.*/awstats\.([^\/]+)\.conf$#$1#;
}

foreach(@list) {
         `perl awstats.pl -config=$_ -upate`;
}
exit(0);

将这个perl脚本防止在awstats的cgi-bin目录里,即/var/www/cgi-bin/awstats/wwwroot/cgi-bin里

4. 配置crontab
以root身份登陆系统,执行crontab -e,增加一条记录:

0 */3 * * * (cd /var/www/cgi-bin/awstats/wwwroot/cgi-bin/; perl cron.pl)

这里定义了每隔3小时呼叫一次cron.pl并更新日志。如果机器的负载很高,并且虚拟主机的数量巨大,那么建议每天只更新1次,并且在深夜负载低时进行。

经过上述配置后,以后新增的虚拟主机只需要简单增加一个awstats的配置即可实现自动的日志分析。访问时,只需要将URL中的config=xxx替换成相应网站名即可。

Posted by hzqbbc at 09:19 AM | Comments (1)

June 26, 2005

Travel story - 云南丽江之行 - 惊险之旅、夜游古城

这次云南之行失望的地方就在去卢沽湖的路上,在宁蒗县遭遇到村民暴动,堵塞了交通。最终没办法通过,只好折返丽江。

据说是是在另一条老路上,一个本地人无驾驶证,开着一台没有车牌的黑车,撞了4个人,一个妇女带着3个小孩,其中一个是8个月大的,被撞出去好远,当场死了,而妇女据说被压在车轮下,也应丧命。由于肇事者无甚家产,卖了他也赔不了几个钱,于是村民累起石墙,拦截来往车辆,后来警察出动也无济于事,最后演变成了骚乱,堵在出事点的个别旅游车被石头砸烂玻璃,还有游客被打破脑袋!

呜呼哀哉! 罪过啊! 不过听同路折返的游客称,他们同团早一批人去了卢沽湖,大呼上当,不好玩。不知是真是假,只有以后再探卢沽湖才能下定论了。

虽然说去不成卢沽湖,还遇到了惊险之事,但在堵车时拍了一些有趣的照片。

宁蒗县的彝族妇女,身后背着竹筐

两个可爱的彝族小娃娃,其中左边那个女孩对着镜头一点都不害羞!

面对镜头不害羞的彝族小女孩

这个彝族小孩则比较害羞,一看见我拿着照相机对着他,他就不好意思了

一个躲在屋檐下玩耍的小男孩

村民集结在出事点,堵塞了交通,各路汽车无奈地在路的两旁等候事情结束

这是离开出事点的方向,车龙很长

只有孩子们不知道发生了什么事,疑惑的看着路上长长的车龙

折返丽江

在南方,这样的景色并不多见,由于这里地处高原,空气非常干净,能见度很高,因此才有如此的效果。

夜游丽江古城
由于此次出游没带三脚架,所以夜景没什么可拍的。在古城里四处走走,感受着喧闹的气氛。夜晚的古城灯火通明,如果说丽江古城的特色,我看夜晚的古城之美就是一大特色。各式灯笼挂在客栈、店铺的屋檐下,显得非常朴质和舒服。

城内小河边的酒吧,据说在这里喝茶吃小吃,聊天是最写意的

高原地区才盛产牦牛,而牦牛的牛角制品则是纪念品的好选择,可惜我对这玩意没兴趣:)

各种手工艺品的小商店随处都是,而且琳琅满目,让人很有购买的欲望!

楼下是小商店,楼上是客栈,这样的布置可以让客人从容的欣赏早晨的古城风光

古城的标志,水车,后面是江泽民的题字

村民闹事后记

后来听当地司机说,这次村民闹事到了第二天才解决,导致的旅游损失至少也有好几万元。而据他们说,彝族人很野蛮,每到黄金周的宁琅,村里的彝族人就在一些险要的地方设“卡”,放滚木和拦路石,抢劫过路的汽车和行人。一般是在山上吊下一个竹篮,要过路的将财物放进去,才移开障碍物让人/车通过。所以云南这一带的司机,一般都会赶在天黑前通过这里,以免出意外。

而那里的小孩也经常在马路上,佯装被车撞到,要车主赔钱,一个司机更愤怒的说“彝族人以偷为乐,以抢为荣,他们没什么宗教信仰,谁偷盗抢劫厉害,谁就受其他彝族人看得起。”这听起来真的有点让人心惊肉跳的!

Posted by hzqbbc at 05:31 PM | Comments (1)

June 25, 2005

Netcraft Web server 6月报告:Apache遥遥领先IIS

在lighttpd邮件列表上看到有一个讨论讲到lighttpd的普及问题,刚好里面引用了netcraft 最新的6月份web server统计报告。于是阅读了一下该报告,发现Apache服务的站点超过4500万个,而IIS仅有1300万,真是令人振奋!

而其他的web server,如Netscape Enterprise和Zeus则比Apache和IIS少了一个数量级,只有180万和58万左右。

在统计列表里,不可忽视的是Thttpd排到了前10名!

Resin 也比Lotus Notes要前,还有各种各样的web server占据着分额非常小的市场。我比较看好的lighttpd还显得很稚嫩,只有不足2000个站使用。

看样子lighttpd还要好好努力啊。

Netcraft网站上有近10年来的web server的市场分额统计和分析,回首10年前(1995年),那个时候还是NCSA 和CERN的天下。技术的发展十分迅速,真的有点“江山倍有人才出”的感觉。

关于Netcraft的全部报告请看:

http://survey.netcraft.com/Survey/Reports/0506/

http://survey.netcraft.com/survey/Reports/9508/ALL/

Posted by hzqbbc at 11:57 PM | Comments (1)

June 24, 2005

Travel story - 云南丽江之行 - 属都湖、松赞林寺

香格里拉有很多著名的高山湖泊,其中属都湖就是一个。属都湖海拔有3300多米。从香格里拉出发,大致要1个小时的车程。以下是摘自网上的属都湖的介绍:

“属都湖”位于云南省中甸县,距县城约30公里,海拔3450米。这里景色
迷人、花香遍地。属都湖湖面约5平方公里,四周群山环抱、苍松翠绿。她
远离市井的喧嚣,远藏深山,极具原始、自然、纯朴的美。

兴致勃勃的去到属都湖,本来以为象是书里写的那样美,然而实际却不是这样。由于去的时候云南一直没有多少雨水,而且现在是夏天,过了花开的季节,因此一眼望去,几乎看不到任何的花草,水也不是很大。但是,当阳光明媚的时候,感觉又不同了。嘿嘿!

属都湖正面,虽然说风光没有好季节时好,但是在湖边,洁净的湖水,清新的空气,还是让人心旷神怡的。

湖边的藏民,热情好客,非常淳朴

牧民的马,在湖边的草淀上悠然的吃着肥美的草

空旷的草淀

一位年长的藏民,是在湖边抓拍的

属都湖入口一角

属都湖入口木牌坊下的祖孙俩,拍照可是要给钱的,不过我用长焦搞定了,免费:)

藏区剪影
沿路经过一些藏族工艺品的商店,还有县城及阿布老屋。这里生活比较悠闲,和繁忙的广州完全不是一个概念。

藏族煮食物的器皿,后面是藏传佛教的图画

县城内的一座藏民开的小旅店

青稞酒吧(酒吧牌子上也用藏语写了“青稞酒吧”字样)

阿布老屋
据老人介绍,该老屋是保存得比较完好的旧式建筑,屋子里还保留了一般老百姓家不可能有的雕刻有龙的窗花,以及过去文化大革命时留下的一些寺庙的残骸。

阿布老屋一个房门的门锁

老屋的厨房

阿布的后人,也是现在屋主

松赞林寺
松赞林寺是云南省规模最大的藏传佛教寺院,也是康区有名的大寺院之一。松赞林寺又称归化寺,距中甸县城5公里。

松赞林寺于藏国第十一绕迥阴土羊年(公元1679年)兴建,于阴铁鸡年(公元1681年)竣工。五世达赖喇嘛亲赐名“噶丹松赞林”。

寺中僧侣分为活佛和扎巴两类,除活佛外的僧侣按学历及资历分为格西、格弄、班卓等,按所承提职责分则有喀姆、老僧、香追、第巴、英则、格干等十数种职务。

第一次参观藏传佛教的寺庙感觉非常非常的好,香格里拉的天气非常好,蓝天白云,空气也非常好。来到寺庙大典门前,感觉心灵也被净化了。

在松赞林寺门口仰望高高在上的大典,感觉离佛祖不远了...

正殿楼顶的装饰物,据说每个装饰物都有一段故事

寺庙四周有很多住房,这些大多是供僧侣居住的

正殿门前,金顶耀眼的光芒十分夺目

据说楼顶的柱子是真金的,在阳光下简直亮得让人睁不开眼,下面是吐宝兽

纳帕海
纳帕还也是一个高原湖泊,但是由于云南最近干旱,湖泊几乎都没有任何的水了,所以看上去只是一个大草地。但是还是很多游人去观光,还有马和牛可以骑。在这里我们呆了一会就撤了。

没有水的纳帕还成了一个草地

Posted by hzqbbc at 07:59 AM | Comments (0)

June 23, 2005

Travel story - 云南丽江之行 - 虎跳峡、长江第一湾、小中甸

据资料称,云南有25个少数民族,而丽江则集中了90%的少数民族,达到了22个之多。其中以纳西族为主。丽江的气候宜人,听当地导游说,冬天大约在8-12度,夏天18-22度,而冬天则会下大雪,因此很多山路因积雪要封闭。

长江第一湾
长江第一湾位于丽江和虎跳峡之间,大约要1个小时车程。这一景点个人认为没无什么特别之处,如果要说特别,那只能说这里是长江的上游,因此到了这里实在也只能说是“到此一游”了。公路边有卖纪念品、水果的小摊贩,稀疏的有三三两两的游客在此观赏。

这就是长江第一湾了

虎跳峡
虎跳峡在香格里拉和丽江之间,位于金沙江的源头,从丽江出发需要2个多小时的车程。虎跳峡之所以得名,是因为这里的水流湍急,虎跳石是一块位于江心的巨石。远远的就听到峡谷里流水的轰鸣声。在虎跳峡观赏点的入口处,有一道一百多级的石楼梯直下江边,但由于虎跳峡所在地海拔就已经有2000多米,因此上下这百来级台阶,明显要比在南方累。

在观赏点附近,还有一些当地人提供轿子服务,收2,30块可以将你从峡谷底抬到公路上边,虽说钱不少,但是却是非常辛苦。

湍急的江水猛烈的向下冲,站在旁边衣服都被水雾弄湿了。

虎跳石,背后是一座石桥

这是虎跳峡四周的大山,感觉自己在谷底就象蝼蚁般渺小...

远处的雪山,名字已经忘记了,因为这里到处都是雪山,只记得三座:哈巴、玉龙和梅里

小中甸
前往香格里拉的途中,会路过小中甸。小中甸一带是藏民的放牧之地,这里稀疏的分布着藏民的房子,根据房子的主梁大小以及窗子雕花的精美程度,即可知道这家人是否有钱,经济是否有实力。恰巧天公作美,天气不错,而且因为是傍晚,阳光象金子一样洒在小中甸的一草一木之上!

佛光普照,感觉那光已射入了自己的心...

这样的蓝天白云,看着真是让人舒服之极!在城市里,蓝天白云实在是稀罕之物

金色的柏油路,阳光迅速地在大地移动...

藏民的房子

宽阔的柏油马路

香格里拉的傍晚
香格里拉海拔3000多米,晚上8,9点才天黑,这里的天空本身就是一个看不腻的景点。在空旷的房顶,仰望苍天,真的有一种忘却烦嚣,归依自然的感觉:-)现在还非常怀念那种感觉......

美哉,壮哉!

Posted by hzqbbc at 08:06 PM | Comments (0)

June 19, 2005

Travel story - 云南丽江之行 - 概述

今年的事情特别多,特别累,心里积压不少郁闷,总想找个地方去走走,上周花了几天时间,去了一趟云南丽江,并顺带走了一趟香格里拉,感觉既新鲜快乐,也有一些失望,我不是什么文人雅士吧,失望的地方并不会欣赏吧,总体而言还是挺不错的。现在真有点后悔过去没抓紧时间,多看看壮丽的祖国河山:-(

接下来将丽江之行的种种经历,一一道来,由于是回忆录,所以内容上将适当的做些裁减,不做太多流水帐的记录。而为了方便那些没去过丽江和香格里拉的朋友,文中也将提供一些旅游TIPS,以备不时之需,说得坦白点,想去丽江的朋友可以参考一下,以免去到那不知民情吃亏了就不好了。

这次云南之旅主要去了几个地方:丽江,香格里拉,大理,途经昆明。主要的景点有:虎跳峡,小中甸,属都湖,阿布老屋,丽江古城,玉龙雪山,大理古城等。顺带去了纳帕海,红豆杉水滩,甘海子,金沙江,石寨子,香格里拉古城等。不过顺带去的那些都只是做短暂停留。

首先当然是看看那的风景了,这还用说?See see:

松赞林寺,又称小“布达拉宫”,不过相比之下,拉萨的布达拉宫还是要气势雄伟得多

彝族小娃娃

这里是凶险的金沙江,著名的虎跳峡景观。

虎跳石后面的石桥,桥底是山水

小中甸,佛光普照,这天的云彩真的美,实在是很能洗涤城市的心灵。

小中甸撒着落日阳光的柏油路,此时真的有几分旅游书上写的味道。

中甸牧民的牧场,也是种一些农作物的田地,民居稀疏地分布在牧场上。

高山湖泊,属都湖,藏语的意思是“奶酪的石头”。

草淀上的马儿在吃草,这是在云南与牧民的牲口接触较为接近的一次

夜幕下的丽江古城一角,各种手工艺品的小作坊小店,遍布在古城每一个角落

云南纳西古文化遗产,东巴象形文字,活化石,这里正中的文字含义为“我爱你”:-)

玉龙雪山一角,真有点冰天雪地的感觉了。

顶峰四周环绕着皑皑白云,不能见其真面目,甚为遗憾

顶峰左侧的小山峰

云杉坪附近的红豆杉景点(好象是叫红豆杉,当时没太仔细听当地导游说)

大理古城墙,远处是著名的洱海。

大理古城正门

Posted by hzqbbc at 08:40 AM | Comments (3)

June 06, 2005

关于google hzqbbc 有关的一些链接(funny!)

一时兴起,在google中搜索了一下关于我的代名词hzqbbc有关的链接,居然发现了很多连自己都忘记的内容,包括过去写的一些文章,一些非常有趣的帖子。

以下是一些精选:

Unix benchmark
Ubench 上对硬件用ubench的测试结果排名,我的测试结果排到了15名。其中“Mandrake 8.2 Linux 2.4.18 2xAMD MP 2400+ 2GHz 512Mb RAM”的结果是我在2002年时做的。

以下是测试具体结果:
http://www.phystech.com/download/ubench/ref110.html

Anti-spam-guide.net有关Anti-spam的链接
该页居然赫然写着“Hzqbbc”,真让我有点吃惊。

地址:http://anti-spam-guide.net/postfixantispam/

里面居然收录了www.hzqbbc.com,估计是搜索引擎的功劳。

公安部4号令应对方案
该文算是引用得较多的一篇了,当时发布在chinaunix.net的邮件技术版,由于当时信息产业部和公安部查垃圾邮件查得严格,文章也就算应了“节”了。

部分引用地址:
http://weblog.kreny.com/archives/2004/06/zt_e4_eaeae.html

SGI工作站下实现Apache代理
这真是一篇旧文了,应该是1999年-2000年间的文章。那时快毕业了,在母校生命科学院里帮一个老师打打杂,看看设备时写的。 可以通过“SGI图形工作站下使用Apache做代理服务器实例”关键字来搜索google.com关于本文的一些收录。

下面是一些引用的地址:
http://www.kehui.org/index.php?op=article&file=read&aid=24

http://herald.seu.edu.cn/blog/shiningray/articles/8540.aspx

PHPlib 使用教程
这也是早年读书接触php约1年后写的文章,那时隐约有了OO及模块化设计的思维。我

某些引用的地址:
http://www.perday.org/archives/125.html

OSLoader 多系统启动
这是比较早期的文章了,该文章当时被转载到不少地方。当时为了能同时引导Linux和windows NT/98,所以研究了好一阵子。现在这些文章基本都应该投入故纸堆了。

该文当时就由我的铁哥们zolo兄转载在水木清华。

引用地址:
http://www.linuxforum.net/books/smth/Linux.AIX/00000122/00000044.htm

后记
后来发现了一些很多年前写的文章(被转来转去的),内容自己都不认识了,如果不是看一些关键信息,已经不知道是谁写的了。

蓝点1.0正式版使用记 该文使用的是很早就废除使用的笔名。

网络数据库指南(翻译)

Posted by hzqbbc at 07:19 AM | Comments (0)

June 05, 2005

Mailing List (邮件列表)原理简述及我的perl实现

注: 本文本来是一早要写的,可是程序写了有段时间了,最近一段时间又很忙,居然给忘了,现在补上。

正文

大部分IT人员都使用过邮件列表,或者类似的服务,但邮件列表的内部工作原理则不是简单的订阅,退订阅那么简单。最近根据自己的一些认识,用perl实现了一个非常简单的MLM程序,也顺便谈谈邮件列表的最基本工作原理。

邮件列表,简单的来说,就是任一列表成员向该列表发的邮件,其他所有人(可以包括他自己)都能收到,并且每个人能自由订阅、退订。更丰富的邮件列表还包括了摘要,精确权限管理,web archive功能等等。

著名的开源邮件列表软件如mailman, majodomo, ezmlm, sympa, ecartis等都是功能完备的邮件列表软件,但归根结底,最简单的邮件列表至少应该包含如下功能:

要实现上述的功能,如果使用perl的话并不复杂,配合Postfix MTA可以非常方便的开发出简易的邮件列表软件。以下是自己开发的MMList(Mini Mailing List) 的基本结构:

MMList atomy(流程图)

配置

基于Postfix,使用alias的方法,将邮件通过管道送到MMList:

main.cf里需要配置的内容:

alias_maps = hash:/etc/postfix/aliases hash:/etc/postfix/mml.aliases
virtual_alias_maps = hash:/etc/postfix/mml.virtual_alias_maps

mml.aliases的内容:

# alias file
test-subscribe-hzqbbc.com:   "|/usr/bin/mml -cmd=subscribe -list=test@hzqbbc.com"
test-confirm-hzqbbc.com:     "|/usr/bin/mml -cmd=confirm -list=test@hzqbbc.com"
test-unsubscribe-hzqbbc.com: "|/usr/bin/mml -cmd=unsubscribe -list=test@hzqbbc.com"

mml.virtual_alias_maps的内容:

test-subscribe@hzqbbc.com        test-subscribe-hzqbbc.com
test-confirm@hzqbbc.com          test-confirm-hzqbbc.com
test-unsubscribe@hzqbbc.com      test-unsubscribe-hzqbbc.com

MMList 的perl实现


#!/usr/bin/perl -w
# vim: set cindent expandtab ts=4 sw=4:
# MMList - a very lightweight MLM software
#
# Author: He zhiqiang <hzqbbc@hzqbbc.com>
# CopyRight (c) 1998-2005 hzqbbc.com
#
# License: GPL v2
use strict;
use Getopt::Long;
use vars qw(%cfg $cmd $list @KEY_MAP);
use vars qw($user $subj $SLOG);
$user = $subj = "";

@KEY_MAP = (
    0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E',
    'F','G','H','I','J','K','L','M','N','O',
    'P','Q','R','S','T','U','V','W','X','Y',
    'Z','a','b','c','d','e','f','g','h','i',
    'j','k','l','m','n','o','p','q','r','s',
    't','u','v','w','x','y','z'
);

# proto-type:
# cmd ==> indicate the 'subscribe' or 'unsubscribe'
# list ==> indicate the list name

my $res = GetOptions(
	"cmd=s" => \$cmd,
        "list=s" => \$list
);

$cfg{'basedir'} = "/var/lib/mmlist";
$cfg{'listdir'} = $cfg{'basedir'}."/lists";
$cfg{'hostname'} = "list.hzqbbc.com";

open (MLOG, ">> $cfg{'basedir'}/mail.log");
open ($SLOG, ">> $cfg{'basedir'}/base.log");
# read from STDIN
while(<STDIN>) {
     print MLOG $_;
 
     if(/^From: (.*)$/) {
          chomp;
          m/([a-zA-Z0-9-_=\.]+\@[a-zA-Z0-9-_=\.]+)/;
          if($1) {
               $user = lc $1;
           }
      }elsif(/^Subject: (.*)$/) {
          chomp;
          $subj = $1;
          $subj =~ s/\s//g;
      }
}

syslog("cmd = $cmd");

if($cmd eq "subscribe") {
     if(user_exist($user)) {
          syslog("$user subscribed");
          my $body = q(Hey guy, you have already subscribed!);
          sendmail($user, "Subscribe failure", $body);
      }else {
          my $sid = gen_sid();
          open(FD, "> $cfg{'listdir'}/$list/queue/$user") or
              syslog("$!") and die "Can't write to $user, $!\n";
          printf FD "%s\:%s\n", time, $sid;
          close FD;
  
          syslog("confirm $user");
          my $body = "Hey guy, reply to me with the code $sid \n"
                    ."in the subject section\n";
          $list =~ m/([^:]+)\@(.*)/;
          my $from = "$1-confirm\@$2";
          sendmail($user, "Confirm subscribe", $body, $from);
      }
}elsif($cmd eq "confirm") {
     if(not user_exist($user)) {
          syslog("$user not exist");
          if(valid_sid($user, $subj)) {
               syslog("added $user");
               add_user($user);
               my $body = "Welcome to $list :-)\n";
               sendmail($user, "Added to the list", $body);
           }else {
               syslog("fail to confirm $user");
               my $body = "Hey guy, your confirm fail, please try again\n";
               sendmail($user, "Confirm failure", $body);
           }
      }else {
          my $body = "Hey guy, you step into a wrong situation!\n";
          sendmail($user, "Wrong action", $body);
      }
}elsif($cmd eq "unsubscribe") {
     if(user_exist($user)) {
          syslog("$user removed");
          del_user($user);
          my $body = "Hey guy, you have been removed from the $list\n";
          sendmail($user, "Goodbye - from $list", $body);
      }else {
          my $body = "Hey guy, you step into a wrong situation!\n";
          sendmail($user, "Wrong action", $body);
      }
}else {
     print STDERR "m3 error cmd!\n";
     exit(13);
}

exit(0);

## funcs to handle mail list
sub sendmail {
     my($to, $subj, $body, $from) = @_;
     if(not defined $from) {
          $from = "m3\@$cfg{'hostname'}";
      }
 
     open(CMD, "| /usr/sbin/sendmail -oi -t -f \"$from\" $to") or 
         die "Can't exec /usr/sbin/sendmail, $!\n";
     print CMD <<EOF
 Return-Path: $from
 From: $from
 To: $to
 Subject: $subj
 
 $body
 EOF
 ;
     close CMD;
}

sub user_exist {
     my $user = shift;
     if (! -r "$cfg{'listdir'}/$list/users.txt") {
          return 0;
      }
 
     open(FD, "< $cfg{'listdir'}/$list/users.txt") or die "Can't open $list, $!\n";
     while(<FD>) {
          chomp;
          if(/^$user$/i) {
               return 1;
           }
      }
     close FD;
     0;
}

# gen_sid - to generate unique Session id
sub gen_sid {
     my ($sid, $len) = ("", $_[0] ? $_[0]-1 : 23);
     srand(time());
     foreach(0...$len) {
          $sid .= $KEY_MAP[int rand(61)]; # total of $#KEY_MAP -1
      }
     $sid;
}

sub valid_sid {
     my ($user, $sid) = @_;
     open(FD, "< $cfg{'listdir'}/$list/queue/$user") or
         syslog("can't open $user, $!") and die "Can't open $user, $!\n";
     $_ = <FD>;
     chomp;
     ($_) = m/[^:]+:(.*)/;
     if($sid eq $_) {
          syslog("auth ok for $user");
          return 1;
      }
     close FD;
     return 0;
}

sub add_user {
     my ($user) = @_;
     unlink "$cfg{'listdir'}/$list/queue/$user"; # clean up user cookie/queue
     open(FD, ">> $cfg{'listdir'}/$list/users.txt") or
         die "Can't append to users.txt for $list, $!\n";
     print FD $user, "\n";
     close FD;
}

sub del_user {
     my ($user) = @_;
     my $buf = undef;
 
     open(FD, "< $cfg{'listdir'}/$list/users.txt") or
         die "Can't open users.txt for $list, $!\n";
     while(<FD>) {
          chomp;
          if(!/^$user$/) {
               $buf.="$_\n";
           }
      }
     close FD;
 
     open(FD, "> $cfg{'listdir'}/$list/users.txt") or
         die "Can't write to users.txt for $list, $!\n";
     print FD $buf;
     close FD;
}

sub syslog {
     my ($msg) = @_;
     chomp $msg;
     printf $SLOG "%s $msg\n", time;
}

Posted by hzqbbc at 03:13 PM | Comments (0)

June 03, 2005

Perl Socket 编程样例(2)

前两天介绍了使用Socket及IO::Socket 来进行TCP client/server的编程基本套路和代码,现在再介绍使用Socket及IO::Socket模块来进行Unix domain Socket的client/server开发。

Unix Domain Socket(简称unix socket)和TCP/UDP等INET类型socket相比起来有几个优点:

因此使用Unix socket来设计单机的IPC应用是首选。非常实用。大量的Unix应用软件都使用unix socket来进行程序间通信。

Unix Domain Socket客户端, Socket模块

简介:使用Unix domain socket的客户端。

#!/usr/bin/perl -w
use strict;
use Socket;
use IO::Handle;

my $path = $ARGV[0] || '/tmp/daytime.sock';

socket(my $sock, PF_UNIX, SOCK_STREAM, 0);
my $sun = sockaddr_un($path);
connect($sock, $sun) or die "Connect: $!\n";
$sock->autoflush(1);
my $buf = <$sock>;
my $bs = length($buf);
print "Received $bs bytes, content $buf\n";
close $sock;

Unix Domain Socket 服务端, Socket模块

简介:使用Unix domain socket实现的daytime服务器。

#!/usr/bin/perl -w
# tcp_socket_dt_srv.pl
use strict;
use Socket;
use IO::Handle;
use POSIX qw(WNOHANG);

my $path     = $ARGV[0] || '/tmp/daytime.sock';

$SIG{'CHLD'} = sub {
      while((my $pid = waitpid(-1, WNOHANG)) >0) {
            print "Reaped child $pid\n";
        }
};

socket(SOCK, PF_UNIX, SOCK_STREAM, 0)
    or die "socket() failed: $!";
setsockopt(SOCK,SOL_SOCKET,SO_REUSEADDR,1)
    or die "Can't set SO_REUSADDR: $!" ;

unlink $path if -r $path;

bind(SOCK,sockaddr_un($path))    or die "bind() failed: $!";
listen(SOCK,SOMAXCONN)           or die "listen() failed: $!";

warn "Starting server on path $path...\n";

while (1) {
      next unless my $sockname = accept(SESSION,SOCK);
      defined (my $pid=fork) or die "Can't fork: $!\n";
 
      if($pid==0) {
          SESSION->autoflush(1);
          print SESSION (my $s = localtime);
          close SESSION;
          exit 0;
       }else {
          print "Forking child $pid\n";
       }
}

close SOCK;

...to be continued

Posted by hzqbbc at 09:00 AM | Comments (1)

June 01, 2005

Perl Socket 编程样例(1)

Perl的networking 功能非常强大,基本上用c/c++能做的事perl都能做,而且做得更轻松方便,甚至可以只用10来行代码就完成了c/c++要几十上百甚至几百行才能完成得好的工作。

在networking方面,最基础的是BSD socket编程,但往往perl入门时在这个方面,最头疼的无疑是如何开始,如何Step by step。最好的药方就是Example,一段完整的可以运行(working)的代码,通过实践来感受远比看枯燥的manual来得深刻。

以下给出几段使用Socket及IO::Socket编写的Server/client,他们能实现最简单但是却最基本的任务,包括一个forking/accept的模型。可以直接复制这些代码,然后小加修改即可开发一些小型的tcp/udp应用了。

TCP 客户端, Socket 模块

简介:实现从服务器端读取一行信息然后返回


#!/usr/bin/perl -w
# tcp_socket_cli.pl
use strict;
use Socket;

my $addr = $ARGV[0] || '127.0.0.1';
my $port = $ARGV[1] || '3000';
my $dest = sockaddr_in($port, inet_aton($addr));
my $buf = undef;

socket(SOCK,PF_INET,SOCK_STREAM,6) or die "Can't create socket: $!";
connect(SOCK,$dest)                or die "Can't connect: $!";

my $bs = sysread(SOCK, $buf, 2048); # try to read 2048
print "Received $bs bytes, content $buf\n"; # actually get $bs bytes
close SOCK;

执行结果:

perl tcp_socket_cli.pl localhost 25
Received 41 bytes, content 220 ESMTP Postfix - ExtMail 0.12-hzqbbc

TCP 服务端 Socket模块, forking/accept模型

简介:一个多进程的TCP 服务器,sample中实现了daytime的功能

#!/usr/bin/perl -w
# tcp_socket_dt_srv.pl
use strict;
use Socket;
use IO::Handle;
use POSIX qw(WNOHANG);

my $port     = $ARGV[0] || '3000';
my $proto    = getprotobyname('tcp');

$SIG{'CHLD'} = sub {
     while((my $pid = waitpid(-1, WNOHANG)) >0) {
          print "Reaped child $pid\n";
      }
};

socket(SOCK, AF_INET, SOCK_STREAM, getprotobyname('tcp'))
    or die "socket() failed: $!";
setsockopt(SOCK,SOL_SOCKET,SO_REUSEADDR,1)
    or die "Can't set SO_REUSADDR: $!" ;

my $my_addr = sockaddr_in($port,INADDR_ANY);
bind(SOCK,$my_addr)    or die "bind() failed: $!";
listen(SOCK,SOMAXCONN) or die "listen() failed: $!";

warn "Starting server on port $port...\n";

while (1) {
     next unless my $remote_addr = accept(SESSION,SOCK);
     defined(my $pid=fork) or die "Can't fork: $!\n";
   
     if($pid==0) {
          my ($port,$hisaddr) = sockaddr_in($remote_addr);
          warn "Connection from [",inet_ntoa($hisaddr),",$port]\n";
          SESSION->autoflush(1);
          print SESSION (my $s = localtime);
          warn "Connection from [",inet_ntoa($hisaddr),",$port] finished\n";
          close SESSION;
          exit 0;
      }else {
          print "Forking child $pid\n";
      }
}

close SOCK;

利用上述tcp_socket_cli.pl访问该server的执行结果:

[hzqbbc@local misc]$ perl tcp_socket_dt_srv.pl 
Starting server on port 3000...
Connection from [127.0.0.1,32888]
Connection from [127.0.0.1,32888] finished
Reaped child 13927
Forking child 13927

TCP 客户端 ,IO::Sockiet模块

简介:同样为客户端,不过使用的是IO::Socket 面向对象模块

#!/usr/bin/perl -w
# tcp_iosocket_cli.pl
use strict;
use IO::Socket;

my $addr = $ARGV[0] || '127.0.0.1';
my $port = $ARGV[1] || '3000';
my $buf = undef;

my $sock = IO::Socket::INET->new(
        PeerAddr => $addr,
        PeerPort => $port,
        Proto    => 'tcp')
    or die "Can't connect: $!\n";
$buf = <$sock>;
my $bs = length($buf);
print "Received $bs bytes, content $buf\n"; # actually get $bs bytes
close $sock;

TCP 服务端, IO::Socket模块, forking/accept模型

简介:同样的一个daytime 服务器,使用IO::Socket重写。

#!/usr/bin/perl
# tcp_iosocket_dt_srv.pl
use strict;
use IO::Socket;
use POSIX qw(WNOHANG);

$SIG = sub {
     while((my $pid = waitpid(-1, WNOHANG)) >0) {
          print "Reaped child $pid\n";
      }
};

my $port     = $ARGV[0] || '3000';
my $sock = IO::Socket::INET->new( Listen    => 20,
                                  LocalPort => $port,
                                  Timeout   => 60*1,
                                  Reuse     => 1)
  or die "Can't create listening socket: $!\n";

warn "Starting server on port $port...\n";
while (1) {
     next unless my $session = $sock->accept;
     defined (my $pid = fork) or die "Can't fork: $!\n";
 
     if($pid == 0) {
          my $peer = gethostbyaddr($session->peeraddr,AF_INET) || $session->peerhost;
          my $port = $session->peerport;
          warn "Connection from [$peer,$port]\n";
          $session->autoflush(1);
          print $session (my $s = localtime), "\n";
          warn "Connection from [$peer,$port] finished\n";
          close $session;
          exit 0;
      }else {
          print "Forking child $pid\n";
      }
}
close $sock;

...to be continue...

Posted by hzqbbc at 09:03 AM | Comments (0)