Size: 26648
Comment:
|
Size: 26802
Comment: 反馈
|
Deletions are marked like this. | Additions are marked like this. |
Line 824: | Line 824: |
== 反馈 == * 看起来很不错,不过有没有对于sip服务更详细的东西?还有你的nt服务的下一章还没开始啊。 -- Dreamingk |
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的脚本改为:
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,内容如下:
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:[email protected] To:[email protected] 发送到指定的服务器上。服务器接收到这个数据包后,将这个数据包的内容用短信发送出去。
当然可以将邮件主题等信息一起发送到手机上,大家根据自己的情况更改吧。
这个程序是根据[" http://sourceforge.net/projects/qmfilt/ "]这个项目更改的,但是直接用他的代码会造成不能收信,大家可以在这个项目的cvs关注他的代码修改情况。
结束,谢谢!
python
python调用com以及com事件
python调用有事件发生的com时,需要一些技巧。
我们以ie这个com为例来讲解一下我的经验。
首先我们需要pywin32这个模块的支持,它提供了我们调用com便利直接方法。你可以www.sf.net搜索并下载它。
先运行如下代码:
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()
我们看到,程序运行正常,能打开我们指定的站点,并各事件被触发后都能作出正确的反映。
但是假如我们希望在事件发生后,能调用我们继承下来的一些方法和属性。你会发现无法使用。
如下代码将展示这个例子
# -*- 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: '<win32com.client.COMEventClass instance at 0x1554295 2>' object has no attribute 'runtest'
我们看到test类的runtest方法并没有被继承进去,为什么呢?我的理解是因为com的事件模式让你无法继承python中的self,因为在调用ie的时候并不是用EventHandler的实例而是将这个类作为了事件处理的方法(不知道这里理解是否正确,如果有更好的理解。我们交流)
经过查找了很多资料和试探了很多方法,只有采用全局变量的方式才能在事件和各个类之间传递数据。代码变更成了这样
# -*- 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这个文件,并写入如下代码:
# -*- 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个人,要随机,速度足够快。我写了一个,请大家指正。
# -*- 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秒了。
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