一、文件传输

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明令。

图片.png

由上图展示的这个协议,其工作流程如下:
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来通信。用户向服务器发送一个请求,服务器就做出相应的响应。

图片.png

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、退出

图片.png
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),如果是只读,则不允许用户修改其中的内容
posted on 2018-10-31 14:42  Q同码  阅读(337)  评论(0编辑  收藏  举报