一、文件传输
1.1 文件传输因特网协议
最流行的协议包括文件传输协议(FTP)、UNIX到UNIX复制协议(UUCP)、用于Web的超文本传输协议(HTTP)。另外,还有(UNIX下的)远程文件复制命令rcp(以及更安全、灵活的scp和rsync)。
HTTP主要用于基于Web的文件下载以及访问Web服务,一般客户端无须登录就可以访问服务器上的文件和服务。大部分HTTP文件传输请求都用于获取网页(即将网页文件下载到本地)。
而scp和rsync需要用户登录到服务器主机。在传输文件之前必须验证客户端的身份,否则不能上传或下载文件。FTP与scp/rsync相同,它也可以上传或下载文件,并采用了UNIX的多用户概念,用户需要输入有效的用户名和密码。但FTP也允许匿名登录。
1.2 文件传输协议
文件传输协议(File Transfer Protocol,FTP)主要用于匿名下载公共文件,也可以用于在两台计算机之间传输文件,特别实在使用Windows进行工作,而文件存储系统使用UNIX的情况下。
FTP要求输入用户名和密码才能访问远程FTP服务器,但也允许没有账号的用户匿名登录。不过管理员要先设置FTP服务器以允许匿名用户登录。这时,匿名用户的用户名是“anonymous”,密码一般是用户的电子邮件地址。与向特定的登录用户传输文件不通过,这相当于公开某些目录让大家访问。但与登录用户相比,匿名用户只能使用有限的几个FTP明令。
由上图展示的这个协议,其工作流程如下:
1、客户端连接远程主机上的FTP服务器
2、客户端输入用户名和密码(或“anonymous”和电子邮件地址)
3、客户端进行各种文件传输和信息查询操作
4、客户端从远程FTP服务器退出,结束传输
有时,由于网络两边计算机的崩溃或网络的问题,会导致整个传输在完成之前中断。如果客户端超过15分钟(900秒)还没有响应,FTP连接就会超时并中断。
在底层,FTP只使用TCP,而不是UDP。另外,可以将FTP看作客户端/服务器编程中的特殊情况。因为这里的客户端和服务器都使用俩个套接字来通信:一个是控制和命令端口(21号端口),另一个是数据端口(有时是20号端口)。
这里说的“有时”是因为FTP由俩种模式:主动和被动。只有主动模式下服务器才使用数据端口。在服务器把20号端口设置为数据端口后,它“主动”连接客户端的数据端口。而在被动模式下,服务器只是告诉客户端随机的数据端口号,客户端必须主动建立数据连接。在这种模式下,FTP服务器在建立数据连接时是“被动”的。最后,现在已经有了一种扩展的被动模式来支持第6版本的因特网协议(IPv6)地址——详见RFC 2428
1.3 Python和FTP
回顾流程:
1、连接到服务器
2、登录
3、发出服务请求(希望得到响应)
4、退出
在使用Python的FTP支持时,所需要做的只是导入ftplib模块,并实例化一个ftplib.FTP类对象。所有的FTP操作(如登录、传输文件和注销等)都要使用这个对象完成。
from ftplib import FTP f = FTP('some.ftp.server') f.login('anonymous', 'your@email.address') . . . f.quit()
1.4 ftplib.FTP类的方法
方法 | 描述 |
login(self, user, password, acct) | 登录到FTP服务器,所有参数都是可选的 |
pwd(self) | 获取当前工作目录 |
cwd(self, dirname) | 在服务器上设置当前目录 |
dir(self, args) | 生成LIST命令返回的目录列表,将其打印到标准输出。可选参数是要列出的目录(默认为当前服务器目录)。可以使用多个参数将非标准选项传递给LIST命令。如果最后一个参数是一个函数,它将被用作回调函数retrlines(); 默认打印到 sys.stdout。此方法返回None。 |
nlst(self, args) | 与dir()类似,但返回一个文件名列表,而不是显示这些文件名 |
retrlines(self, cmd, callback) | 给定FTP命令(如“RETR filename”),用于下载文件。可选的回调函数cb用于处理文件的每一行 |
retribinary(self, cmd, fp, blocksize, callback, rest) | 与retrlines类似,只是这个指令处理二进制文件。回调函数用于处理每一块(块大小默认8KB) |
storlines(self, cmd, fp, callback) | 给定FTP命令(如“STOR filename”),用来上传文本文件。要给定一个文件对象f |
storbinary(self, cmd, fp, blocksize, callback, rest) | 与storlines()类似,只是这个指令处理二进制文件。要给定一个文件对象f,上传块大小bs默认为8KB |
rename(self, fromname, toname) | 文件重命名 |
delete(self, filename) | 删除名为dirname的远程文件。如果成功,则返回响应的文本,否则会引发error_perm权限错误或 error_reply其他错误。 |
mkd(self, dirname) | 在服务器上创建一个新目录 |
rmd(self, dirname) | 删除服务器上名为dirname的目录 |
quit(self) | 关闭连接并退出 |
close(self) | 单方面关闭连接 |
FTP对象更多信息:https://docs.python.org/3/library/ftplib.html
1.5 客户端FTP程序示例
import ftplib import os import socket HOST = 'ftp.sjtu.edu.cn' # 不可用ftp://ftp.sjtu.edu.cn/ DIRN = 'logs/rsync' FILE = 'fedora-epel.trace' def main(): try: f = ftplib.FTP(HOST) except (socket.error, socket.gaierror) as e: print('ERROR:cannot reach %s' % HOST) return print('*** Connected to host %s' % HOST) try: f.login() except ftplib.error_perm: print('ERROR: cannot login anoymously') f.quit() return print("*** Logged in as 'anonymous'") try: f.cwd(DIRN) except ftplib.error_perm: print('ERROR: cannot CD to %s' % DIRN) f.quit() return print('*** Changed to %s folder' % f.pwd()) try: f.retrbinary('RETR %s' % FILE, open(FILE, 'r')) except ftplib.error_perm: print('ERROR: cannot read file %s' % FILE) os.unlink(FILE) else: print('*** Downloaded %s to CWD' % FILE) f.quit() if __name__ == '__main__': main()
说明:
第一到七行
代码前几行导入要用的模块(主要用于抓取异常对象),并设置一些常量
第九行到四十一行
创建一个FTP对象,尝试连接到FTP服务器(第十到十五行),然后返回。如果发生任何错误九退出。接着尝试用“anonymous”登录,如果不行九结束(第十七到二十三行)。下一步就是转到发布目录(第二十五到三十一行),最后下载文件(第三十三扫四十一行)。
在第三十四行,向retrbinary()传递一个回调函数,没接收到一块二进制数据的时候都会调用这个回调函数。这个函数就是船舰文件的本地版本时需要用到的文件对象的write()方法。传输结束时,python解释器会自动关闭这个文件对象,因此不会丢失数据。虽然方便,但要尽量做到在资源不再被使用的时候九立即释放,而不是依赖其他代码来完成释放操作。这里应该把开放的文件对象保存到一个变量(如变量loc),然后把loc.write传给ftp.retrbinary()。
完成传输后,调用loc.close()。如果由于某些原因无法保存文件,则移除空的文件夹来避免弄乱文件系统(第三十七行)。在os.unlink(FILE)俩侧添加一些错误检查代码,以应对文件不存在的情况。
第四十二到四十三行
运行独立脚本的惯用方法
1.6 FTP的其他内容
Python同时支持主动和被动模式。注意,在Python2.0及以前版本中,被动模式默认时关闭的;在Python2.1及以后版本中,默认时打开的。
以下是一些典型的FTP客户端类型:
- 命令行客户端程序: 使用一些FTP客户端程序(如/bin/ftp或NcFTP)进行FTP传输,用户可以在命令中交互式执行FTP传输
- GUI客户端程序: 与命令客户端程序相似,但它是一个GUI程序,如WS_FTP、Filezilla、CuteFTP、Fetch、SmartFTP
- Web浏览器: 除了使用HTTP之外,大多数Web浏览器(也称为客户端)可以进行FTP传输。URL/URI的第一部分就用来表示所使用的协议,如“http://blahblah”。这就告诉浏览器要使用HTTP作为指定网站传输数据的协议。通过修改协议部分,就可以发送使用FTP的请求,如“ftp://blahblah”,这与使用HTTP的网页URL很像(“blahblah”可以展开为“host/path?attributes”)。如果要登录,用户可以把登录信息(以明文方式)放在URL里,如“ftp://user:password@host/path?attr1=vall&attr2=val2…”
- 自定义应用程序: 自己编写的用于FTP文件传输的程序。这些式用于特殊目的的应用程序,一般这种程序不允许用户与服务器交互
二、网络新闻
2.1 Usenet与新闻组
Usenet新闻系统式一个全球存档的“电子公告板”。各个主题的新闻组一应俱全,新闻组可以面向全球,也可以只面向某个特定区域。老的Usenet使用UUCP作为其网络传输机制,在20世纪80年代中期出现了另一个网络协议TCP/IP,之后大部分网络流量转向使用TCP/IP。
2.2 网络新闻传输协议
作为客户端/服务器架构的另一个例子,NNTP与FTP的操作方式相似,但更简单。FTP中,登录、传输数据和控制需要使用不同的端口,而NNTP只使用一个标准端口119来通信。用户向服务器发送一个请求,服务器就做出相应的响应。
2.3 Python和NNTP
NNTP协议流程:
1、连接到服务器
2、登录(根据需要)
3、发出服务请求
4、退出
from nntplib import NNTP n = NNTP('your.nntp.server') r,c,f,l,g = n.group('comp.lang.python') . n.quit()
登录后需要调用group()方法来选择一个感兴趣的新闻组。该方法返回服务器的回复、文章的数量、第一篇和最后一篇文章的ID、新闻组的名称。
2.4 nntplib.NNTP类方法
方法 | 描述 |
group(self, name) | 选择一个组的名字,返回一个元组(rsp,ct,fst,lst,group),分别表示服务器响应信息、文章数量、第一个和最后一个文章的编号、组名,所有数据都是字符串。(返回的group与传进去的name应该式相同的) |
xhdr(self, hdr, str, file) | 发送XHDR命令。该命令没有在RFC中定义,但是是一个常见的扩展。的报头参数是一个报头的关键字,例如'subject'。该字符串参数应具有的形式'first-last',其中第一和最后一个是第一个和最后的文章编号,以搜索。返回一对(response, list),其中list是对的列表(id, text),其中id是商品编号(作为字符串),text是该文章请求的标题的文本。如果提供了文件参数,则XHDR命令的输出将存储在文件中。如果文件是一个字符串,然后该方法将打开一个名称为文件对象,写入并关闭它。如果file是一个文件对象,那么它将开始调用write()它来存储命令输出的行。如果提供了文件,则返回的列表是一个空列表。 |
body(self,message_spec, file) | 发送一个BODY命令,其中id的含义与以前相同stat()。如果提供了文件参数,则主体存储在文件中。如果file是一个字符串,那么该方法将打开一个具有该名称的文件对象,写入并关闭它。如果文件是一个文件对象,那么它将开始调用write()它来存储正文的行。返回head()。如果提供了文件,则返回的列表是一个空列表。 |
head(self,message_spec, file) | 发送一个HEAD命令,其中id的含义与以前相同stat()。返回一个元组(response, number, id, list),其中前三个元素与for相同stat(),list是文章标题列表(一个没有解释的行列表,没有尾随换行符)。 |
article(self,message_spec, file) | 发送一个ARTICLE命令,其中id的含义与以前相同stat()。返回head()。 |
stat(self,message_spec) | 发送一个STAT命令,其中id是消息ID(用'<'and 括起来'>')或商品编号(作为字符串)。返回一个triple (response, number, id),其中number是商品编号(作为字符串),id是消息ID(用'<'和括起来'>')。 |
next(self) | 把文章指针移到下一篇文章,返回与stat()相似的元组 |
last(self) | 把文章指针移到最后一篇文章,返回与stat()相似的元组 |
post(self,data) | 使用该POST命令发布文章。的文件参数是其使用其读直至EOF一个打开的文件对象readline()的方法。它应该是一篇格式良好的新闻文章,包括必需的标题。该post()方法自动转义以.。开头的行。 |
quit(self) | 关闭连接并退出 |
2.5 客户端程序NNTP示例
import nntplib import socket HOST = 'your.nntp.server' GRNM = 'comp.lang.python' USER = 'wesley' PASS = 'youllNeverGuess' def main(): try: n = nntplib.NNTP(HOST) #, user = USER, password = PASS) except socket.gaierror as e: print('ERROR: cannot reach host %s' % HOST) print('%s' % eval(str(e))[1]) return except nntplib.NNTPermanentError as e: print('ERROR:access denied on %s' % HOST) print('%s' % str(e)) return print('*** Connected to host %s' % HOST) try: rsp, ct, fst, lst, grp = n.group(GRNM) except nntplib.NNTPTemporaryError as ee: print('ERROR: cannot load group %s' % GRNM) print('%s' % str(e)) print('Server may require authentication') print('Uncomment/edit login line above') n.quit() return except nntplib.NNTPTemporaryError as ee: print('ERROR: group %s unavailable' % GRNM) print('%s' % str(e)) n.quit() return print('*** Found newsgroup %s' % GRNM) rng = '%s-%s' % (lst, lst) rsp, frm = n.xhdr('from', rng) rsp, sub = n.xhdr('subject',rng) rsp, dat = n.xhdr('date', rng) print('''*** Found last article (#%s) From:%s Subject:%s Date:%s ''' % (lst, frm[0][1], sub[0][1], dat[0][1])) rsp, anum, mid, data = n.body(lst) displayFirst20(data) n.quit() def displayFirst20(data): print('*** First (<=20) meaningful lines:\n') count = 0 lines = (line.rstrip() for line in data) lastBlank = True for line in lines: if line: lower = line.lower() if(lower.startswith('>') and not \ lower.startswith('>>>')) or \ lower.startswith('|') or \ lower.startswith('in article') or \ lower.endswith('writes:') or \ lower.endswith('wrote:'): continue if not lastBlank or (lastBlank and line): print('%s' % line) if line: count += 1 lastBlank = False else: lastBlank = True if count == 20: break if __name__ == '__main__': main()
说明:
第九行到三十七行:
尝试连接NNTP主机服务器,如果失败就退出。接着尝试读取指定的新闻组。同样,如果新闻组不存在,或服务器没有保存这个新闻组,或需要身份验证,就退出
第三十九到五十一行:
这一部分读取并显示一些头消息(第三十九到第四十七行)。程序会读取作者、主题、日期这些数据并显示给用户。每次调用xhdr()方法时,都要给定想要提取消息头的文章的范围。因为这里只想获取一条消息,所以范围就是“X-X”,其中X是最新一条消息的号码。xhdr()方法返回一个长度为2的元组,其中包含了服务器的响应(rsp)和指定范围的消息头的列表。
最后一部分是下载文章的内容(第四十九到五十一行)。先调用body()方法,然后至多显示前20个有意义的行,最后从服务器注销,完成处理。
第五十三到七十六行:
该函数接受文章的一些内容,并做一些预处理,如把计数器清0,创建一个生成器表达式对文章内容的所有行做一些处理,然后“假装”刚碰到并显示了一行空行(第五十五到五十七行)。
由于不想显示引用的文本和引用文本指示行,而在第六十到六十七使用了一个大if语句。只有在当前行不是空行时,才做这个检查。检查的时候,会把字符串转成小写,这样就能做到比较的时候不区分大小写。
第六十八行的if语句表示只有在上一行不为空,或者上一行为空但当前不为空的时候才显示。
三、电子邮件
3.1 电子邮件电子组件和协议
电子邮件系统的重要组件是消息传输代理(MTA).这是在邮件交换主机上运行的服务进程,它负责邮件的路由、队列处理和发送工作。MTA就是邮件从发送主机到接收主机所要经过的主机和“跳板”,也称为“消息传输”的“代理”。MTA要知道的俩件事:
1、如何找到消息应该到达的下一个MTA
2、如何与另一台MTA通信
3.2 Python和SMTP
需要一个smtplib模块和一个需要实例化的smtplib.SMTP类。流程:
1、连接到服务器
2、登录(根据需要)
3、发出服务请求
4、退出
from smtplib import SMTP n = SMTP('smtp.yourdomain.com') . . . n.quit()
3.4 smtplib.SMTP类方法
方法 | 描述 |
sendmail(self, from_addr, to_addr, msg, mail_options, rcpt_options) | 将msg从from_addr发送至to_addr,还可以选择性地设置ESMTP邮件(mail_options)和收件人(rcpt_options)选项 |
ehlo(self, name)或helo(self, name) | 使用ehlo指令像ESMTP(SMTP扩展)确认你的身份/使用helo指令向SMTP服务器确认你的身份 |
starttls(self, keyfile, certfile, context) | 让服务器启用TLS模式。如果给定了keyfile或certfile,则它们用来创建安全套接字 |
set_debuglevel(self,debuglevel) | 为服务器通信设置调试级别 |
quit(self) | 关闭连接并退出 |
login(self, user, password, initial_response_ok) | 使用用户名和密码登录SMTP服务器 |
3.5 Python和POP3
需要一个poplib模块和一个需要实例化的poplib.POP3类。流程:
1、连接到服务器
2、登录(根据需要)
3、发出服务请求
4、退出
from poplib import POP3 p = POP3('pop.python.is.cool') p.user(...) p.pass_(...) ... p.quit()
3.6 poplib.POP3类方法
方法 | 描述 |
user(self, user) | 向服务器发送登录名,并显示服务器的响应,表示服务器正在等待输入该用户的密码 |
pass_(self, pswd) | 在用户使用user()登录后,发送password。如果登录失败,则抛出异常 |
stat(self) | 返回邮件的状态,即一个长度为2的元组,分别表示消息的数量和消息的总大小 |
list(self, which) | stat()的扩展,从服务器返回以三元组表示的整个消息列表,分别表示为服务器的响应、消息列表、返回消息的大小。 |
retr(self, which) | 从服务器中得到消息的邮件,并设置其“已读”标志。返回一个长度为3的元组,分别为服务器的响应、消息的邮件的所有行、消息的字节数 |
dele(self, which) | 将标记的消息删除,大多数服务器在调用quit()后执行删除操作 |
quit(self) | 注销、提交修改(如处理“已读”和“删除”标记等)、解锁邮箱、终止连接,然后退出 |
3.7 客户端程序SMTP和POP3示例
from smtplib import SMTP from poplib import POP3 from time import sleep from email.parser import Parser from email.mime.text import MIMEText SMTPSVR = 'smtp.exmail.qq.com' # SMTP服务器 POPSVR = 'pop.exmail.qq.com' # POP服务器 mail_user = "username" # 用户名 mail_pass = "password" # 密码 sender = 'sender mail' # 发件人邮箱 receivers = ['receiver mail'] # 接收人邮箱 content = 'Hello World!' title = 'test msg' # 邮件主题 message = MIMEText(content, 'plain', 'utf-8') # 内容, 格式, 编码 message['From'] = "{}".format(sender) message['To'] = ",".join(receivers) message['Subject'] = title sendSvr = SMTP(SMTPSVR) sendSvr.login(mail_user, mail_pass) errs = sendSvr.sendmail(sender, receivers, message.as_string()) # 发送邮件,sendmail()的第三个参数是电子邮件消息本身 sendSvr.quit() assert len(errs) == 0, errs sleep(10) recvSvr = POP3(POPSVR) recvSvr.user(mail_user) recvSvr.pass_(mail_pass) rsp, msg, siz = recvSvr.retr(recvSvr.stat()[0]) # stat()方法得到可用消息列表,通过[0]符号选中第一条消息;retr()下载这条消息 msg_content = b'\r\n'.join(msg).decode('utf-8') recvBody = Parser().parsestr(msg_content)
3.8 Python和IMAP4
from imaplib import IMAP4 s = IMAP4('imap.python.is.cool') s.login(...) ... s.close() s.logout()
3.9 imaplib.IMAP4类的常用方法
方法 | 描述 |
close(self) | 关闭当前邮箱。如果访问权限不是只读,则本地删除大的邮件在服务器端也会被丢弃 |
login(self, user, password) | 使用指定的用户名和密码登录 |
logout(self) | 从服务器注销 |
fetch(self, message_set, message_parts) | 获取之前由message_set设置的电子邮件消息状态(或使用message_parts获取部分状态信息) |
noop(self) | ping服务器,但不产生任行为 |
search(self, charset, *criteria) | 查询邮箱中至少匹配一块criteria的消息。如果charset为False,则默认使用US-ASCII |
select(self, mailbox, readonly) | 选择一个文件夹(默认是INBOX),如果是只读,则不允许用户修改其中的内容 |