网络客户端编程

今天学习了网络客户端编程

主要看了文件传输和电子邮件

没有非常细的看

觉得主要还是要理解其思想

及实现步骤

要用的时候就没那么困难了

4.1首先:什么是因特网客户端
  因特网客户端到底是什么”?要回答这个问题,我们把因特网简化成一个数据交换中心,数据交换的参与者是一个服务提供者和一个服务的使用者。有的人把它称为“生产者-消费者”(虽然这个词一般只用在讲解操作系统相关信息时)。服务器就是生产者,它提供服务,一般只有一个服务器(进程或主机等),和多个消费者,就像我们之前看的客户端/服务器模型那样。虽然现在我们不再使用底级别的套接字来创建因特网客户端,但模型是完全相同的。
  

4.2.1文件传输
  因特网中最流行的事情就是文件的交换。文件交换无处不在。有很多协议可以供因特网上传输文件使用。最流行的有文件传输协议(FTP),Unix-to-Unix 复制协议(UUCP),以及网页的超文本传输协议(HTTP)。另外,还有(Unix 下的)远程文件复制指令rcp(以及更安全,更灵活的scp 和rsync)。

4.2.2文件传输协议(FTP)
  它主要用于匿名下载公共文件。也可以用于在两台电脑之间传输文件,尤其是在使用Unix 系统做为文件存储系统,使用其它机器来工作的情况。早在网络流行之前,FTP 就是在因特网上文件传输,软件和源代码下载的主要手段之一。FTP 要求输入用户名和密码才能访问远程的FTP 服务器,但它也允许没有帐号的用户以匿名用户登录。不过,管理员要先设置FTP 服务器允许匿名用户登录。这时,匿名用户的用户名“anonymous”,密码一般是用户的e-mail 地址。与特定的用户拥有特定的帐户不同,这有点像是把FTP 公开出来让大家访问。匿名用户通过FTP 协议可以使用的命令与一般的用户相比来说,限制更多。

其工作流程如下:
  1. 客户端连接远程的FTP 服务器
  2. 客户端输入用户名和密码(或“anonymous”和e-mail 地址)
  3. 客户端做各种文件传输和信息查询操作
  4. 客户端登出远程FTP 服务器,结束通讯


  当然,这只是很泛的一个流程。有时,由于网络两边电脑的崩溃或是网络的问题,会导致整个事务在完成之前被中断。一般,在客户端超过15 分钟(900 秒)不活动之后,连接就会被关闭。在底层上,FTP 只使用TCP(见前面网络编程相关章节)——它不使用UDP。而且,FTP 是客户端/服务器编程中很“与众不同”的例子。客户端和服务器都使用两个套接字来通讯:一个是控制和命令端口(21 号端口),另一个是数据端口(有时是20 号端口)。客户端和服务器使用指令和控制端口发送FTP 协议,而数据通过数据端口传输。我们说“有时”是因为FTP 有两种模式:主动和被动。只有在主动模式服务器才使用数据端口。在服务器把20 号端口设置为数据端口后,它“主动”连接客户端的数据端口。而被动模式中,服务器只是告诉客户端它的随机端口的号码,客户端必须主动建立数据连接。在这种模式下,你会看到,FTP 服务器在建立数据连接时是“被动”的。最后,现在已经有了一种扩展被动模式来支持第6 版本的因特网协议(IPv6)地址——见 RFC 2428。

 

Python 已经支持了包括FTP 在内的大多数据因特网协议。支持各个协议的客户端模块可以在http://docs.python.org/lib/internet.html 找到。现在看看用Python 创建一个因特网客户端程序有多简单。


4.3.1python和FTP

那么,我们怎么用Python 写FTP 客户端程序呢?其实,我们之前已经提到过一些了。现在还要再加上相应的Python 模块导入和调用的操作。现在再来回顾一下流程:


1. 连接到服务器
2. 登录
3. 发出服务请求 (有可能有返回信息)
4. 退出


在使用Python 的FTP 支持时,你所需要做的就是导入ftplib 模块,并实例化一个ftplib.FTP类对象。所有的FTP 操作(如登录,传输文件和登出等)都要使用这个对象来完成。下面是一段Python的伪代码:


from ftplib import FTP
f = FTP('ftp.python.org')
f.login('anonymous', 'guess@who.org')
:
f.quit()
在看真实的例子之前,我们要先熟悉一下ftplib.FTP 类的方法,这些方法将在代码中用到。

 

4.3.2ftplib.FTP类方法
  也就是说,你不一定要使用其它的方法,因为它们或者是辅助函数,或者是管理函数,或者是被API 调用的。


