20.5 备份或镜像一个文件

文件传输协议(FTP)是用来在网络主机之间传输文件的协议。就像使用HTTP连接一样,使用PHP,可以在FTP中使用fopen()函数和其他不同的文件函数来连接一个FTP服务器,并在客户端和FTP服务器之间传送文件。但是,标准的PHP安装也提供了一整套专门适用于FTP的函数。

在默认情况下,这些函数并没有内置在PHP的标准安装中。要在UNIX下使用这些函数,必须运行带有—enable-ftp命令选项的configure程序,然后再次运行make文件。如果使用标准的Windows安装,FTP函数将被自动启用。(关于如何配置PHP的详细信息,请参阅附录A“安装PHP和MySQL”。)

20.5.1 使用FTP备份或镜像一个文件

对于从一台主机到另一台主机之间的文件移动和复制来说,FTP函数是非常有用的。FTP最常见的应用可能就是在另一个位置备份Web站点或者镜像文件。接下来,我们来了解一个使用FTP函数镜像文件的简单例子,如程序清单20-4所示。

程序清单20-4 ftp_mirror.php——一个从FTP服务器下载新版本文件的脚本


<html>

<head>

<title>Mirror update</title>

</head>

<body>

<h1>Mirror update</h1>

<?php

//set up variables-change these to suit application

$host='ftp.cs.rmit.edu.au';

$user='anonymous';

$password='me@example.com';

$remotefile='/pub/tsg/teraterm/ttssh14.zip';

$localfile='/tmp/writable/ttssh14.zip';

//connect to host

$conn=ftp_connect($host);

if(!$conn)

{

echo'Error:Could not connect to ftp server<br/>';

exit;

}

echo"Connected to$host.<br/>";

//log in to host

$result=@ftp_login($conn,$user,$pass);

if(!$result)

{

echo"Error:Could not log on as$user<br/>";

ftp_quit($conn);

exit;

}

echo"Logged in as$user<br/>";

//check file times to see if an update is required

echo'Checking file time…<br/>';

if(file_exists($localfile))

{

$localtime=filemtime($localfile);

echo'Local file last updated';

echo date('G:i j-M-Y',$localtime);

echo'<br/>';

}

else

$localtime=0;

$remotetime=ftp_mdtm($conn,$remotefile);

if(!($remotetime>=0))

{

//This doesn't mean the file's not there,server may not support mod

time

echo'Can\'t access remote file time.<br/>';

$remotetime=$localtime+1;//make sure of an update

}

else

{

echo'Remote file last updated';

echo date('G:i j-M-Y',$remotetime);

echo'<br/>';

}

if(!($remotetime>$localtime))

{

echo'Local copy is up to date.<br/>';

exit;

}

//download file

echo'Getting file from server…<br/>';

$fp=fopen($localfile,'w');

if(!$success=ftp_fget($conn,$fp,$remotefile,FTP_BINARY))

{

echo'Error:Could not download file';

ftp_quit($conn);

exit;

}

fclose($fp);

echo'File downloaded successfully';

//close connection to host

ftp_quit($conn);

?>

</body>

</html>


在特定情况下,运行以上脚本的输出结果如图20-4所示。

20.5 备份或镜像一个文件 - 图1

图 20-4 该FTP镜像脚本将检查文件的本地版本是否是最新版本,如果不是,就下载一个新的版本

这是一个非常普通的脚本。可以看到,在此脚本的开始处设置了一些变量:


$host='ftp.cs.rmit.edu.au';

$user='anonymous';

$password='me@example.com';

$remotefile='/pub/tsg/teraterm/ttssh14.zip';

$localfile='/tmp/writable/ttssh14.zip';


变量$host包含了即将连接的FTP服务器的名称,$user和$password对应于登录的用户名和密码。

