20121227

用邮件分割和传送大文件,python实现 II

用邮件分割和传送大文件,python实现 II

3. 软件的使用过程

下面这段,是软件写完以后运行的效果,不过,在软件开始写以前,它的样子就已经在我的心中。用个去年还是前年流行然后就消声匿迹的词来形容,软件在写第一行代码以前,就应该有个"愿景"。


以下,以发送191字节的 test.in
为例,分成3个包,每包100字节。从young@nenu.edu.cn发出,发给young@nenu.edu.cn。每包100字节是参数的默认值指定的。

3.1. 发送方zhumao

~/running/zhumao-baoyu-mail $ python zhumao.py -f young@nenu.edu.cn -t
young@nenu.edu.cn -L "[test-in]" -p "mypass***" -F test.in

To:young@nenu.edu.cn From: young@nenu.edu.cn Subject:[test-in] 1/3

sent.

To:young@nenu.edu.cn From: young@nenu.edu.cn Subject:[test-in] 2/3

sent.

To:young@nenu.edu.cn From: young@nenu.edu.cn Subject:[test-in] 3/3

sent.

3.2 接收方baoyu

~/running/zhumao-baoyu-mail $ python baoyu.py -u young@nenu.edu.cn -p
"mypass***" -L "[test-in]" -f young@nenu.edu.cn -F test.out
+------------------------- |1 [test-in] 3/3 +-------------------------
3/3 +------------------------- |2 [test-in] 2/3
+------------------------- 2/3 +------------------------- |3 [test-in]
1/3 +------------------------- 1/3

以上接收过程可以看到,一共3包,第3包的接收早于第2包,早于第1包。

用diff对比发送和接收到的文件,一致。

~/running/zhumao-baoyu-mail $ diff test.in test.out
~/running/zhumao-baoyu-mail $


3.3. 手册,帮助

$ python zhumao.py --help Usage: %zhumao.py [--help]

Options: --version show program's version number and exit -h, --help
show this help message and exit -F FILE, --file=FILE file need to be
sent -S SMTP_SERVER, --smtp=SMTP_SERVER smtp server -f FROM,
--from=FROM mail sender -p PASSWORD, --password=PASSWORD password -t
TO, --to=TO mail receiver -s SIZE, --size=SIZE size of each mail -L
subject_prefix, --subject=subject_prefix the prefix of mail subject

$ python baoyu.py --help Usage: %baoyu.py [--help]

Options: --version show program's version number and exit -h, --help
show this help message and exit -F FILE, --file=FILE file need to be
saved -P POP3_SERVER, --pop=POP3_SERVER pop3 server -u USER,
--user=USER user name -p PASSWORD, --password=PASSWORD password -f
FROM, --from=FROM from whom to be filtered -L subject_prefix,
--subject=subject_prefix the prefix of mail subject as filter

4. 发送方zhumao

以下是发送方zhumao.py的代码及讨论。希望能为像我一样的初学者带来启发的同时,渴望熟悉python什么的大牛们指导和批评我代码的各种错误,一方面我求进步,另一方面也免得我误导别人。谢谢啦。

这两天又访问不了github了,所以暂时没传上去。各位将就着看吧。

不连续的行号,是空行,我解释的时候略过了。

4.1 基本相当于导言

1 #! /usr/bin/python 2 # -*- coding: utf-8 -*- 3 4 # zhumao,邮件发送者

第1行,注释,指定脚本解释器。这和shell程序设计是一个路子。

第2行,注释,文件编码。祖国尚未统一世界,看来各种编码的乱像还会继续相当长时间。

5 __usage__ = "usage: %zhumao.py [--help]" 6 __version__ = "zhumao by
Young 2012-12-21"

我照抄的,看效果是显示使用方法。这个实现有意思,方便程序员与用户沟通啊。增加一点方便,估计就多几个百分比的程序员实现这一功能。

8 from optparse import OptionParser 9 import smtplib 10 import base64
11 import time 12 import sys

我猜就是 C里的include,猜的。还有名字空间、模块这样的作用。from的作用,似乎是使得模块中的符号名在当前作用域可见。这些模块是后面的代码要用到的。

4.2 命令行解析

这里使用了

8 from optparse import OptionParser

中指定的模块,第15行中的p是个OptionParser实例。在第16行以后,调用实例的方法
p.add_option。这些参数,基本可以根据出现的顺序、内容,与上述"愿景"对比,猜出来含义。不赘述。

14 # 命令行解析15 p = OptionParser(usage=__usage__, version=__version__,
description=__doc__) 16 p.add_option("-F", "--file", dest="filename",
17 help="file need to be sent", metavar="FILE", default="test.in") 18
p.add_option("-S", "--smtp", dest="smtp", 19 help="smtp server",
metavar="SMTP_SERVER", default="smtp.nenu.edu.cn") 20 # help="smtp
server", metavar="SMTP_SERVER", default="smtp.gmail.com") 21
p.add_option("-f", "--from", dest="fromaddr", 22 help="mail sender",
metavar="FROM") 23 p.add_option("-p", "--password", dest="password",
24 help="password", metavar="PASSWORD") 25 p.add_option("-t", "--to",
dest="to", 26 help="mail receiver", metavar="TO") 27
p.add_option("-s", "--size", dest="size", 28 help="size of each mail",
metavar="SIZE", default=100) 29 p.add_option("-L", "--subject",
dest="subject_prefix", 30 help="the prefix of mail subject",
metavar="subject_prefix", default='[zhumao_baoyu_mail]') 31 32 (opt,
args) = p.parse_args()