表17.1 FTP 对象的方法
方法             描述
login(user='anonymous',
passwd='', acct='')      登录到FTP 服务器,所有的参数都是可选的
pwd()             得到当前工作目录
cwd(path)           把当前工作目录设置为path
dir([path[,...[,cb]])       显示path 目录里的内容,可选的参数cb 是一个回调函数,它会被传给retrlines()方法
nlst([path[,...]) 与dir()    类似,但返回一个文件名的列表,而不是显示这些文件名
retrlines(cmd [, cb])       给定FTP 命令(如“RETR filename”),用于下载文本文件。可选的回调函数cb 用于处理文件的每一行
retrbinary(cmd, cb[,
bs=8192[, ra]])        与retrlines()类似,只是这个指令处理二进制文件。回调函数cb 用于处理每一块(块大小默认为8K)下载的数据。
storlines(cmd, f)        给定FTP 命令(如“STOR filename”),以上传文本文件。要给定一个文件对象f
storbinary(cmd, f[,
bs=8192])           与storlines()类似,只是这个指令处理二进制文件。要给定一个文件对象f,上传块大小bs 默认为8Kbs=8192])
rename(old, new)         把远程文件old 改名为new
delete(path)       删除位于path 的远程文件
mkd(directory)       创建远程目录
rmd(directory)         删除远程目录
quit()         关闭连接并退出


在一般的FTP 通讯中,要使用到的指令有login(), cwd(), dir(), pwd(), stor*(), retr*()和quit()。有一些没有列出的FTP 对象方法也是很有用的。请参阅Python 的文档以得到更多关于FTP 对象的信息:


http://python.org/docs/current/lib/ftp-objects.html


4.3.3客户端FTP程序举例

  之前我们说过,你可以不写脚本,在交互环境中使用FTP。不过,下面我们还是要写一段脚本,假设你要从Mozilla 的网站下载最新的Bugzilla 的代码。例17.1 就是用来完成这个工作的。我们在试着写一个应用程序,不过,你也可以交互式地运行这段代码。我们的程序使用FTP 库来下载文件,也做了一些错误检测。不过,程序并不完全自动。你要自己决定什么时候要去下载。如果你在使用类Unix 系统,你可以设定一个“cron”任务来自动下载。另一个问题是,如果文件的文件名或目录名改了的话,程序就不能正常工作了。


例17.1 FTP 下载示例 (getLatestFTP.py)
这个程序用于下载网站中最新版本的文件。你可以修改这个程序让它下载你喜欢的程序。

1 #!/usr/bin/env python
2
3 import ftplib
4 import os
5 import socket
6
7 HOST = 'ftp.mozilla.org'
8 DIRN = 'pub/mozilla.org/webtools'
9 FILE = 'bugzilla-LATEST.tar.gz'
10
11 def main():
12 try:
13 f = ftplib.FTP(HOST)
14 except (socket.error, socket.gaierror), e:
15 print 'ERROR: cannot reach "%s"' % HOST
16 return
17 print '*** Connected to host "%s"' % HOST
18
19 try:
20 f.login()
21 except ftplib.error_perm:
22 print 'ERROR: cannot login anonymously’
23 f.quit()
24 return
25 print '*** Logged in as "anonymous"'
26
27 try:
28 f.cwd(DIRN)
29 except ftplib.error_perm:
30 print 'ERROR: cannot CD to "%s"' % DIRN
31 f.quit()
32 return
33 print '*** Changed to "%s" folder' % DIRN
34
35 try:
36 f.retrbinary('RETR %s' % FILE,
37 open(FILE, 'wb').write)
38 except ftplib.error_perm:
39 print 'ERROR: cannot read file "%s"' % FILE
40 os.unlink(FILE)
41 else:
42 print '*** Downloaded "%s" to CWD' % FILE
43 f.quit()
44 return
45
46 if __name__ == '__main__':
47 main()

 


如果运行脚本时没有出错,则会得到如下输出:

$ getLatestFTP.py
*** Connected to host "ftp.mozilla.org"
*** Logged in as "anonymous"
*** Changed to "pub/mozilla.org/webtools" folder
*** Downloaded "bugzilla-LATEST.tar.gz" to CWD
$

 


逐行解释
1-9 行
  代码前几行导入要用的模块和设置一些常量
11-44 行
  main()函数分为以下几步:创建一个FTP 对象,尝试连接到FTP 服务器(12-17 行)然后返回。在有任何错误发生的时候退出。我们尝试用“anonymous”登录,如果不行就结束(19-25 行)。下一步就是转到发布目录(27-33 行),最后,下载文件(35-44 行)。


  在35-36 行,我们传了一个回调函数给retrbinary(),它在每接收到一块二进制数据的时候都会被调用。这个函数就是我们创建的本地文件对应文件对象的write 方法。在传输结束的时候,Python解释器会自动关闭这个文件对象,而不会丢失数据。虽然这样方便,但最好还是不要这样做,做为一个程序员,要尽量做到在资源不再被使用的时候就直接释放,而不是依赖其它代码来做释放操作。在这里, 我们应该把文件对象保存到一个变量中, 如变量loc , 然后把loc.write 传给ftp.retrbinary()方法。