许多FTP网站都支持匿名登录(anony-mous),匿名登录是指任何人都可以使用这个用户名来连接FTP服务器。匿名登录不需要密码,但通常的做法是把电子邮件作为密码,这样管理员能够知道用户来自什么地方。在这里,我们沿袭了这个惯例。

变量$remotefile包含我们将要下载的文件的路径。在这个例子中,我们要下载并镜像Tera Term SSH的一个本地拷贝,以及一个适用于Windows平台的SSH客户端(SSH表示安全Shell。它是Telnet的加密形式)。

变量$localfile包含了下载文件存储在机器上的本地路径。在这个例子中,我们创建了/tmp/writable目录,该目录的权限设置允许PHP对其执行写操作,也就是可以写入一个文件。无论操作系统是什么,必须创建这个目录,这样这个脚本才能正确运行。如果操作系统具有严格的权限机制,必须确认它们允许这个脚本执行写操作。我们应该能够根据需要修改这些变量和脚本。

该脚本的基本执行步骤与在命令行中手动使用FTP传输文件一样:

1.连接远程FTP服务器。

2.登录(通过用户名或匿名登录)。

3.检查远程文件是否已经更新。

4.如果更新过,下载此文件。

5.关闭FTP连接。

下面,让我们依次讨论这些步骤。

1.连接远程FTP服务器

这一步与在Windows或者UNIX平台的命令提示符下输入如下命令等价:


ftp hostname


在PHP中,通过如下所示代码完成此步骤:


$conn=ftp_connect($host);

if(!$conn)

{

echo'Error:Could not connect to ftp server<br/>';

exit;

}

echo"Connected to$host.<br/>";


在这里,我们调用的函数是ftp_connect()。此函数以主机名作为参数并返回连接的句柄或者false(如果不能建立连接)。这个函数也能够以要连接的主机端口号作为可选的第二个参数(这里没有用到)。如果不指定一个端口号,此函数将使用默认值21(FTP的默认端口)。

2.登录到FTP服务器

下一步是使用一个使用特定用户名和密码登录到FTP服务器上。可以通过函数ftp_login()来完成此步骤:


$result=@ftp_login($conn,$user,$pass);

if(!$result)

{

echo"Error:Could not log on as$user<br/>";

ftp_quit($conn);

exit;

}

echo"Logged in as$user<br/>";


这个函数需要3个参数:一个FTP连接句柄(通过函数ftp_connect()获得)、用户名和密码。如果用户能够登录,此函数将返回true,如果不能登录,函数将返回false。注意我们在这一行的开始处放置一个“@”符号来抑制错误。这是因为如果用户不能登录,他将在浏览器窗口中获得一个PHP警告信息。通过测试$result变量的内容,可以捕获这个错误,并可以自己定制更多的、更友好的用户出错信息。

请注意,如果登录尝试失败,实际上将通过ftp_quit()函数关闭FTP连接;稍后将详细介绍该函数。

3.检查文件更新时间

假设我们打算更新文件的本地副本,比较明智的做法是首先检查文件是否需要更新,因为如果文件是最新的,就无须重新下载此文件,特别是当它是一个很大的文件的时候。这将避免不必要的网络通信量。现在,让我们来查看实现检查文件更新时间的代码。

文件时间是我们使用FTP函数而不是其他更简单的文件函数的原因。文件函数可以很容易读,而且在某些情况下,也可以通过网络接口写文件,但是大多数状态函数,例如filemtime(),无法执行远程操作。这一点将在以后发生变化。

要确定是否需要下载一个文件,可以通过函数file_exists()确认一个文件的本地副本是否存在。如果没有,显然需要下载此文件。如果此文件存在,通过函数filemtime()获得文件的最后修改时间,并把它存储在变量$localtime中。如果此文件不存在,设置变量$localtime为0,这样,此文件将会比任何可能的远程文件修改时间都要“老”。


echo'Checking file time…<br/>';

if(file_exists($localfile))

