
本文旨在解决php使用`ssh2`扩展通过`ssh2.sftp://` uri从sftp服务器下载文件时,本地文件为空的问题。文章将详细解释导致此问题的原因,并提供两种有效的解决方案:一是利用`stream_copy_to_stream`函数进行流数据复制,二是推荐使用更简洁高效的`ssh2_scp_recv`函数(若服务器支持scp),确保文件内容能够正确传输到本地。
在使用PHP的ssh2扩展与SFTP服务器交互时,开发者可能会遇到一个常见问题:通过ssh2.sftp:// URI打开远程文件流并尝试保存到本地时,最终得到的本地文件却是空的。这通常是由于误解了fopen函数在此上下文中的行为所致。
原始代码片段如下:
$connection = ssh2_connect('{my server ip}', 22);
ssh2_auth_password($connection, '{user}', '{password}');
$sftp = ssh2_sftp($connection);
$file = "test.txt"; // 本地文件名
$remote_path = "/public_html/$file"; // 远程文件路径
$target_path = "ssh2.sftp://$sftp" . $remote_path; // 远程文件URI
$stream = fopen($target_path, 'r'); // 打开远程文件流
fopen($file, "w"); // 打开本地文件用于写入
fclose($stream); // 关闭远程文件流这段代码的问题在于,它确实成功打开了远程文件作为可读流($stream),也成功创建了一个本地文件用于写入。然而,它从未执行将数据从远程流传输到本地文件的操作。fopen函数本身并不会进行数据复制,它只是建立了一个文件句柄或流。因此,在没有数据传输的情况下,本地文件自然会保持为空。
要解决上述问题,我们需要明确地将数据从打开的远程流复制到打开的本地流。PHP提供了stream_copy_to_stream函数,专门用于此目的。
立即学习“PHP免费学习笔记(深入)”;
stream_copy_to_stream(resource $source, resource $dest, ?int $maxlength = null, int $offset = 0): int|false
这个函数将从$source流中读取数据,并将其写入$dest流中,直到$source流到达末尾,或者复制了$maxlength字节的数据。
以下是使用stream_copy_to_stream的正确实现:
<?php
// 假设这些变量已正确配置
$server_ip = '{my server ip}';
$username = '{user}';
$password = '{password}';
$remote_file_name = "test.txt"; // 远程服务器上的文件名
$local_file_name = "downloaded_test.txt"; // 本地保存的文件名
$remote_base_path = "/public_html"; // 远程文件所在的目录
// 1. 建立SSH连接
$connection = ssh2_connect($server_ip, 22);
if (!$connection) {
die("无法连接到服务器。\n");
}
// 2. 进行身份验证
if (!ssh2_auth_password($connection, $username, $password)) {
die("身份验证失败。\n");
}
// 3. 获取SFTP子系统
$sftp = ssh2_sftp($connection);
if (!$sftp) {
die("无法初始化SFTP子系统。\n");
}
// 4. 构建远程文件的完整SFTP URI
$remote_full_path = $remote_base_path . '/' . $remote_file_name;
$sftp_uri = "ssh2.sftp://$sftp" . $remote_full_path;
// 5. 打开远程文件流(只读)
$remote_stream = @fopen($sftp_uri, 'r');
if (!$remote_stream) {
die("无法打开远程文件: " . $remote_full_path . "\n");
}
// 6. 打开本地文件流(写入模式)
$local_stream = @fopen($local_file_name, 'w');
if (!$local_stream) {
fclose($remote_stream); // 确保关闭已打开的远程流
die("无法创建本地文件: " . $local_file_name . "\n");
}
// 7. 将数据从远程流复制到本地流
$bytes_copied = stream_copy_to_stream($remote_stream, $local_stream);
// 8. 关闭所有流
fclose($remote_stream);
fclose($local_stream);
if ($bytes_copied !== false) {
echo "文件 '" . $remote_file_name . "' 已成功下载到本地 '" . $local_file_name . "',共复制了 " . $bytes_copied . " 字节。\n";
} else {
echo "文件下载失败。\n";
}
?>代码解析:
如果你的服务器同时支持SCP(Secure Copy Protocol),那么PHP的ssh2扩展提供了一个更简洁、更直接的函数ssh2_scp_recv来下载文件。SCP通常与SFTP一同提供,并且在许多情况下,它能提供更快的传输速度。
ssh2_scp_recv(resource $session, string $remote_file, string $local_file): bool
这个函数直接将远程文件复制到本地文件,无需手动处理流。
以下是使用ssh2_scp_recv的示例:
<?php
// 假设这些变量已正确配置
$server_ip = '{my server ip}';
$username = '{user}';
$password = '{password}';
$remote_file_name = "test.txt"; // 远程服务器上的文件名
$local_file_name = "downloaded_test.txt"; // 本地保存的文件名
$remote_base_path = "/public_html"; // 远程文件所在的目录
// 1. 建立SSH连接
$connection = ssh2_connect($server_ip, 22);
if (!$connection) {
die("无法连接到服务器。\n");
}
// 2. 进行身份验证
if (!ssh2_auth_password($connection, $username, $password)) {
die("身份验证失败。\n");
}
// 3. 构建远程文件的完整路径
$remote_full_path = $remote_base_path . '/' . $remote_file_name;
// 4. 使用ssh2_scp_recv直接下载文件
if (ssh2_scp_recv($connection, $remote_full_path, $local_file_name)) {
echo "文件 '" . $remote_file_name . "' 已成功通过SCP下载到本地 '" . $local_file_name . "'.\n";
} else {
echo "文件SCP下载失败。\n";
}
// 注意:ssh2_scp_recv内部处理了文件的打开和关闭,无需手动fclose。
// 如果不再需要SSH连接,可以关闭它,尽管PHP脚本结束时会自动清理。
// ssh2_disconnect($connection); // PHP 8.0+ 可用
?>代码解析:
注意事项:
当使用PHP的ssh2扩展从SFTP服务器下载文件时,切记fopen仅是打开文件流,并不会自动传输数据。为了确保文件内容能够正确下载到本地,你需要:
在实际应用中,建议优先考虑ssh2_scp_recv,因为它代码量少、易于理解和维护。如果遇到SCP不支持的情况,再回退到stream_copy_to_stream方法。同时,务必加入适当的错误处理机制,以应对连接失败、认证失败或文件不存在等异常情况。
以上就是PHP SFTP文件传输:解决ssh2.sftp://下载空文件问题的详细内容,更多请关注php中文网其它相关文章!
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号