在代码中,如果由于某些原因我们无法保存这个文件,那要把存在的空文件给删掉,以防搞乱文件系统(40 行)。最后,我们使用了try-except-else 子句(35-42 行),而不是写两遍关闭FTP连接然后返回的代码。


46-47 行
  这是运行独立脚本的惯用方法。

4.4.1电子邮件

  在看e-mail 的底层的结构之前,你有没有问过自己,e-mail 的确切定义到底是什么?根据RFC2822,“消息由头域(合起来叫消息头)以及后面可选的消息体组成”。对于一般用户来说,一说起e-mail 就会让我们想到它的内容,不管它是一封真的邮件还是一封不请自来的商业广告(即spam,垃圾邮件),都应该有内容。不过,RFC 规定,邮件体是可选的,只有邮件头是必要的。这一点要特别注意。

4.4.2E-mail 系统组件和协议

  不管你是怎么样想的,电子邮件(e-mail)实际上在现代的因特网出现之前就已经出现了。它一开始用于mainframe 的用户之间简单的交换信息。注意,由于他们都在使用同一台电脑,所以,这里甚至都没有涉及到网络。后来,当网络成为现实的时候,用户就可以在不同的主机之间交换信息。当然,由于用户使用着不同的电脑,电脑之间使用着不同的协议,信息交换成了一个很复杂的概念。直到20 世纪80 年代,因特网上用e-mail 进行信息交换才有了一个事实上的统一的标准。在深入细节之前,我们先问问自己,e-mail 是怎么工作的?一条消息是如何从发件人那通过浩瀚的因特网,到达收件人的?简单点来说,有一台发送电脑(发件人的消息从这里发送出去),和一台目的电脑(收件人的信件服务器)。最好的解决方案是发送电脑知道如何连接到接收电脑,这样一来,它就可以直接把消息发送过去。不过,实际上一般并不这么顺利。发送电脑要查询到某一台中间主机,这台中间主机能到达最后的收件主机。然后这台中间主机要找一台离目的主机更近一些的主机。所以,在发送主机和目的主机之间,可能会有多台叫做“跳板”的主机。如果你仔细看看你收到的e-mail 的邮件头,你会看到一个“passport”标记,其中记录了邮件寄给你这一路上都到过了哪些地方。为了让描述清楚一些,让我们先看看e-mail 系统的各个组件。最主要的组件是消息传输代理(MTA)。这是一个在邮件交换主机上运行的一个服务器程序,它负责邮件的路由,队列和发送工作。
  它们就是邮件从源主机到目的主机所要经过的跳板。所以也被称为是“信息传输”的“代理”。要让所有这些工作起来,MTA 要知道两件事情:1) 如何找到消息应该去的下一台MTA 2) 如何与另一台MTA 通讯。第一件事由域名服务(DNS)来查找目的域名的MX(邮件交换Mail eXchange)来完成。这对于最后的收件人是不必要的,但对其它的跳板来说,则是必要的。对于第二件事,MTA怎么把消息转给其它的MTA 呢?

4.4.3发送E-mail

  要能发送e-mail,你的邮件客户端一定要连接到一个MTA,它们靠某种协议进行通讯。MTA 之间通讯所使用的协议叫消息传输系统(MTS)。只有两个MTA 都使用这个协议时,才能进行通讯。在本节开始时就说过,由于以前存在很多不同的计算机系统,每个系统都使用不同的网络软件,这种通讯很危险,具有不可预知性。更复杂的是,有的电脑使用互连的网络,而有的电脑使用调制解调器拨号,消息的发送时间也是不可预知的。事实上,笔者曾经有一封邮件在发送9 个月后才收到!互连网的速度怎么会这么慢?出于对这些复杂度的考虑,现代e-mail 的基础之一,简单邮件传输协议(SMTP)于1982 年出现了。


SMTP
  SMTP 由已故的Jonathan Postel(加利福尼亚大学信息学院)创建,记录在RFC 821 中,于1982 年8 月公布。其后的修改记录在RFC 2821 中,于2001 年4 月公布。一些已经实现了SMTP的著名MTA 包括:


开源MTA
􀁺 Sendmail
􀁺 Postfix
􀁺 Exim
􀁺 qmail (免费发布,但不开源)
商业MTA
􀁺 Microsoft Exchange
􀁺 Lotus Notes Domino Mail Server


注意,虽然它们都实现了RFC 2821 中定义的最小化SMTP 协议,它们中的大多数,尤其是一些商业MTA,都在服务器中加入了协议定义之外的特有的功能。SMTP 是在因特网上MTA 之间用于消息交换的最常用的MTS。它被MTA 用来把e-mail 从一台主机传送到另一台主机。在你发e-mail 的时候,你必须要连接到一个外部的SMTP 服务器,这时,你的邮件程序是一个SMTP 客户端。你的SMTP 服务器也因此成为了你的消息的第一个跳板。 

 

