17.2 文件传输

17.2.1 文件传输网际协议

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

迄今为止,HTTP、FTP和scp/rsync还是非常流行的。HTTP主要用于网页文件的下载和访问Web服务上。它一般不要求用户输入登录的用户名密码就可以访问服务器上的文件和服务。HTTP文件传输请求主要是用于获取网页(文件下载)。

相对的,scp和rsync要求用户登录到服务器,否则不能上传或下载文件。至于FTP,跟scp/rsync一样,可以上传或下载文件,还采用了Unix的多用户的概念,用户一定要输入有效的用户名和密码才能使用。不过,FTP也允许匿名登录。接下来,我们先仔细看看FTP。

17.2.2 文件传输协议(FTP)

文件传输协议由已故的Jon Postel(作者这里使用的Jon是Jonathon的简写,下文中会使用全名)和Joyce Reynolds开发,记录在RFC (Request for Comment)959号文档中,于1985年10月发布,主要用于匿名下载公共文件。也可以用于在两台电脑之间传输文件,尤其是在使用Unix系统作为文件存储系统,使用其他机器来工作的情况。早在网络流行之前,FTP就是在因特网上文件传输、软件和源代码下载的主要手段之一。

FTP要求输入用户名和密码才能访问远程的FTP服务器,但它也允许没有账号的用户以匿名用户登录。不过,管理员要先设置FTP服务器允许匿名用户登录。这时,匿名用户的用户名是“匿名”(anonymous),密码一般是用户的电子邮件地址。与特定的用户拥有特定的账户不同,这有点像是把FTP公开出来让大家访问。匿名用户通过FTP协议可以使用的命令与一般的用户相比来说,限制更多。

图17-1展示了这个协议,其工作流程如下:

1.客户端连接远程的FTP服务器;

2.客户端输入用户名和密码(或“匿名”和电子邮件地址);

3.客户端做各种文件传输和信息查询操作;

4.客户端登出远程FTP服务器,结束通讯。

17.2 文件传输 - 图1

图 17-1 因特网上的FTP客户端和服务器。客户端和服务器使用指令和控制端口发送FTP协议,而数据通过数据端口传输。

当然,这只是一个大致流程。有时,由于网络两边电脑的崩溃或是网络的问题,会导致整个事务在完成之前被中断。一般在客户端超过15分钟(900秒)不活动之后,连接就会被关闭。

在底层上,FTP只使用TCP(见前面网络编程相关章节)——它不使用UDP。而且,FTP是客户端/服务器编程中很“与众不同”的例子。客户端和服务器都使用两个套接字来通讯:一个是控制和命令端口(21号端口),另一个是数据端口(有时是20号端口)。

我们说“有时”是因为FTP有两种模式:主动和被动。只有在主动模式服务器才使用数据端口。在服务器把20号端口设置为数据端口后,它“主动”连接客户端的数据端口。而被动模式中,服务器只是告诉客户端它的随机端口的号码,客户端必须主动建立数据连接。在这种模式下,你会看到,FTP服务器在建立数据连接时是“被动”的。最后,现在已经有了一种扩展被动模式来支持第6版本的网际协议(IPv6)地址——见RFC 2428。

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

17.2.3 Python和FTP

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

1.连接到服务器;

2.登录;

3.发出服务请求(有可能有返回信息);

4.退出。

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

17.2 文件传输 - 图2

在看真实的例子之前,我们要先熟悉一下ftplib.FTP类的方法,这些方法将在代码中用到。

17.2.4 ftplib.FTP类方法

在表17.1中列出了最常用的方法,这个表并不全面——想查看所有的方法,请参阅模块源代码——但这里列出的方法组成了我们在Python中FTP客户端编程的“API”。

17.2 文件传输 - 图3

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

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

17.2 文件传输 - 图4

17.2.5 交互式FTP示例

在Python中使用FTP非常的简单,你甚至可以不用写脚本,直接在交互式解释器中实时地看到交互与输出。下面这个例子是在几年前,python.org还支持ftp服务的时候做的。

17.2 文件传输 - 图5

17.2 文件传输 - 图6

17.2.6 客户端FTP程序举例

之前我们说过,你可以不写脚本,在交互环境中使用FTP。不过,下面我们还是要写一段脚本,假设你要从Mozilla的网站下载最新的Bugzilla的代码。例17.1就是用来完成这个工作的。我们在试着写一个应用程序,不过,你也可以交互式地运行这段代码。我们的程序使用FTP库来下载文件,也做了一些错误检测。

不过,程序并不完全自动。你要自己决定什么时候要去下载。如果你在使用类Unix系统,你可以设定一个“cron”任务来自动下载。另一个问题是,如果文件的文件名或目录名改了的话,程序就不能正常工作了。

这个程序用于下载网站中最新版本的文件。你可以修改这个程序让它下载你喜欢的程序。

17.2 文件传输 - 图7

17.2 文件传输 - 图8

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

17.2 文件传输 - 图9

逐行解释

1 ~ 9行

代码前几行导入要用的模块和设置一些常量。

11 ~ 44行

main()函数分为以下几步:创建一个FTP对象,尝试连接到FTP服务器(12〜17行)然后返回。在有任何错误发生的时候退出。我们尝试用“匿名”登录,如果不行就结束(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行

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

17.2.7 FTP的其他方面

Python同时支持主动和被动模式。注意,在Python2.0及以前版本中,被动模式支持默认是关闭的,在Python2.1及以后版本中,默认是打开的。

以下是一些典型的FTP客户端类型:

“ ftp://user:passwd@host/path?attrl=vall&attr2=val2… ”。

  • 定制程序:你自己写的用于FTP文件传输的程序。由于程序用于特殊目的,一般这种程序都不允许用户与服务器接触。

这4种客户端类型都可以用Python来写。上面,我们用ftplib来创建了一个自己的定制程序,你也可以自己做一个命令行的应用程序。在命令行的基础上,你可以使用一些界面工具包,如Tk、wxWidgets、GTK+、Qt、MFC,甚至Swing(要导入相应的Python[或Jython]的接口模块)来创建一个完整的GUI程序。最后,你可以使用Python的urllib模块来解析FTP的URL并进行FTP传输。在urllib的内部也导入并使用了ftplib,urllib也是ftplib的客户端。

FTP不仅可以用在下载应用程序上,还可以用在系统之间文件的转移上。比如,如果你是一个工程师或是系统管理员,你需要传输文件。在跨网络的时候,很明显可以使用scp或rsync命令,或者把文件放到一个外部能访问的服务器上。不过,在一个安全网络的内部机器之间移动大量的日志或数据库文件,这种方法的开销就太大了,要注意安全性、加密、压缩、解压缩等。如果你想要做的只是写一个FTP程序来帮助你在下班后自动移动文件,那用Python是一个非常好的主意。

从FTP协议定义/规范(RFC959)中,你可以得到更多关于FTP的信息:ftp://ftp.isi.edu/in-notes/rfc959.txt以及网页http://www.networksorcery.com/enp/protocol/ftp.htm。其他相关的 RFC有 2228、2389、2428、2577、2640和4217。想了解更多Python对FTP的支持,可以访问网址http://python.org/docs/current/lib/module-ftplib.html。