PHP ftp_fget() 函数
ftp_fget() 函数从FTP服务器下载一个文件并写入到已打开的文件指针中。
注意:与ftp_get()函数不同,ftp_fget()需要预先打开一个本地文件句柄,然后将内容写入该句柄。这使得它更适合需要自定义文件处理的场景。
语法
ftp_fget(
resource $ftp_stream,
resource $handle,
string $remote_file,
int $mode = FTP_BINARY,
int $resumepos = 0
): bool
参数说明
| 参数 |
描述 |
$ftp_stream |
必需。FTP连接的资源标识符,由ftp_connect()或ftp_ssl_connect()函数返回。 |
$handle |
必需。已打开的本地文件指针,用于写入下载的内容。必须使用fopen()等函数打开,且以写入模式(如"w"、"wb")打开。 |
$remote_file |
必需。要下载的远程文件路径。 |
$mode |
可选。传输模式。可以是:
FTP_ASCII - ASCII文本模式
FTP_BINARY - 二进制模式(默认)
FTP_AUTORESUME - 自动恢复传输
|
$resumepos |
可选。远程文件中开始下载的位置(字节偏移量)。默认为0。 |
返回值
示例
示例1:基本使用
从FTP服务器下载文件到本地:
<?php
// FTP服务器信息
$ftp_server = "ftp.example.com";
$ftp_user = "username";
$ftp_pass = "password";
// 建立FTP连接
$conn_id = ftp_connect($ftp_server);
if ($conn_id === false) {
die("无法连接到FTP服务器");
}
// 登录FTP服务器
if (!ftp_login($conn_id, $ftp_user, $ftp_pass)) {
ftp_close($conn_id);
die("FTP登录失败");
}
// 开启被动模式
ftp_pasv($conn_id, true);
// 远程文件
$remote_file = "public_html/document.pdf";
$local_file = "downloaded_document.pdf";
// 打开本地文件句柄(写入模式,二进制)
$handle = fopen($local_file, "wb");
if ($handle === false) {
ftp_close($conn_id);
die("无法创建本地文件: $local_file");
}
// 从FTP服务器下载文件
if (ftp_fget($conn_id, $handle, $remote_file, FTP_BINARY)) {
echo "文件下载成功: $local_file\n";
// 获取文件大小
fseek($handle, 0, SEEK_END);
$file_size = ftell($handle);
echo "文件大小: " . formatBytes($file_size) . "\n";
} else {
echo "文件下载失败\n";
}
// 关闭文件句柄
fclose($handle);
// 关闭FTP连接
ftp_close($conn_id);
// 辅助函数:格式化文件大小
function formatBytes($bytes, $precision = 2) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
?>
示例2:下载文本文件(ASCII模式)
<?php
/**
* 下载文本文件并使用ASCII模式转换行结束符
*/
function downloadTextFile($ftp_server, $ftp_user, $ftp_pass, $remote_file, $local_file) {
$conn_id = ftp_connect($ftp_server);
if (!$conn_id) {
throw new Exception("无法连接到FTP服务器");
}
if (!ftp_login($conn_id, $ftp_user, $ftp_pass)) {
ftp_close($conn_id);
throw new Exception("FTP登录失败");
}
ftp_pasv($conn_id, true);
// 打开本地文件(文本模式)
$handle = fopen($local_file, "w");
if (!$handle) {
ftp_close($conn_id);
throw new Exception("无法创建本地文件");
}
try {
// 使用ASCII模式下载(自动转换行结束符)
if (!ftp_fget($conn_id, $handle, $remote_file, FTP_ASCII)) {
throw new Exception("文件下载失败");
}
echo "文本文件下载成功\n";
// 返回到文件开头并读取内容
rewind($handle);
$content = stream_get_contents($handle);
$line_count = substr_count($content, "\n") + 1;
echo "文件信息:\n";
echo "- 文件路径: $local_file\n";
echo "- 行数: $line_count\n";
echo "- 字符数: " . strlen($content) . "\n";
} finally {
// 确保资源被释放
fclose($handle);
ftp_close($conn_id);
}
return true;
}
// 使用示例
try {
downloadTextFile(
"ftp.example.com",
"username",
"password",
"public_html/logs/access.log",
"local_access.log"
);
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
?>
示例3:断点续传(恢复下载)
<?php
/**
* 支持断点续传的文件下载
*/
function resumeDownload($ftp_server, $ftp_user, $ftp_pass, $remote_file, $local_file) {
$conn_id = ftp_connect($ftp_server);
if (!$conn_id) {
return ["success" => false, "message" => "无法连接到FTP服务器"];
}
if (!ftp_login($conn_id, $ftp_user, $ftp_pass)) {
ftp_close($conn_id);
return ["success" => false, "message" => "FTP登录失败"];
}
ftp_pasv($conn_id, true);
// 检查远程文件是否存在
$remote_size = ftp_size($conn_id, $remote_file);
if ($remote_size == -1) {
ftp_close($conn_id);
return ["success" => false, "message" => "远程文件不存在"];
}
// 检查本地文件是否存在及大小
$local_size = 0;
$mode = "wb"; // 默认写二进制模式
if (file_exists($local_file)) {
$local_size = filesize($local_file);
if ($local_size < $remote_size) {
// 部分下载,需要续传
$mode = "ab"; // 追加模式
echo "发现部分下载的文件,从 $local_size 字节处继续下载\n";
} elseif ($local_size == $remote_size) {
ftp_close($conn_id);
return ["success" => true, "message" => "文件已完整下载"];
} else {
// 本地文件比远程大,重新下载
$mode = "wb";
$local_size = 0;
echo "本地文件异常,重新下载\n";
}
}
// 打开文件句柄
$handle = fopen($local_file, $mode);
if (!$handle) {
ftp_close($conn_id);
return ["success" => false, "message" => "无法打开本地文件"];
}
// 如果是从中间开始下载,移动文件指针到末尾
if ($mode == "ab") {
fseek($handle, 0, SEEK_END);
}
// 执行下载(指定从哪个位置开始)
if (ftp_fget($conn_id, $handle, $remote_file, FTP_BINARY, $local_size)) {
fclose($handle);
ftp_close($conn_id);
$final_size = filesize($local_file);
$downloaded = $final_size - $local_size;
return [
"success" => true,
"message" => "下载完成",
"total_size" => $remote_size,
"downloaded" => $downloaded,
"local_size" => $final_size
];
} else {
fclose($handle);
ftp_close($conn_id);
return ["success" => false, "message" => "下载失败"];
}
}
// 使用示例
$result = resumeDownload(
"ftp.example.com",
"username",
"password",
"public_html/large_file.zip",
"local_large_file.zip"
);
if ($result['success']) {
echo "下载成功!\n";
echo "远程文件大小: " . formatBytes($result['total_size']) . "\n";
echo "本次下载: " . formatBytes($result['downloaded']) . "\n";
echo "本地文件大小: " . formatBytes($result['local_size']) . "\n";
} else {
echo "下载失败: " . $result['message'] . "\n";
}
?>
示例4:下载并实时处理文件内容
<?php
/**
* 下载文件并实时处理内容(逐行读取大文件)
*/
class StreamingFTPDownloader {
private $conn;
private $temp_file;
private $temp_handle;
public function __construct($host, $username, $password) {
$this->conn = ftp_connect($host);
if (!$this->conn) {
throw new Exception("无法连接到FTP服务器");
}
if (!ftp_login($this->conn, $username, $password)) {
ftp_close($this->conn);
throw new Exception("FTP登录失败");
}
ftp_pasv($this->conn, true);
// 创建临时文件
$this->temp_file = tempnam(sys_get_temp_dir(), 'ftp_');
$this->temp_handle = fopen($this->temp_file, "w+b");
if (!$this->temp_handle) {
throw new Exception("无法创建临时文件");
}
}
/**
* 下载文件并逐行处理
*/
public function downloadAndProcess($remote_file, callable $processor) {
// 下载文件到临时句柄
if (!ftp_fget($this->conn, $this->temp_handle, $remote_file, FTP_BINARY)) {
throw new Exception("文件下载失败");
}
// 回到文件开头
rewind($this->temp_handle);
$line_number = 0;
$processed_lines = 0;
// 逐行读取并处理
while (!feof($this->temp_handle)) {
$line = fgets($this->temp_handle);
$line_number++;
if ($line !== false) {
$result = $processor($line, $line_number);
if ($result !== false) {
$processed_lines++;
}
}
}
return [
'total_lines' => $line_number,
'processed_lines' => $processed_lines
];
}
/**
* 下载文件并搜索内容
*/
public function downloadAndSearch($remote_file, $search_pattern) {
$results = [];
$processor = function($line, $line_number) use ($search_pattern, &$results) {
if (preg_match($search_pattern, $line)) {
$results[] = [
'line' => $line_number,
'content' => trim($line)
];
return true;
}
return false;
};
$this->downloadAndProcess($remote_file, $processor);
return $results;
}
/**
* 下载并过滤CSV文件
*/
public function downloadAndFilterCSV($remote_file, $filter_callback) {
$filtered_data = [];
$processor = function($line, $line_number) use ($filter_callback, &$filtered_data) {
$data = str_getcsv($line);
if ($line_number === 1) {
// 第一行可能是标题
$filtered_data[] = $data;
return true;
}
if ($filter_callback($data)) {
$filtered_data[] = $data;
return true;
}
return false;
};
$this->downloadAndProcess($remote_file, $processor);
return $filtered_data;
}
public function __destruct() {
if ($this->temp_handle) {
fclose($this->temp_handle);
}
if ($this->temp_file && file_exists($this->temp_file)) {
unlink($this->temp_file);
}
if ($this->conn && is_resource($this->conn)) {
ftp_close($this->conn);
}
}
}
// 使用示例
try {
$downloader = new StreamingFTPDownloader("ftp.example.com", "username", "password");
// 示例1:搜索包含"error"的日志行
echo "搜索错误日志...\n";
$errors = $downloader->downloadAndSearch(
"logs/application.log",
'/error/i'
);
echo "找到 " . count($errors) . " 个错误:\n";
foreach ($errors as $error) {
echo "[行 {$error['line']}] {$error['content']}\n";
}
// 示例2:过滤CSV文件
echo "\n过滤CSV文件...\n";
$filtered = $downloader->downloadAndFilterCSV(
"data/sales.csv",
function($row) {
// 只保留金额大于1000的行
return isset($row[3]) && floatval($row[3]) > 1000;
}
);
echo "过滤后保留 " . count($filtered) . " 行数据\n";
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
?>
示例5:Web界面文件下载
<?php
// Web界面文件下载示例
session_start();
// FTP配置
$ftp_config = [
'server' => 'ftp.example.com',
'username' => 'webuser',
'password' => 'password',
'root_dir' => '/public_html/files/'
];
// 处理下载请求
if (isset($_GET['download']) && !empty($_GET['file'])) {
$remote_file = basename($_GET['file']);
$full_remote_path = $ftp_config['root_dir'] . $remote_file;
$local_filename = $remote_file;
$conn = ftp_connect($ftp_config['server']);
if ($conn && ftp_login($conn, $ftp_config['username'], $ftp_config['password'])) {
ftp_pasv($conn, true);
// 检查文件是否存在
$file_size = ftp_size($conn, $full_remote_path);
if ($file_size != -1) {
// 设置HTTP头
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $local_filename . '"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . $file_size);
// 创建临时文件句柄
$temp_file = tmpfile();
if ($temp_file && ftp_fget($conn, $temp_file, $full_remote_path, FTP_BINARY)) {
// 输出文件内容
rewind($temp_file);
fpassthru($temp_file);
fclose($temp_file);
exit;
}
}
ftp_close($conn);
}
// 如果下载失败,返回错误
header('HTTP/1.1 404 Not Found');
echo "文件下载失败";
exit;
}
// 获取文件列表
$file_list = [];
$conn = ftp_connect($ftp_config['server']);
if ($conn && ftp_login($conn, $ftp_config['username'], $ftp_config['password'])) {
ftp_pasv($conn, true);
$files = ftp_nlist($conn, $ftp_config['root_dir']);
if ($files !== false) {
foreach ($files as $file) {
$filename = basename($file);
if ($filename != '.' && $filename != '..') {
$file_size = ftp_size($conn, $ftp_config['root_dir'] . $filename);
if ($file_size != -1) {
$file_list[] = [
'name' => $filename,
'size' => $file_size,
'formatted_size' => formatFileSize($file_size),
'modified' => ftp_mdtm($conn, $ftp_config['root_dir'] . $filename)
];
}
}
}
}
ftp_close($conn);
}
// 辅助函数
function formatFileSize($bytes) {
if ($bytes >= 1073741824) {
return number_format($bytes / 1073741824, 2) . ' GB';
} elseif ($bytes >= 1048576) {
return number_format($bytes / 1048576, 2) . ' MB';
} elseif ($bytes >= 1024) {
return number_format($bytes / 1024, 2) . ' KB';
} else {
return $bytes . ' 字节';
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FTP文件下载中心</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
.file-item:hover {
background-color: #f8f9fa;
}
.file-icon {
color: #6c757d;
font-size: 1.2em;
}
.download-progress {
display: none;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container mt-4">
<h1 class="mb-4"><i class="fas fa-cloud-download-alt me-2"></i>FTP文件下载中心</h1>
<div class="card">
<div class="card-header">
<h5 class="mb-0">可用文件</h5>
</div>
<div class="card-body">
<?php if (empty($file_list)): ?>
<p class="text-muted">没有可用的文件</p>
<?php else: ?>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>文件名</th>
<th>大小</th>
<th>修改时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($file_list as $file): ?>
<tr class="file-item">
<td>
<i class="fas fa-file file-icon me-2"></i>
<?php echo htmlspecialchars($file['name']); ?>
</td>
<td><?php echo $file['formatted_size']; ?></td>
<td>
<?php echo $file['modified'] ? date('Y-m-d H:i:s', $file['modified']) : '未知'; ?>
</td>
<td>
<a href="?download=1&file=<?php echo urlencode($file['name']); ?>"
class="btn btn-sm btn-primary download-btn"
data-filename="<?php echo htmlspecialchars($file['name']); ?>">
<i class="fas fa-download me-1"></i>下载
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<!-- 下载进度提示 -->
<div id="downloadProgress" class="download-progress">
<div class="alert alert-info">
<h5><i class="fas fa-spinner fa-spin me-2"></i>正在下载...</h5>
<p id="downloadingFile"></p>
<div class="progress mt-2">
<div class="progress-bar progress-bar-striped progress-bar-animated"
style="width: 100%"></div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 下载按钮点击事件
document.querySelectorAll('.download-btn').forEach(function(btn) {
btn.addEventListener('click', function(e) {
var filename = this.dataset.filename;
document.getElementById('downloadingFile').textContent =
'正在下载: ' + filename;
document.getElementById('downloadProgress').style.display = 'block';
// 滚动到进度提示
document.getElementById('downloadProgress').scrollIntoView({
behavior: 'smooth'
});
});
});
// 如果URL中有下载参数,显示进度
if (window.location.search.includes('download=1')) {
document.getElementById('downloadProgress').style.display = 'block';
}
});
</script>
</body>
</html>
传输模式说明
FTP传输模式:
| 模式常量 |
值 |
描述 |
适用文件类型 |
FTP_ASCII |
1 |
ASCII文本模式,自动转换行结束符 |
.txt, .html, .php, .css, .js等文本文件 |
FTP_BINARY |
2 |
二进制模式,原样传输字节 |
.jpg, .png, .zip, .exe, .pdf等二进制文件 |
FTP_AUTORESUME |
-1 |
自动恢复模式,尝试从断点续传 |
大文件下载,不稳定网络环境 |
与ftp_get()的区别
| 对比项 |
ftp_fget() |
ftp_get() |
| 文件处理 |
写入到已打开的文件指针 |
直接写入到指定路径的文件 |
| 灵活性 |
高(可以控制文件打开方式) |
低(自动创建文件) |
| 内存使用 |
可以流式处理大文件 |
需要足够磁盘空间 |
| 断点续传 |
支持(通过$resumepos参数) |
不支持 |
| 适用场景 |
需要自定义处理、大文件下载、实时处理 |
简单文件下载、小文件传输 |
常见问题
- 文件句柄问题:文件句柄未以正确的模式打开(需写入模式)
- 权限问题:FTP用户没有读取远程文件的权限
- 路径错误:远程文件路径不正确或文件不存在
- 网络问题:连接中断或超时
- 磁盘空间:本地磁盘空间不足
- 防火墙:FTP被动模式被防火墙阻止
<?php
// 根据文件扩展名选择传输模式
function getTransferMode($filename) {
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$text_extensions = ['txt', 'html', 'htm', 'php', 'css', 'js', 'json', 'xml', 'csv'];
$binary_extensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'zip', 'rar', 'exe', 'doc', 'xls'];
if (in_array($extension, $text_extensions)) {
return FTP_ASCII;
} elseif (in_array($extension, $binary_extensions)) {
return FTP_BINARY;
} else {
// 默认使用二进制模式
return FTP_BINARY;
}
}
// 使用示例
$filename = "document.pdf";
$mode = getTransferMode($filename);
echo "文件 {$filename} 使用模式: " . ($mode == FTP_ASCII ? "ASCII" : "二进制") . "\n";
?>
- 设置超时时间:使用
ftp_set_option($conn, FTP_TIMEOUT_SEC, 300)增加超时
- 使用被动模式:
ftp_pasv($conn, true)避免防火墙问题
- 实现断点续传:使用
$resumepos参数
- 分块下载:将大文件分成多个部分下载
- 错误重试:实现自动重试机制
- 进度监控:显示下载进度,提高用户体验
相关函数
| 函数 |
描述 |
ftp_get() |
从FTP服务器下载文件到本地路径 |
ftp_put() |
上传文件到FTP服务器 |
ftp_nb_fget() |
非阻塞方式从FTP服务器下载文件到文件指针 |
fopen() |
打开文件或URL |
fclose() |
关闭已打开的文件指针 |
最佳实践:
- 使用
ftp_fget()处理大文件,避免内存溢出
- 二进制文件使用
FTP_BINARY模式,文本文件使用FTP_ASCII模式
- 下载完成后检查文件完整性(如比较文件大小)
- 使用临时文件处理敏感数据,处理完成后删除
- 实现错误处理和日志记录机制
- 对于公开下载,考虑添加下载次数统计