4.5.1Python和SMTP
是的,也存在一个smtplib 模块和一个smtplib.SMTP 类要实例化。再来看看这个已经熟悉的过
程吧:
1. 连接到服务器
2. 登录(如果需要的话)
3. 发出服务请求
4. 退出
像NNTP 一样,登录是可选的,只有在服务器打开了SMTP 认证(SMTP-AUTH)时才要登录。SMTP-AUTH
在RFC 2554 中定义。还是跟NNTP 一样,SMTP 通讯时,只要一个端口25。
下面是一些Python 的伪代码:
from smtplib import SMTP
n = SMTP('smtp.yourdomain.com')
...
n.quit()
在看真实的例子之前,我们要先介绍一下smtplib.SMTP 类的一些常用的方法。


4.5.2smtplib.SMTP 类方法
  跟之前一样,我们会列出smtplib.SMTP 类的方法,但不会列出所有的方法,只列出你创建SMTP客户端程序所需要的方法。对大多数e-mail 发送程序来说,只有两个方法是必须的:sendmail()和it()。    sendmail()的所有参数都要遵循RFC 2822,即e-mail 地址必须要有正确的格式,消息体要有正确的前导头,前导头后面是两个回车和换行(\r\n)对。注意,实际的消息体不是必要的。根据RFC 2822,“唯一要求的头信息只有发送日期和发送地址”,即“Date:”和“From:”:(MAIL FROM, RCPT TO, DATA)还有一些方法没有被提到,不过,一般来说,它们不是发送e-mail 所必须的。请参考Python文档以获取SMTP 对象的所有方法的信息。


表17.3 SMTP 对象的方法
方法         描述
Sendmail(from, to, msg[,mopts, ropts])           把msg 从from 发送给to(列表或元组)。ESMTP 设置(mopts)和收件人设置(ropts)为可选。
quit()                           关闭连接,然后退出
login(user, passwd)a                     使用user 用户和passwd 密码登录到SMTP 服务器a. 只在有SMTP-AUTH 时使用。

4.4.5 交互式SMTP 示例
  同样地,我们先给一个交互式的例子:

>>> from smtplib import SMTP as smtp
>>> s = smtp('smtp.python.is.cool')
>>> s.set_debuglevel(1)
>>> s.sendmail('wesley@python.is.cool', ('wesley@python.is.cool',
'chun@python.is.cool'), ''' From: wesley@python.is.cool\r\nTo:
wesley@python.is.cool, chun@python.is.cool\r\nSubject: test
msg\r\n\r\nxxx\r\n.''')
send: 'ehlo myMac.local\r\n'
reply: '250-python.is.cool\r\n'
reply: '250-7BIT\r\n'
reply: '250-8BITMIME\r\n'
reply: '250-AUTH CRAM-MD5 LOGIN PLAIN\r\n'
reply: '250-DSN\r\n'
reply: '250-EXPN\r\n'

reply: '250-HELP\r\n'
reply: '250-NOOP\r\n'
reply: '250-PIPELINING\r\n'
reply: '250-SIZE 15728640\r\n'
reply: '250-STARTTLS\r\n'
reply: '250-VERS V05.00c++\r\n'
reply: '250 XMVP 2\r\n'
reply: retcode (250); Msg: python.is.cool
7BIT
8BITMIME
AUTH CRAM-MD5 LOGIN PLAIN
DSN
EXPN
HELP
NOOP
PIPELINING
SIZE 15728640
STARTTLS
VERS V05.00c++
XMVP 2
send: 'mail FROM:<wesley@python.is.cool> size=108\r\n'
reply: '250 ok\r\n'
reply: retcode (250); Msg: ok
send: 'rcpt TO:<wesley@python.is.cool>\r\n'
reply: '250 ok\r\n'
reply: retcode (250); Msg: ok
send: 'data\r\n'
reply: '354 ok\r\n'
reply: retcode (354); Msg: ok
data: (354, 'ok')
send: 'From: wesley@python.is.cool\r\nTo:
wesley@python.is.cool\r\nSubject: test
msg\r\n\r\nxxx\r\n..\r\n.\r\n'
reply: '250 ok ; id=2005122623583701300or7hhe\r\n'
reply: retcode (250); Msg: ok ; id=2005122623583701300or7hhe
data: (250, 'ok ; id=2005122623583701300or7hhe')
{}
>>> s.quit()
send: 'quit\r\n'
reply: '221 python.is.cool\r\n'
reply: retcode (221); Msg: python.is.cool

 

 

posted @ 2015-12-13 20:49  Fanyear  阅读(322)  评论(1编辑  收藏  举报