##language:zh <> = freebsd = == qmail下使用python编写脚本来过滤邮件和发送短信 == 实现qmail下邮件过滤和邮件到达短信通知 作者:梅劲松 时间:2004年9月10日 感谢:HD、刘鑫 短信这么火热,如果自己的邮件服务器也能和短信结合起来,那多么好啊?在qmail下,我用qmail_queue来解决邮件过滤和短信到达通知。 本邮件系统在freebsd 4.10上实施成功。 1. 对于邮件系统的安装,没有太多说的。建议大家看HD的[[ http://bsd.huangdong.com/server/mail/qmail.html ]]来安装。如果已经安装了有qmail+vpopmail+qmail_queue补丁的,请直接跳到4继续安装。在新装邮件服务器时有几点需要注意的是,安装qmail的时候应该将步骤改为: {{{ cd /usr/ports/mail/qmail make WITH_QMAILQUEUE_PATCH=yes WITH_BIG_TODO_PATCH=yes WITH_OUTGOINGIP_PATCH=yes WITH_PRESERVE_CONFIG_FILES=yes extract cd /usr/ports/mail/qmail/work/qmail-1.03 fetch http://www.nimh.org/dl/qmail-smtpd.c cd /usr/ports/mail/qmail make WITH_QMAILQUEUE_PATCH=yes WITH_BIG_TODO_PATCH=yes WITH_OUTGOINGIP_PATCH=yes WITH_PRESERVE_CONFIG_FILES=yes install make disable-sendmail make enable-qmail make clean }}} 在安装vpopmail的时候可能因为port更新的原因让使用vpopmail的时候出现奇怪问题,最好是安装webmin将建立的vpopmail数据库删除后,重新建立,并指定数据库的用户权限。由于webmin的安装不是本文重点,这里将忽略。 2、关于文章里面的杀毒部分因为系统效率问题,我没有安装。各位请自行决定是否安装。 3、反垃圾邮件当然需要,我用的是[[ http://anti-spam.org.cn ]]的cbl+服务。使用的时候只需要将smtpd的脚本改为: {{{ #!/bin/sh QMAILDUID=`/usr/bin/id -u qmaild` NOFILESGID=`/usr/bin/id -g qmaild` exec /usr/local/bin/tcpserver -H -R -l 0 -p -x \ /usr/local/vpopmail/etc/tcp.smtp.cdb -u"$QMAILDUID" \ -g"$NOFILESGID" -v -c100 0 smtp rblsmtpd \ -r cblplus.anti-spam.org.cn \ -r relays.ordb.org \ /var/qmail/bin/qmail-smtpd /usr/local/vpopmail/bin/vchkpw-smtpd /usr/bin/true 2>&1 }}} 从[[ http://anti-spam.org.cn/cgi-bin/rblclient/(你的邮件服务器的dns服务器IP地址) ]]看到你的邮件服务器的流量和使用cbl+服务的情况。 4、这里是重点了。过滤和短信到达是需要qmail_queue来完成的,一定要打这个包。 使用python来实现这个功能,当然需要安装python啦。 {{{ cd /usr/ports/lang/python make;make install;make clean }}} 一般来讲这个安装是非常顺利的。 安装结束后。 {{{ cd /var/qmail/bin }}} 编辑qmfilt.py,内容如下: {{{ #!python #!/usr/local/bin/python -- import os, sys, string, time, traceback, re, socket Version = '1.1' PyVersion = '1.0' Logging = 1 Debug = 0 QmailD = 82#这里需要和你/etc/password里面qmaild用户的一样 LogFile = None TestMode = None Filters = [] MailEnvelope = '' MailData = '' ValidActions = { 'trap': '', 'drop' : '', 'block' :'' } #这里是你通过ucp协议将消息发送到哪个服务器的哪个端口 def mail_sms(msg): host = "xxx.xxx.114.2" port = 9999 buf = 500 addr = (host,port) # Create socket UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) def_msg = msg; UDPSock.sendto(def_msg,addr) # Close socket UDPSock.close() def openlog(): global LogFile if not Logging: return try: LogFile = os.open('/var/log/qmfilt', os.O_APPEND|os.O_WRONLY|os.O_CREAT, 0744) except: if TestMode: print "Can't create /var/log/qmfilt" else: # Indicate a write error sys.exit(53) def log(message): message = time.asctime(time.localtime(time.time())) + " - " + message + "\n" if Logging: os.write(LogFile, message) # Indicate a write error #sys.exit(53) if TestMode: print message def showFilters(): if len(Filters) == 0: print "No filters" for f in Filters: print "Filter - %s - Action: %s Regex: %s" % (f[0], f[1], f[2]) def readFilters(): global Filters try: file = open('/var/qmail/control/qmfilt', 'r') configLines = file.readlines() file.close() except: log("Can't read /var/qmail/control/qmfilt") if not TestMode: # Indicate a 'cannot read config file error' sys.exit(53) reg = re.compile('(.*)::(.+)::(.+)::') for line in configLines: if line[0] == '#': continue m = reg.match(line) if m != None: action = string.lower(m.group(2)) if not ValidActions.has_key(action): log("Invalid action in config file [%s] - SKIPPED" %(action)) continue Filters.append( [m.group(1), string.lower(m.group(2)), m.group(3)] ) def readDescriptor(desc): result = '' while 1: data = os.read(desc, 16384) if data == '': break result = result + data return result def writeDescriptor(desc, data): while data: num = os.write(desc, data) data = data[num:] os.close(desc) def filterHits(): for regEx in Filters: reg = re.compile(regEx[2], re.M|re.I) match = reg.search(MailData) if match: log("Matched [%s]" % (regEx[0])) return regEx[1], regEx[0] return None,None def storeInTrap(hit): try: pid = os.getpid() mailFile = os.open('/var/qmail/qmfilt/qmfilt.%s' %(pid), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744) except: log("Can't create /var/qmail/qmfilt/qmfilt.%s" %(pid)) # Indicate a write error sys.exit(53) try: #如果不屏蔽会出现很多问题,至于为什么我还没弄明白,如果你找到问题所在,请告诉我。 """(None, inode, None, None, None, None, size, None, None, None) = os.fstat(mailFile)""" os.rename('/var/qmail/qmfilt/qmfilt.%s' %(pid), '/var/qmail/qmfilt/%s.mail' %(inode)) except: log("Can't rename /var/qmail/qmfilt/qmfilt.%s -> /var/qmail/qmfilt/%s.mail" %(pid, inode)) # Indicate a write error sys.exit(53) try: envFile = os.open('/var/qmail/qmfilt/%s.envelope' %(inode), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744) mesgFile = os.open('/var/qmail/qmfilt/%s.qmfilt' %(inode), os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0744) writeDescriptor(mailFile, MailData) writeDescriptor(envFile, MailEnvelope) writeDescriptor(mesgFile, "Matched filter [ %s] to file %s" %(hit, inode)) log("Matched filter [ %s] to file %s" %(hit, inode)) except: log("Can't create/write files into trap") # Indicate a write error sys.exit(53) return 0 def sendToQmailQueue(): # Open a pipe to qmail queue fd0 = os.pipe() fd1 = os.pipe() pid = os.fork() if pid < 0: log("Error couldn't fork") sys.exit(81) if pid == 0: # This is the child os.dup2(fd0[0], 0) os.dup2(fd1[0], 1) i = 2 while (i < 64): try: os.close(i) except: pass i = i + 1 os.chdir('/var/qmail') #time.sleep(10) os.execv("bin/qmail-queue", ('bin/qmail-queue',)) log("Something went wrong") sys.exit(127) # This is only reached on error else: # This is the parent # close the readable descriptors os.close(fd0[0]) os.close(fd1[0]) # Send the data recvHdr = "Received: (QMFILT: %s); " %(Version) recvHdr = recvHdr + time.strftime("%d %b %Y %H:%M:%S", time.gmtime(time.time())) recvHdr = recvHdr + " -0000\n" os.write(fd0[1], recvHdr) writeDescriptor(fd0[1], MailData) writeDescriptor(fd1[1], MailEnvelope) # Catch the exit code to return result = os.waitpid(pid, 0)[1] if PyVersion > '1.5.1': if os.WIFEXITED(result): return os.WEXITSTATUS(result) else: log("Didn't exit normally") sys.exit(81) else: return result def conver(msg): msg = msg.replace(chr(00),' ') msg = msg.replace('F','From:',1) msg = msg.replace(' T',' To:') return msg def main(argv, stdout, environ): global TestMode global MailData global MailEnvelope global PyVersion PyVersion = string.split(sys.version)[0] if len(argv) > 1 and argv[1] == '--test': TestMode = 1 os.setuid(QmailD) # Insure Environment is OK # Try to open log openlog() if not os.path.exists('/var/qmail/qmfilt'): # Indicate a problem with the queue directory log("Directory /var/qmail/qmfilt doesn't exist") if not TestMode: sys.exit(61) if os.path.exists('/var/qmail/control/qmfilt'): readFilters() else: if TestMode: print "No filter file /var/qmail/control/qmfilt - no filters applied" if TestMode: showFilters() # Go no further if in test mode if TestMode: sys.exit(0) # Get the data # Read the data MailData = readDescriptor(0) # Read the envelope MailEnvelope = readDescriptor(1) if Debug: log(MailData) log(MailEnvelope) action,hit = filterHits() if action == 'trap': storeInTrap(hit) if action == 'block': log("Matched filter [ %s] and email was BLOCKED/Refused delivery" %(hit)) sys.exit(31) if action == 'drop': log("Matched filter [ %s] and email was DROPPED" %(hit)) if action == None: sendToQmailQueue() #Log log(conver(MailEnvelope)) #send sms mail_sms(conver(MailEnvelope)) if Debug: log("qmailqueue returned [%d]" %(result)) sys.exit(0) if __name__ == "__main__": try: main(sys.argv, sys.stdout, os.environ) # Catch the sys.exit() errors except SystemExit, val: sys.exit(val) except: # return a fatal error for the unknown error if TestMode: traceback.print_exc() sys.exit(81) }}} 然后在cd /var/qmail/control 编辑qmfilt内容如下: {{{ # # This is the qmfilt control file # If any email comes in that matches this # filter, the mail will be redirected # to the filter directory # # A filter regular expression must be on a single # line and in between a pair of '::' # # Valid actions: # trap - store in the trap directory # block - tell the smtp sender that we won't take the mail # drop - accept the mail, but don't queue it # # This will match any executable Visual Basic Scripts VisualBasic::block::^Content-Type: application/octet-stream;\s+name="(.*\.vbs)":: SHS::block::^Content-Type: application/octet-stream;\s+name="(.*\.shs)":: SHB::block::^Content-Type: application/octet-stream;\s+name="(.*\.shb)":: COM::block::^Content-Type: application/octet-stream;\s+name="(.*\.com)":: EXE::block::^Content-Type: application/octet-stream;\s+name="(.*\.exe)":: SCR::block::^Content-Type: application/octet-stream;\s+name="(.*\.scr)":: PIF::block::^Content-Type: application/octet-stream;\s+name="(.*\.pif)":: BAT::block::^Content-Type: application/octet-stream;\s+name="(.*\.bat)":: # This is the Outlook date overflow bug DATE::block::^Date:.{160,}:: }}} 在/var/log下建立qmfilt文件,内容为空 {{{ chown qmaild qmfilt }}} 在/var/qmail下建立qmfilt目录。 {{{ chown -R qmaild qmfilt }}} 好了,来让我们的程序启用吧 先测试一把 {{{ /var/qmail/bin/qmfilt.py --test }}} 如果返回了先定义的qmfilt里面的内容,恭喜成功了一半了。内容应该和下面的相似: {{{ Filter - VisualBasic - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.vbs)" Filter - SHS - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.shs)" Filter - SHB - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.shb)" Filter - COM - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.com)" Filter - EXE - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.exe)" Filter - SCR - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.scr)" Filter - PIF - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.pif)" Filter - BAT - Action: block Regex: ^Content-Type: application/octet-stream;\s+name="(.*\.bat)" Filter - DATE - Action: block Regex: ^Date:.{160,} }}} 好了,让我们的过滤器用起来吧 {{{ cd /usr/local/vpopmail/etc }}} 编译tcp.smtp内容如下 {{{ 127.:allow,RELAYCLIENT="",QMAILQUEUE="bin/qmfilt.py" :allow,QMAILQUEUE="bin/qmfilt.py" }}} 然后生成tcp.smtp.cdb文件 {{{ tcprules tcp.smtp.cdb tcp.smtp.tmp < tcp.smtp }}} 发个带exe为附件的邮件看看。如果成功过滤,应该在/var/log/qmfilt里面看到如下信息: {{{ Fri Sep 10 14:37:01 2004 - Matched filter [ EXE] and email was BLOCKED/Refused delivery Fri Sep 10 14:38:09 2004 - Matched [SCR] }}} 如果你是用foxmail等客户端发送带exe为结尾的邮件的话,服务器会提示你拒绝接收的。到这里,你的邮件过滤已经成功了。 你可以在/var/qmail/control的qmfilt文件里面定义你需要过滤的指定代码,然后测试规则是否生效了。 关于短信到达通知,因为安全和商业上的问题。不能将服务器端的代码贴出来。我将机制给大家讲一下。 qmfilt.py这个程序里面mail_sms用udp将类似{{{ From:info@xxx.com.cn To:weilx@xxx.com }}}发送到指定的服务器上。服务器接收到这个数据包后,将这个数据包的内容用短信发送出去。 当然可以将邮件主题等信息一起发送到手机上,大家根据自己的情况更改吧。 这个程序是根据[[ http://sourceforge.net/projects/qmfilt/ ]]这个项目更改的,但是直接用他的代码会造成不能收信,大家可以在这个项目的cvs关注他的代码修改情况。 结束,谢谢! == FreeBSD 5.3的使用技巧 == FreeBSD5.3直接安装后不像4x样默认启动了sshd服务,你需要在安装的时候选择使用sshd服务。或者在/etc/rc.conf中加入sshd_enable="YES" 如果你使用SecureCRT用ssh2连接时提示unable to authenticate using any of the configured authentication methods !错误,请在连接属性中的Authentication的Primary选项中,选择Keyboard Interactive。 == FreeBSD 5.3下安装Jail == 感谢:黄冬 按照黄冬的文章在FB5.3下安装Jail未果,他提醒我FB5.3的Jail已经发生了变更,在他的帮助下成功在FB5.3下安装了Jail,整理成资料大家分享一下。 首先安装系统时,最好划分一个单独的分区来存放你的vhost,如果不想变更你的分区设置。也可以将vhost安装在你的/usr或者/home中。如果你有了cvsup,请到/usr/share/examples/cvsup中修改你的stable-supfile文件,设置*default host=cvsup.FreeBSD.org。然后将他丢到后台开始下载最新的源码吧。cvsup -g -L 2 stable-supfile & 由于FB5.3已经不能直接make world ,更新完代码后,到/usr/src下按步骤编译。如果你初装系统,建议你将整个系统都编译和优化一下。如果只想安装jail,就没有必要完整编译整个系统了。 在/usr/src下make buildworld&在后台编译,这个过程可能需要2小时或者4小时。你可以做点别的事情去。结束后,我们来安装我们的jail吧。我做了个新建jail的脚本new_jail.sh,贴到这里 {{{ #!/bin/sh if [ -z $1 ]; then echo "specify dest dir such as $0 /some/dir" exit fi D=$1 echo $D mkdir -p $D cd /usr/src make installworld DESTDIR=$D cd etc make distribution DESTDIR=$D cd $D ln -sf dev/null kernel }}} 先给它加上执行权限chmod +x new_jail.sh 然后 mkdir -p /vhost/jail/179 ./new_jail.sh /vhost/jail/179 来创建你的新vhost,这里/vhost/jail/179替换为你要安装的虚机路径。最好是绝对地址,防止出错。 当一切结束后,用单用户来启动我们的vhost吧 ifconfig_eth0_alias0="inet 10.0.0.179 netmask 255.255.255.255" 替换eh0为你的实际网卡设备名,如果你不知道可以用ifconfig命令查看 jail /vhost/jail/179 dns 10.0.0.79 /bin/sh 现在设置你的root密码吧passwd 输入新密码 vhost没有tty供你操作,你需要运行sysinstall,在用户管理中建立一个属于wheel组的用户,用来ssh登陆上去。然后设置一下时区是非常有必要的。 然后我们还需要为ssh准备一个key /etc/rc.d/sshd start 然后回车,或者输入点什么东西再回车。 我们还需要将这个vhost中设置它的文件 /etc/hosts设置主机名 /etc/rc.conf 中加入 sshd_enable="YES" sendmail_enable="NONE" /etc/crontab中删除和adjkerntz 相关的内容。 /etc/resolv.conf 中设置你的dns服务器地址 格式为:nameserver IP地址 好了,输入exit退出单用户模式。回到主系统后修改主系统的rc.conf,加上以下信息 {{{ ifconfig_eth0_alias0="inet 10.0.0.179 netmask 255.255.255.255" jail_enable="YES" jail_list="dns" jail_dns_hostname="dns.test.com" jail_dns_ip="10.0.0.179" jail_dns_rootdir="/vhost/jail/179" jail_dns_exec="/bin/sh /etc/rc" jail_dns_devfs_enable="YES" jail_dns_devfs_ruleset="devfsrules_jail" }}} 重新启动你的机器吧,当然如果你想继续你的uptime时间,你可以先输入init 1后,在提示行下输入exit来重新回到多用户模式。 dmesg -a | more来看看你的启动信息,如果你的jail和下面的信息相似,恭喜你。可以用ssh连接10.0.0.179来登陆你的jail了。 Starting jails: dns.test.com . Local package initialization: 还有点小技巧 1、 /etc/rc.d/jail这个命令可以用来开始,结束,重新启动你的jail,输入这个命令看看帮助吧 jls这个命令可以看现在正在运行的 jail的列表。试一下? vhost1# jls JID IP Address Hostname Path 3 10.0.0.179 dns.test.com /vhost/jail/179 2、 删除jail /etc/rc.d/jail stop dns chflags -R noschg 179 rm -R 179就可以删除了 3、 在jail中使用ports 先在jail中建立ports目录,比如mkdir /usr/ports 在再主系统中执行mount_nullfs /usr/ports /vhost/jail/179/usr/ports 4、 如果需要同时运行多个jail,你的rc.conf应该这样配置 {{{ ifconfig_eth0_alias0="inet 10.0.0.179 netmask 255.255.255.255" ifconfig_eth0_alias0="inet 10.0.0.180 netmask 255.255.255.255" jail_enable="YES" jail_list="dns mail" jail_dns_hostname="dns.test.com" jail_dns_ip="10.0.0.179" jail_dns_rootdir="/vhost/jail/179" jail_dns_exec="/bin/sh /etc/rc" jail_dns_devfs_enable="YES" jail_dns_devfs_ruleset="devfsrules_jail" jail_mail_hostname="mail.test.com" jail_mail_ip="10.0.0.180" jail_mail_rootdir="/vhost/jail/180" jail_mail_exec="/bin/sh /etc/rc" jail_mail_devfs_enable="YES" jail_mail_devfs_ruleset="devfsrules_jail" }}} 启动或者停止其中一个jail可以/etc/rc.d/jail start mail或者/etc/rc.d/jail stop dns来操作。 = python = == python调用com以及com事件 == python调用有事件发生的com时,需要一些技巧。 我们以ie这个com为例来讲解一下我的经验。 首先我们需要pywin32这个模块的支持,它提供了我们调用com便利直接方法。你可以www.sf.net搜索并下载它。 先运行如下代码: {{{ #!python import win32gui import win32com import win32com.client import pythoncom import time class EventHandler: def OnVisible(self, visible): global bVisibleEventFired bVisibleEventFired = 1 def OnDownloadBegin(self): print "DownloadBegin" def OnDownloadComplete(self): print "DownloadComplete" def OnDocumentComplete(self, pDisp = pythoncom.Missing , URL = pythoncom.Missing): print "documentComplete of %s" % URL #这里用EventHandler类来处理ie中发生的事件,这里的函数名必须和事件名称一致。 ie = win32com.client.DispatchWithEvents("InternetExplorer.Application", EventHandler) ie.Visible = 1 ie.Navigate("www.aawns.com") #这里是等待事件的发生 pythoncom.PumpMessages() ie.Quit() }}} 我们看到,程序运行正常,能打开我们指定的站点,并各事件被触发后都能作出正确的反映。 但是假如我们希望在事件发生后,能调用我们继承下来的一些方法和属性。你会发现无法使用。 如下代码将展示这个例子 {{{ #!python # -*- coding: cp936 -*- import win32gui import win32com import win32com.client import pythoncom import time class test: def runtest(self): print 'tuntest' class EventHandler: def OnVisible(self, visible): global bVisibleEventFired bVisibleEventFired = 1 def OnDownloadBegin(self): print "DownloadBegin" def OnDownloadComplete(self): print "DownloadComplete" def OnDocumentComplete(self, pDisp = pythoncom.Missing , URL = pythoncom.Missing): print "documentComplete of %s" % URL #在这里我们再调用test的runtest方法,看是否继承成功。 self.runtest() class runcom(test): def __init__(self): ie = win32com.client.DispatchWithEvents("InternetExplorer.Application", EventHandler) ie.Visible = 1 ie.Navigate("www.aawns.com") #这里调用test的runtest方法,看是否继承成功。 self.runtest() pythoncom.PumpMessages() ie.Quit() a=runcom() }}} 运行结果是错误的。 {{{ tuntest DownloadBegin DownloadComplete DownloadBegin DownloadComplete documentComplete of http://www.aawns.com/ pythoncom error: Python error invoking COM method. Traceback (most recent call last): File "C:\Python23\Lib\site-packages\win32com\server\policy.py", line 283, in _ Invoke_ return self._invoke_(dispid, lcid, wFlags, args) File "C:\Python23\Lib\site-packages\win32com\server\policy.py", line 288, in _ invoke_ return S_OK, -1, self._invokeex_(dispid, lcid, wFlags, args, None, None) File "C:\Python23\Lib\site-packages\win32com\server\policy.py", line 550, in _ invokeex_ return func(*args) File "D:\python\test.py", line 24, in OnDocumentComplete self.runtest() File "C:\Python23\Lib\site-packages\win32com\client\__init__.py", line 454, in __getattr__ raise AttributeError, "'%s' object has no attribute '%s'" % (repr(self), att r) exceptions.AttributeError: '' object has no attribute 'runtest' }}} 我们看到test类的runtest方法并没有被继承进去,为什么呢?我的理解是因为com的事件模式让你无法继承python中的self,因为在调用ie的时候并不是用EventHandler的实例而是将这个类作为了事件处理的方法(不知道这里理解是否正确,如果有更好的理解。我们交流) 经过查找了很多资料和试探了很多方法,只有采用全局变量的方式才能在事件和各个类之间传递数据。代码变更成了这样 {{{ #!python # -*- coding: cp936 -*- import win32gui import win32com import win32com.client import pythoncom import time class EventHandler: def OnVisible(self, visible): global bVisibleEventFired bVisibleEventFired = 1 def OnDownloadBegin(self): print "DownloadBegin" #先继承全局变量增加一个字符串 global testlist testlist.append("DownloadBegin") def OnDownloadComplete(self): print "DownloadComplete" #先继承全局变量增加一个字符串 global testlist testlist.append("DownloadComplete") def OnDocumentComplete(self, pDisp = pythoncom.Missing , URL = pythoncom.Missing): print "documentComplete of %s" % URL #先继承全局变量再打印 global testlist print testlist class runcom: def __init__(self): global testlist ie = win32com.client.DispatchWithEvents("InternetExplorer.Application", EventHandler) ie.Visible = 1 ie.Navigate("www.aawns.com") #打印全局变量 print testlist pythoncom.PumpMessages() ie.Quit() testlist=[] a=runcom() }}} 可以看到,用全局变量的方式解决了于事件内传输数据的问题。 没有解决的问题:使用Twisted的时候也有相应的事件,如何保证Twisted 和com中的事件都被触发? == 使用python写nt服务 == 如果我们想让系统启动的时候就执行某个程序,windows系统和unix系统是不一样的,对于unix只需要将要执行的命令放到rc.local中,系统重新启动的时候就可以加载了。windows就麻烦多了,如果你将程序放到启动组中,只有输入了密码后,程序才被执行,如果想在系统一启动的时候就执行程序,必须使用nt服务。 python下如何使用nt服务,其实很简单。 下载python的win32支持。我使用的是:pywin32-202.win32-py2.3.exe安装好后就可以来写我们的服务了。 我们先来建立一个空的服务,建立test1.py这个文件,并写入如下代码: {{{ #!python # -*- coding: cp936 -*- import win32serviceutil import win32service import win32event class test1(win32serviceutil.ServiceFramework): _svc_name_ = "test_python" _svc_display_name_ = "test_python" def __init__(self, args): win32serviceutil.ServiceFramework.__init__(self, args) self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) def SvcStop(self): # 先告诉SCM停止这个过程 self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) # 设置事件 win32event.SetEvent(self.hWaitStop) def SvcDoRun(self): # 等待服务被停止 win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE) if __name__=='__main__': win32serviceutil.HandleCommandLine(test1) }}} 这里注意,如果你需要更改文件名,比如将win32serviceutil.HandleCommandLine(test1)中的test1更改为你的文件名,同时class也需要和你的文件名一致,否则会出现服务不能启动的问题。 在命令窗口执行,test1.py可以看到帮助提示 {{{ C:\>test1.py Usage: 'test1.py [options] install|update|remove|start [...]|stop|restart [...]| debug [...]' Options for 'install' and 'update' commands only: --username domain\username : The Username the service is to run under --password password : The password for the username --startup [manual|auto|disabled] : How the service starts, default = manual --interactive : Allow the service to interact with the desktop. C:\> }}} 安装我们的服务 {{{ C:\>test1.py install Installing service test_python to Python class C:\test1.test1 Service installed C:\> }}} 我们就可以用命令或者在控制面板-》管理工具-》服务中管理我们的服务了。在服务里面可以看到test_python这个服务,虽然这个服务什么都不做,但能启动和停止他。 下面我们来写个复杂点的服务。 改天写,要下班了。 == 使用python来发扑克牌 == 在QQ群里面出了个题目,发52张扑克牌到4个人,要随机,速度足够快。我写了一个,请大家指正。 {{{ #!python #!/usr/local/bin/python -- # -*- coding: cp936 -*- import random,time class timer: def __init__(self): self.start= time.time() def stop(self): self.end= time.time() return "\n 本次运行已用时 %s秒"% (self.end-self.start) class people: #定义四个玩牌的人 jack = [] python = [] java = [] stephen = [] def print_jack(self): i=0 tmp = '' while i < 13: tmp += self.jack[i]+' ' i += 1 return tmp def print_python(self): i=0 tmp = '' while i < 13: tmp += self.python[i]+' ' i += 1 return tmp def print_java(self): i=0 tmp = '' while i < 13: tmp += self.java[i]+' ' i += 1 return tmp def print_stephen(self): i=0 tmp = '' while i < 13: tmp += self.stephen[i]+' ' i += 1 return tmp class agent(people): def __init__(self): #定义一个空的列表存放牌 self.card = [] #定义洗牌的次数,并不是越大牌就越乱 self.count = 52 #定义一个基本的牌序 self.base = ['A','2','3','4','5','6','7','8','9','10','J','Q','K'] def arrangement(self): #重新排列纸牌 i = 0 while i < 13: self.card.insert(0,'黑桃'+self.base[i]) i += 1 i = 0 while i < 13: self.card.insert(0,'红桃'+self.base[i]) i += 1 i = 0 while i < 13: self.card.insert(0,'草花'+self.base[i]) i += 1 i = 0 while i < 13: self.card.insert(0,'方块'+self.base[i]) i += 1 def shuffle(self): #洗牌 i = 1 while i < self.count: #产生一个随机数来确定要交换牌的位置 position = random.randint(0,51) #临时取出一张牌准备用来交换位置 tmp = self.card[position] #交换随机位置的牌和第一张牌 self.card[position] = self.card[0] self.card[0] = tmp i += 1 def outcards(self): #发牌 i = 0 while i < 52: if i <= 12: self.jack.insert(0,self.card[i]) elif i >= 12 and i <= 25 : self.python.insert(0,self.card[i]) elif i >= 25 and i <= 38 : self.java.insert(0,self.card[i]) elif i >= 38 and i <= 52 : self.stephen.insert(0,self.card[i]) i += 1 start=timer() a=agent() a.arrangement() a.shuffle() a.outcards() print a.print_jack() print a.print_python() print a.print_java() print a.print_stephen() print start.stop() }}} 另外,0.00999999046326好象是最小计数了,再小就计算成0秒了。 == 如何打印对象的方法和属性 == 有时候我们需要让对象可以打印来方便调试,可以重载对象的__str__方法来实现。 {{{ class header: """dns消息头抽象类""" def __init__(self, qid = 1): #初始化消息头 self.qid = qid #DNS 查询封包编号,作为确认依据。长度为16 byte self.qr = 0 #查询封包为 0 ﹔回应为 1 。长度为 1 byte self.opcode = 0 #封包类别(QUERY, IQUERY, STATUS, Reserved)。长度为 4 bytes。 self.aa = 0 #Flags共 4 bytes ,各表示:AA(Authoritative Answer)、TC(Truncation)、RD(Recursion Desired)、RA(Recursion Avalable)。 self.tc = 0 self.rd = 1 self.ra = 0 self.reserved = 0 #保留未用。 self.rcode = 0 #回应讯息,长 4 bytes ,除 0 及 6-15 保留未用外,1-5 分别为:Format Error、Server Failure、Name self.qsection = 1 #问题部分,只支持1。 self.ansection = 0 #答案部分。 self.ausection = 0 #权力部分。 self.arsection = 0 #另外的部分。 def __str__(self): """字符串化""" if len(self.__dict__) > 0: plist = [] for field in self.__dict__: plist.append(str(field) + ":" + str(self.__dict__[field])) return reduce(lambda x,y: x + "\n" + y, plist) else: return "" test=header() print test }}} == 使用Twisted实现一个简单Web服务器 == 作者:梅劲松 版权:本文档为MIT授权 运行环境:Python 2.3+Twisted的py-23安装版本 自己实现Web服务器的优点就不用说太多了,主要是能控制具体的实现。也能按照自己的习惯实现互动方式。 而Twisted在tcp以下是C写的,ip和udp部分应该是C和Python的混合产物,而http smtp等则是Python的,自己能很好的扩充。 下面来看个具体的例子: 首先你需要编辑一个html为结尾的文件名放到你的htm目录下。 然后在htm的上一级目录建立一个文件,文件名为web.py,内容如下: 代码: {{{ PORT = 80#这个是80,如果你的端口被占用了,换成其他的 from twisted.web.resource import Resource from twisted.web import server from twisted.web import static from twisted.internet import reactor class ReStructured( Resource ): def __init__( self, filename, *a ): self.rst = open( filename ).read( ) def render( self, request ): return self.rst resource = static.File('./htm/') resource.processors = { '.html' : ReStructured } resource.indexNames = [ 'index.html'] reactor.listenTCP( PORT, server.Site( resource ) ) reactor.run( ) }}} 在控制台下进入目录输入 python web.py,然后打开浏览器,输入http://127.0.0.1,看到你的站点了吗? = sip = == 用sip服务器实现msn企业内部交流 == winxp里面的windows messager可以登陆企业自己的sip服务器,实现企业内部交流。 你可以在这里下载berkeke OnDO SIP Server 1.2 http://www.brekeke-sip.com/download/oss/dl/oss1_2_7_4.exe 如果不能下载,请自己想办法。这个软件确实可下,只不过被国际出口上锁住了。 配置berkeke server的方法在QQ群6390838里的朋友苏醒的帮助下,终于成功了。 在服务里启动server后,进入http://localhost:18080/oss/ 页面,用sa,sa登录后,选择Authentication栏目,在右边的edit框里填写用户的名称 User Password (Confirm) Name Email Address Description 接着按"add"按钮,有Update succeeded. 字样。然后在windows messenger里的服务器ip写你的服务器ip,比如192.168.3.135,可以不加端口号5060。还要把连接方式改为udp(你连接不上,可能是你没改协议的原因)。可以不写用户名。然后登录,在登录的时候写你的User名。 这段时间准备用python写一个sip服务器提供给windows messager用,有兴趣的朋友可以和我联系。 == 反馈 == * 看起来很不错,不过有没有对于sip服务更详细的东西?还有你的nt服务的下一章还没开始啊。 -- Dreamingk