在 PHP 中,使用 fsockopen() 函数来实现 FTP 文件上传的过程相对来说比较低级,涉及到对 FTP 协议的手动操作。虽然 PHP 提供了 ftp_* 系列的函数来简化 FTP 上传的过程,但是有时候你可能会遇到特殊需求,或者为了更细粒度地控制上传流程,选择直接通过 fsockopen() 来实现。本文将详细介绍如何通过 fsockopen() 实现 FTP 文件上传,并指出一些需要特别注意的关键点。
FTP(File Transfer Protocol)是一种常用的网络协议,用于在客户端与服务器之间传输文件。使用 fsockopen() 函数,可以在 PHP 中打开一个到 FTP 服务器的套接字连接,从而与 FTP 服务器进行数据交互,执行上传、下载等操作。
建立连接
首先,我们需要使用 fsockopen() 打开到 FTP 服务器的连接。通常,FTP 服务器的默认端口是 21。
<span><span><span class="hljs-variable">$ftp_server</span></span><span> = </span><span><span class="hljs-string">'ftp.example.com'</span></span><span>;
</span><span><span class="hljs-variable">$ftp_port</span></span><span> = </span><span><span class="hljs-number">21</span></span><span>;
</span><span><span class="hljs-variable">$ftp_user</span></span><span> = </span><span><span class="hljs-string">'username'</span></span><span>;
</span><span><span class="hljs-variable">$ftp_pass</span></span><span> = </span><span><span class="hljs-string">'password'</span></span><span>;
</span><span><span class="hljs-comment">// 创建到FTP服务器的连接</span></span><span>
</span><span><span class="hljs-variable">$ftp_socket</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fsockopen</span></span><span>(</span><span><span class="hljs-variable">$ftp_server</span></span><span>, </span><span><span class="hljs-variable">$ftp_port</span></span><span>, </span><span><span class="hljs-variable">$errno</span></span><span>, </span><span><span class="hljs-variable">$errstr</span></span><span>, </span><span><span class="hljs-number">30</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-variable">$ftp_socket</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"FTP连接失败: <span class="hljs-subst">$errstr</span></span></span><span> (</span><span><span class="hljs-subst">$errno</span></span><span>)");
}
</span></span>
接收服务器响应
FTP 通常会在连接建立时发送一个欢迎消息。我们需要读取并检查服务器的响应,确保成功连接。
<span><span><span class="hljs-variable">$response</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fgets</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">substr</span></span><span>(</span><span><span class="hljs-variable">$response</span></span><span>, </span><span><span class="hljs-number">0</span></span><span>, </span><span><span class="hljs-number">3</span></span><span>) != </span><span><span class="hljs-string">'220'</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"连接失败: <span class="hljs-subst">$response</span></span></span><span>");
}
</span></span>
发送用户名和密码
在 FTP 协议中,登录过程是通过发送用户名(USER)和密码(PASS)命令来完成的。我们可以使用 fputs() 函数发送这些命令。
<span><span><span class="hljs-comment">// 发送用户名</span></span><span>
</span><span><span class="hljs-title function_ invoke__">fputs</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-string">"USER <span class="hljs-subst">$ftp_user</span></span></span><span>\r\n");
</span><span><span class="hljs-variable">$response</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fgets</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">substr</span></span><span>(</span><span><span class="hljs-variable">$response</span></span><span>, </span><span><span class="hljs-number">0</span></span><span>, </span><span><span class="hljs-number">3</span></span><span>) != </span><span><span class="hljs-string">'331'</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"用户名错误: <span class="hljs-subst">$response</span></span></span><span>");
}
</span><span><span class="hljs-comment">// 发送密码</span></span><span>
</span><span><span class="hljs-title function_ invoke__">fputs</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-string">"PASS <span class="hljs-subst">$ftp_pass</span></span></span><span>\r\n");
</span><span><span class="hljs-variable">$response</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fgets</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">substr</span></span><span>(</span><span><span class="hljs-variable">$response</span></span><span>, </span><span><span class="hljs-number">0</span></span><span>, </span><span><span class="hljs-number">3</span></span><span>) != </span><span><span class="hljs-string">'230'</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"密码错误: <span class="hljs-subst">$response</span></span></span><span>");
}
</span></span>
设置被动模式(可选)
在某些网络环境中,使用被动模式(PASV)更为稳定。被动模式会在 FTP 服务器端开启一个新的端口,客户端连接该端口进行数据传输。
<span><span><span class="hljs-comment">// 进入被动模式</span></span><span>
</span><span><span class="hljs-title function_ invoke__">fputs</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-string">"PASV\r\n"</span></span><span>);
</span><span><span class="hljs-variable">$response</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fgets</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">substr</span></span><span>(</span><span><span class="hljs-variable">$response</span></span><span>, </span><span><span class="hljs-number">0</span></span><span>, </span><span><span class="hljs-number">3</span></span><span>) != </span><span><span class="hljs-string">'227'</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"进入被动模式失败: <span class="hljs-subst">$response</span></span></span><span>");
}
</span></span>
服务器返回的响应中会包含一个数据端口的信息,您需要解析这个响应来获得数据端口。
上传文件
要上传文件,需要先发送 STOR 命令指定文件的上传位置,然后将文件内容逐块传输给服务器。
<span><span><span class="hljs-variable">$local_file</span></span><span> = </span><span><span class="hljs-string">'local_file.txt'</span></span><span>;
</span><span><span class="hljs-variable">$remote_file</span></span><span> = </span><span><span class="hljs-string">'remote_file.txt'</span></span><span>;
</span><span><span class="hljs-comment">// 打开本地文件</span></span><span>
</span><span><span class="hljs-variable">$file</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fopen</span></span><span>(</span><span><span class="hljs-variable">$local_file</span></span><span>, </span><span><span class="hljs-string">'rb'</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-variable">$file</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"无法打开本地文件: <span class="hljs-subst">$local_file</span></span></span><span>");
}
</span><span><span class="hljs-comment">// 发送 STOR 命令开始上传</span></span><span>
</span><span><span class="hljs-title function_ invoke__">fputs</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-string">"STOR <span class="hljs-subst">$remote_file</span></span></span><span>\r\n");
</span><span><span class="hljs-variable">$response</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fgets</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">substr</span></span><span>(</span><span><span class="hljs-variable">$response</span></span><span>, </span><span><span class="hljs-number">0</span></span><span>, </span><span><span class="hljs-number">3</span></span><span>) != </span><span><span class="hljs-string">'150'</span></span><span>) {
</span><span><span class="hljs-keyword">die</span></span><span>(</span><span><span class="hljs-string">"开始上传失败: <span class="hljs-subst">$response</span></span></span><span>");
}
</span><span><span class="hljs-comment">// 逐块上传文件内容</span></span><span>
</span><span><span class="hljs-keyword">while</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">feof</span></span><span>(</span><span><span class="hljs-variable">$file</span></span><span>)) {
</span><span><span class="hljs-variable">$data</span></span><span> = </span><span><span class="hljs-title function_ invoke__">fread</span></span><span>(</span><span><span class="hljs-variable">$file</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">fputs</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-variable">$data</span></span><span>);
}
</span><span><span class="hljs-title function_ invoke__">fclose</span></span><span>(</span><span><span class="hljs-variable">$file</span></span><span>);
</span></span>
关闭连接
上传完文件后,关闭 FTP 连接。
<span><span><span class="hljs-title function_ invoke__">fputs</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>, </span><span><span class="hljs-string">"QUIT\r\n"</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">fclose</span></span><span>(</span><span><span class="hljs-variable">$ftp_socket</span></span><span>);
</span></span>
错误处理
FTP 协议涉及多个命令和响应,所以错误处理非常重要。每个 FTP 命令的响应都包含一个三位数的状态码(如 220, 230, 331 等),通过检查这些状态码,可以确定操作是否成功。
被动模式与主动模式
在某些网络环境下,使用被动模式可以避免 NAT(网络地址转换)设备和防火墙的阻塞。如果默认模式不适合,记得切换到被动模式。
二进制与ASCII模式
上传文件时,需要确保文件传输模式的正确设置。对于二进制文件(如图片、视频、压缩文件等),应该使用二进制模式(即 TYPE I)。对于文本文件,使用 ASCII 模式(即 TYPE A)。
缓冲区大小
在文件上传过程中,读取和写入操作时,可以通过调整缓冲区大小来提高上传性能。fread() 和 fputs() 的缓冲区大小会影响数据传输的速度。
防止资源泄露
在通过 fsockopen() 创建套接字连接后,务必记得在上传完成后关闭连接,避免资源泄漏。
通过 fsockopen() 实现 FTP 文件上传虽然相对底层,但它给予开发者更多控制文件传输的能力。通过手动实现 FTP 协议的每个步骤,我们可以在复杂的场景下进行定制化操作。然而,使用时需要注意协议的细节、错误处理、传输模式等问题,确保上传过程的顺利进行。对于常规的文件上传需求,可以使用 PHP 内置的 FTP 函数来简化操作,而通过 fsockopen() 进行定制化的上传操作则适合需要更细致控制的场景。
相关标签:
FTP