到这里为止,命令行参数 (及其默认值)就已经成为p的成员了。后面这样引用,比如: opt.smtp。

第32行,这个返回值对C程序员来说,看起来有点奇特。手册[http://docs.python.org/2/library/optparse.html]说

"parse_args() returns two values:

options, an object containing values for all of your options—e.g. if
--file takes a single string argument, then options.file will be the
filename supplied by the user, or None if the user did not supply that
option args, the list of positional arguments leftover after parsing
options"

4.3 输入文件

34 # 输入文件35 file = open (opt.filename, "r") 36 s = file.read()

第35行,opt.filename是从命令行中解析出来的,在我们的"愿景"中,就是test.in。open,
read,这样的写法,容易望文生义,而且似乎也对,不赘述。执行完这段以后,文件的内容全在s中了。

4.4 编码文件

38 # 编码文件39 s = base64.standard_b64encode(s) 40 41 ## sys.stdout.write(s)

第39行,使用了 base64编码 字符串s,编码的结果再赋值给s。第41行是我写程序的时候用来输出测试的。

4.5 准备发送,登录

43 # 准备发送,登录44 sleep = 1 45 to = opt.to 46 user = opt.fromaddr 47 pwd
= opt.password 48 smtp = opt.smtp

第44行,准备每发送一包睡一秒。根据你用的SMTP服务器对你的坏人判断有多严格,你可以调整sleep的值。

从第45行至第48行,从命令行解析的结果中得到 发给谁、发送者、SMTP发送者的口令、SMTP服务器地址。

49 smtpserver = smtplib.SMTP(smtp) 50 # smtpserver =
smtplib.SMTP(smtp, 587) # for gmail 51 smtpserver.ehlo() 52
smtpserver.starttls() 53 smtpserver.ehlo 54 smtpserver.login(user,
pwd)

第49行使用smtplib,构造出smtpserver实例。如果用的是gmail,端口需要指定为587。

从第51行到第54行,根据你选的smtp服务器的要求决定对它说些啥。我的smtp服务器要求先
扩展的hello,然后tls架密,然后再问候一次,然后登录。如果不知道应该说些啥,可以用thunderbird登录一次,用sniffer看一下正确的步骤。

4.6 拆分-标记-发送

麻烦的地方到了。

4.6.1 拆成

56 # 折分文件57 end = len(s) 58 for i in xrange(0, end, opt.size): 59 if
i+opt.size < end : 60 current = s[i:i+opt.size] 61 else: 62 current =
s[i:]

python的for循环语法,我老是忘。希望这回能记住。

"for 变量 in 待遍历的东西 : "。

这里,待遍历的东西是一个数据范围,用xrange生成,从0开始,到end结束,步进为 opt.size。

在循环体中--这也是python有意思的一个地方,缩进是语法要求,而不仅是为了美观--如果没有到最后一包,当前的这包就是字符串切割出来的。s[i:i+opt.size]表示切割s,从
i 切到 i+opt.size;如果不是最后一包,从i切到结尾,写作 s[i:]。

python切割字符串的方法,还真是有C的风格,把字符串当成纯粹的容器/数组看待。

4.6.2 标记

还在循环里。

63 # 标记64 subject = opt.subject_prefix + ' '
+str(i/opt.size+1)+'/'+str(end/opt.size+1) 65 header = 'To:' + to +
'\n' + 'From: ' + user + '\n' + 'Subject:' + subject +' \n' 66 current
= 'BODY_START:' + current + '\n' + 'BODY_END:NOTHINGGOESHERE' 67 print
68 print header

第64行,生成主题subject,第65行,生成邮件头header,第66行,生成正文。第67和第68行,不是为了调试,而是为了避免用户在长时间切割的时候不知道跑了多远,还剩多远。

这里输出的东西就是下面这段:

To:young@nenu.edu.cn From: young@nenu.edu.cn Subject:[test-in] 2/3

sent.

其中,主题里的 [test-in]
是加的标记,包师弟接收的时候可以用这个标记过滤出我们特别的邮件。太阳底下无新鲜事,大毅同学提到在cisco设备中,vlan/trunk也使用了类似的方法做标记tag。后面的2/3表示共3包,这是第2包,用来在baoyu接收端拼接文件的时候作为顺序的依据,也用来显示出来,避免包师弟了解下载了多少,还有多少没下载。

4.6.3 发送

还在循环里。

69 #发送70 msg = header + current 71 smtpserver.sendmail(user, to, msg)
72 print 'sent.' 73 time.sleep(sleep) 74 75 smtpserver.close()

第70行,把邮件头和邮件正文拼在一起。这看起来是很天真的做法,邮件头和正文就这么就拼一起了啊?是的,是SMTP还是什么协议,就是这么规定的。好像head和body中间还有个回车。

第71行,发送出去了。

第72行,避免用户心急,程序把邮件发出去了要告诉用户一声。

第73行,前面提到了,睡一下,避免SMTP服务器认为我们是发送垃圾邮件的坏人。

第75行,离开循环,邮件都发送完了,断开连接。做事要有始有终,保持优雅。

明天,或者后天,讨论一下接收端baoyu.py。

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

博客会手工同步到以下地址:

[http://giftdotyoung.blogspot.com]

[http://blog.csdn.net/younggift]

2 comments:

Anonymous said...

I am really delighted to glance at this blog posts which includes tons of helpful facts, thanks for providing these information.


my weblog social media and facebook

Anonymous said...

Howdy! Someone in my Facebook group shared this website with us so I came to
look it over. I'm definitely loving the information. I'm bookmarking and will be tweeting this to my followers!
Great blog and terrific design.

Also visit my weblog; green tree lending