{

$localtime=filemtime($localfile);

echo'Local file last updated';

echo date('G:i j-M-Y',$localtime);

echo'<br/>';

}

else

$localtime=0;


(在第2章“数据的存储与检索”和第19章“与文件系统和服务器的交互”中,我们已经分别介绍了函数file_exists()和filemtime()的使用)。

解决了本地时间的问题后,我们需要获得远程文件的修改时间。可以通过函数ftp_mdtm()获得此信息,如下所示:


$remotetime=ftp_mdtm($conn,$remotefile);


这个函数带有两个参数(FTP连接句柄和远程文件的路径),并返回文件最后修改时间的UNIX时间戳或者-1(如果存在某种错误的话)。不是所有的FTP服务器都支持这种特性,因此通过这个函数可能得不到一个有用的结果。在这种情况下,可以选择手动设置,将变量$localtime加1,使变量$remotetime比$localtime“新”。这样,就可以确保能够下载文件,如下代码所示:


if(!($remotetime>=0))

{

//This doesn't mean the file's not there,server may not support mod time

echo'Can't access remote file time.<br>';

$remotetime=$localtime+1;//make sure of an update

}

else

{

echo'Remote file last updated';

echo date('G:i j-M-Y',$remotetime);

echo'<br>';

}


当我们拥有二者的时间时,就可以对它们进行比较,确认是否需要下载这个文件:


if(!($remotetime>$localtime))

{

echo'Local copy is up to date.<br>';

exit;

}


4.下载文件

到这一步,我们就可以从服务器上下载文件。


echo'Getting file from server…<br>';

$fp=fopen($localfile,'w');

if(!$success=ftp_fget($conn,$fp,$remotefile,FTP_BINARY))

{

echo'Error:Could not download file';

fclose($fp);

ftp_quit($conn);

exit;

}

fclose($fp);

echo'File downloaded successfully';


正如我们在前面代码所见到的,我们通过函数fopen()打开一个本地文件。在成功打开该文件后,调用函数ftp_fget(),该函数将下载这个文件并存储到本地文件中。ftp_fget()函数带有4个参数。前3个非常简单:FTP连接句柄、本地文件句柄和远程文件路径。第4个参数是FTP模式。

对于FTP传输,有两种模式:ASCII和二进制。ASCII模式用于传输文本文件(也就是说,文件全部由ASCII字符组成),二进制模式用于传输所有其他类型的文件。二进制模式在传输一个文件的过程中不会修改这个文件,而ASCII模式将回车换行字符转换成适用于系统的特定字符(在UNIX下为\n,Windows下为\r\n,而Macintosh下为\r)。

PHP的FTP库有两个预定义的常量,FTP_ASCII和FTP_BINARY,它们分别代表这两种模式。我们需要确定哪种模式适合文件,并将相应的常量作为第4个变量传给函数ftp_fget()。在这个例子中,传输的是一个压缩文件,因此我们使用了FTP_BINARY模式。

如果所有的这些操作都是正常执行,ftp_fget()函数将返回true;如果遇到错误,此函数将返回false。函数执行结果存储在变量$success中,这样用户可以知道此函数的运行情况。

在文件下载完成之后,需要调用函数fclose()关闭本地文件。

除了ftp_fget()函数外,还可以使用函数ftp_get()来实现此目的,此函数具有如下所示的原型:


int ftp_get(int ftp_connection,string localfile_path,

string remotefile_path,int mode)


此函数在许多方面与函数ftp_fget()相同,但是它不需要打开本地文件。传递给此函数的参数为本地文件的系统文件名而不是文件句柄。

请注意,PHP中还没有与FTP命令mget等价的函数,此命令能够一次下载多个文件。我们必须多次调用ftp_fget()函数或者ftp_get()函数来代替mget命令。

5.关闭连接

在完成FTP连接之后,应该通过函数ftp_quit()来关闭此连接。


ftp_quit($conn);


你必须将FTP连接的句柄传递给此函数。