PHP ftp_fput() 函数
ftp_fput() 函数将已打开的文件上传到FTP服务器。
注意:与ftp_put()函数不同,ftp_fput()需要一个已经打开的文件句柄作为参数,这允许更灵活的文件处理和上传控制。
语法
ftp_fput(
resource $ftp_stream,
string $remote_file,
resource $handle,
int $mode = FTP_BINARY,
int $startpos = 0
): bool
参数说明
| 参数 |
描述 |
$ftp_stream |
必需。FTP连接的资源标识符,由ftp_connect()或ftp_ssl_connect()函数返回。 |
$remote_file |
必需。远程服务器上文件的路径。 |
$handle |
必需。已打开的本地文件指针,用于读取上传的内容。必须使用fopen()等函数打开,且以读取模式(如"r"、"rb")打开。 |
$mode |
可选。传输模式。可以是:
FTP_ASCII - ASCII文本模式
FTP_BINARY - 二进制模式(默认)
|
$startpos |
可选。远程文件中开始写入的位置(字节偏移量)。默认为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);
// 本地文件
$local_file = "document.pdf";
$remote_file = "public_html/uploads/document.pdf";
// 打开本地文件句柄(读取模式,二进制)
$handle = fopen($local_file, "rb");
if ($handle === false) {
ftp_close($conn_id);
die("无法打开本地文件: $local_file");
}
// 获取文件大小
fseek($handle, 0, SEEK_END);
$file_size = ftell($handle);
rewind($handle);
echo "开始上传文件: $local_file\n";
echo "文件大小: " . formatBytes($file_size) . "\n";
// 上传文件到FTP服务器
if (ftp_fput($conn_id, $remote_file, $handle, FTP_BINARY)) {
echo "文件上传成功: $remote_file\n";
// 验证远程文件大小
$remote_size = ftp_size($conn_id, $remote_file);
if ($remote_size == $file_size) {
echo "文件大小验证通过\n";
} else {
echo "警告: 远程文件大小($remote_size)与本地文件大小($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 uploadTextFile($ftp_server, $ftp_user, $ftp_pass, $local_file, $remote_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, "r");
if (!$handle) {
ftp_close($conn_id);
throw new Exception("无法打开本地文件");
}
try {
// 获取文件内容用于统计
$content = file_get_contents($local_file);
$line_count = substr_count($content, "\n") + 1;
echo "开始上传文本文件...\n";
echo "- 文件路径: $local_file\n";
echo "- 行数: $line_count\n";
echo "- 字符数: " . strlen($content) . "\n";
// 使用ASCII模式上传(自动转换行结束符)
if (!ftp_fput($conn_id, $remote_file, $handle, FTP_ASCII)) {
throw new Exception("文件上传失败");
}
echo "文本文件上传成功\n";
} finally {
// 确保资源被释放
fclose($handle);
ftp_close($conn_id);
}
return true;
}
// 使用示例
try {
uploadTextFile(
"ftp.example.com",
"username",
"password",
"local_script.php",
"public_html/scripts/script.php"
);
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
?>
示例3:断点续传(恢复上传)
<?php
/**
* 支持断点续传的文件上传
*/
function resumeUpload($ftp_server, $ftp_user, $ftp_pass, $local_file, $remote_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);
// 检查本地文件
if (!file_exists($local_file)) {
ftp_close($conn_id);
return ["success" => false, "message" => "本地文件不存在"];
}
$local_size = filesize($local_file);
// 检查远程文件是否存在及大小
$remote_size = ftp_size($conn_id, $remote_file);
if ($remote_size == -1) {
// 文件不存在,从头开始上传
$startpos = 0;
echo "远程文件不存在,开始新上传\n";
} else {
// 文件已存在,检查是否需要续传
if ($remote_size < $local_size) {
$startpos = $remote_size;
echo "发现部分上传的文件,从 $remote_size 字节处继续上传\n";
} elseif ($remote_size == $local_size) {
ftp_close($conn_id);
return ["success" => true, "message" => "文件已完整上传"];
} else {
// 远程文件比本地大,删除重新上传
ftp_delete($conn_id, $remote_file);
$startpos = 0;
echo "远程文件异常,重新上传\n";
}
}
// 打开本地文件
$handle = fopen($local_file, "rb");
if (!$handle) {
ftp_close($conn_id);
return ["success" => false, "message" => "无法打开本地文件"];
}
// 如果是从中间开始上传,移动文件指针
if ($startpos > 0) {
fseek($handle, $startpos);
}
// 执行上传(指定从哪个位置开始)
if (ftp_fput($conn_id, $remote_file, $handle, FTP_BINARY, $startpos)) {
fclose($handle);
ftp_close($conn_id);
$uploaded = $local_size - $startpos;
return [
"success" => true,
"message" => "上传完成",
"total_size" => $local_size,
"uploaded" => $uploaded,
"start_position" => $startpos
];
} else {
fclose($handle);
ftp_close($conn_id);
return ["success" => false, "message" => "上传失败"];
}
}
// 使用示例
$result = resumeUpload(
"ftp.example.com",
"username",
"password",
"large_video.mp4",
"public_html/videos/video.mp4"
);
if ($result['success']) {
echo "上传成功!\n";
echo "本地文件大小: " . formatBytes($result['total_size']) . "\n";
echo "本次上传: " . formatBytes($result['uploaded']) . "\n";
echo "开始位置: " . formatBytes($result['start_position']) . "\n";
} else {
echo "上传失败: " . $result['message'] . "\n";
}
?>
示例4:从字符串或内存中上传数据
<?php
/**
* 将字符串内容作为文件上传到FTP
*/
class StringToFTPUploader {
private $conn;
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);
}
/**
* 上传字符串内容为文件
*/
public function uploadString($content, $remote_file, $mode = FTP_BINARY) {
// 创建临时文件
$temp_file = tempnam(sys_get_temp_dir(), 'ftp_upload_');
$handle = fopen($temp_file, "w+b");
if (!$handle) {
throw new Exception("无法创建临时文件");
}
try {
// 写入内容到临时文件
fwrite($handle, $content);
rewind($handle);
// 上传文件
if (!ftp_fput($this->conn, $remote_file, $handle, $mode)) {
throw new Exception("文件上传失败");
}
echo "字符串内容已成功上传为文件: $remote_file\n";
echo "内容大小: " . strlen($content) . " 字节\n";
} finally {
fclose($handle);
if (file_exists($temp_file)) {
unlink($temp_file);
}
}
return true;
}
/**
* 上传数组为JSON文件
*/
public function uploadArrayAsJson($array, $remote_file) {
$json_content = json_encode($array, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
return $this->uploadString($json_content, $remote_file, FTP_ASCII);
}
/**
* 上传CSV数据
*/
public function uploadArrayAsCSV($data, $remote_file, $headers = null) {
$temp_file = tempnam(sys_get_temp_dir(), 'csv_upload_');
$handle = fopen($temp_file, "w+b");
if (!$handle) {
throw new Exception("无法创建临时文件");
}
try {
if ($headers) {
fputcsv($handle, $headers);
}
foreach ($data as $row) {
fputcsv($handle, $row);
}
rewind($handle);
if (!ftp_fput($this->conn, $remote_file, $handle, FTP_ASCII)) {
throw new Exception("CSV文件上传失败");
}
$row_count = count($data);
echo "CSV文件上传成功: $remote_file ($row_count 行数据)\n";
} finally {
fclose($handle);
unlink($temp_file);
}
return true;
}
/**
* 从URL抓取内容并上传
*/
public function uploadFromUrl($url, $remote_file) {
echo "从URL抓取内容: $url\n";
// 获取URL内容
$content = file_get_contents($url);
if ($content === false) {
throw new Exception("无法从URL获取内容");
}
// 根据内容类型选择模式
$content_type = 'application/octet-stream';
if (function_exists('get_headers')) {
$headers = get_headers($url, 1);
if (isset($headers['Content-Type'])) {
$content_type = is_array($headers['Content-Type']) ?
end($headers['Content-Type']) : $headers['Content-Type'];
}
}
// 判断是否为文本文件
$mode = FTP_BINARY;
if (strpos($content_type, 'text/') === 0 ||
strpos($content_type, 'application/json') !== false ||
strpos($content_type, 'application/xml') !== false) {
$mode = FTP_ASCII;
}
return $this->uploadString($content, $remote_file, $mode);
}
public function __destruct() {
if ($this->conn && is_resource($this->conn)) {
ftp_close($this->conn);
}
}
}
// 使用示例
try {
$uploader = new StringToFTPUploader("ftp.example.com", "username", "password");
// 上传字符串
$uploader->uploadString(
"<?php\nphpinfo();\n?>",
"public_html/info.php",
FTP_ASCII
);
// 上传数组为JSON
$data = [
"name" => "示例数据",
"version" => "1.0",
"items" => [1, 2, 3, 4, 5],
"timestamp" => date('Y-m-d H:i:s')
];
$uploader->uploadArrayAsJson($data, "public_html/data/config.json");
// 上传CSV数据
$csv_data = [
["张三", "男", 28, "工程师"],
["李四", "女", 32, "设计师"],
["王五", "男", 25, "测试员"]
];
$headers = ["姓名", "性别", "年龄", "职位"];
$uploader->uploadArrayAsCSV($csv_data, "public_html/data/staff.csv", $headers);
// 从URL上传
$uploader->uploadFromUrl(
"https://raw.githubusercontent.com/example/sample/master/README.md",
"public_html/docs/readme.md"
);
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
?>
示例5:Web界面多文件上传
<?php
// Web界面文件上传示例
session_start();
// FTP配置
$ftp_config = [
'server' => 'ftp.example.com',
'username' => 'webuser',
'password' => 'password',
'upload_dir' => '/public_html/uploads/'
];
// 处理上传请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload'])) {
$upload_results = [];
if (!empty($_FILES['files']['name'][0])) {
$conn = ftp_connect($ftp_config['server']);
if ($conn && ftp_login($conn, $ftp_config['username'], $ftp_config['password'])) {
ftp_pasv($conn, true);
// 确保上传目录存在
ftp_chdir($conn, $ftp_config['upload_dir']);
foreach ($_FILES['files']['tmp_name'] as $key => $tmp_name) {
$filename = basename($_FILES['files']['name'][$key]);
$remote_path = $filename;
// 检查文件类型
$file_type = $_FILES['files']['type'][$key];
$mode = FTP_BINARY;
if (strpos($file_type, 'text/') === 0 ||
$file_type === 'application/json' ||
$file_type === 'application/xml') {
$mode = FTP_ASCII;
}
// 打开临时文件
$handle = fopen($tmp_name, "rb");
if ($handle) {
if (ftp_fput($conn, $remote_path, $handle, $mode)) {
$upload_results[] = [
'name' => $filename,
'status' => 'success',
'size' => $_FILES['files']['size'][$key],
'message' => '上传成功'
];
} else {
$upload_results[] = [
'name' => $filename,
'status' => 'error',
'message' => '上传失败'
];
}
fclose($handle);
} else {
$upload_results[] = [
'name' => $filename,
'status' => 'error',
'message' => '无法读取文件'
];
}
}
ftp_close($conn);
$_SESSION['upload_results'] = $upload_results;
} else {
$_SESSION['upload_error'] = "FTP连接失败";
}
} else {
$_SESSION['upload_error'] = "没有选择文件";
}
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
}
// 格式化文件大小
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>
.upload-area {
border: 2px dashed #ccc;
border-radius: 10px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
}
.upload-area:hover {
border-color: #0d6efd;
background-color: #f8f9fa;
}
.upload-area.dragover {
border-color: #0d6efd;
background-color: #e7f1ff;
}
.file-list {
max-height: 300px;
overflow-y: auto;
}
.progress {
height: 20px;
}
</style>
</head>
<body>
<div class="container mt-4">
<h1 class="mb-4"><i class="fas fa-cloud-upload-alt me-2"></i>FTP文件上传器</h1>
<?php if (isset($_SESSION['upload_error'])): ?>
<div class="alert alert-danger">
<?php echo $_SESSION['upload_error']; unset($_SESSION['upload_error']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['upload_results'])): ?>
<div class="alert alert-info">
<h5>上传结果</h5>
<ul class="mb-0">
<?php foreach ($_SESSION['upload_results'] as $result): ?>
<li>
<?php echo htmlspecialchars($result['name']); ?> -
<?php if ($result['status'] === 'success'): ?>
<span class="text-success"><i class="fas fa-check me-1"></i>成功</span>
<?php if (isset($result['size'])): ?>
(大小: <?php echo formatFileSize($result['size']); ?>)
<?php endif; ?>
<?php else: ?>
<span class="text-danger"><i class="fas fa-times me-1"></i>失败: <?php echo $result['message']; ?></span>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php unset($_SESSION['upload_results']); ?>
<?php endif; ?>
<div class="card">
<div class="card-header">
<h5 class="mb-0">上传文件到FTP服务器</h5>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data" id="uploadForm">
<input type="hidden" name="upload" value="1">
<div class="mb-3">
<label for="fileInput" class="form-label">选择文件(支持多选)</label>
<div class="upload-area" id="uploadArea">
<i class="fas fa-cloud-upload-alt fa-3x text-muted mb-3"></i>
<p class="mb-2">点击或拖放文件到这里</p>
<p class="text-muted small">支持单个或多个文件上传</p>
<input type="file" class="form-control d-none" id="fileInput" name="files[]" multiple>
</div>
</div>
<div class="mb-3" id="fileListContainer" style="display: none;">
<h6>已选择文件:</h6>
<div class="file-list border rounded p-2" id="fileList"></div>
</div>
<div class="mb-3">
<label for="remoteDir" class="form-label">远程目录</label>
<input type="text" class="form-control" id="remoteDir"
value="<?php echo htmlspecialchars($ftp_config['upload_dir']); ?>" readonly>
<div class="form-text">文件将上传到此目录</div>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="overwrite" name="overwrite">
<label class="form-check-label" for="overwrite">覆盖已存在的文件</label>
</div>
<button type="submit" class="btn btn-primary" id="uploadBtn">
<i class="fas fa-upload me-2"></i>开始上传
</button>
</form>
<!-- 上传进度(客户端模拟) -->
<div id="uploadProgress" class="mt-3" style="display: none;">
<div class="progress mb-2">
<div class="progress-bar progress-bar-striped progress-bar-animated"
id="progressBar" style="width: 0%"></div>
</div>
<p class="text-center mb-0" id="progressText">准备上传...</p>
</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() {
const fileInput = document.getElementById('fileInput');
const uploadArea = document.getElementById('uploadArea');
const fileList = document.getElementById('fileList');
const fileListContainer = document.getElementById('fileListContainer');
const uploadForm = document.getElementById('uploadForm');
const uploadBtn = document.getElementById('uploadBtn');
const uploadProgress = document.getElementById('uploadProgress');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
let selectedFiles = [];
// 点击上传区域触发文件选择
uploadArea.addEventListener('click', function() {
fileInput.click();
});
// 拖放功能
uploadArea.addEventListener('dragover', function(e) {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', function() {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', function(e) {
e.preventDefault();
uploadArea.classList.remove('dragover');
if (e.dataTransfer.files.length) {
fileInput.files = e.dataTransfer.files;
handleFileSelection();
}
});
// 文件选择变化
fileInput.addEventListener('change', handleFileSelection);
function handleFileSelection() {
selectedFiles = Array.from(fileInput.files);
if (selectedFiles.length > 0) {
// 显示文件列表
fileList.innerHTML = '';
selectedFiles.forEach((file, index) => {
const fileItem = document.createElement('div');
fileItem.className = 'd-flex justify-content-between align-items-center mb-2 p-2 border-bottom';
fileItem.innerHTML = `
<div>
<i class="fas fa-file me-2"></i>
<span class="file-name">${file.name}</span>
<small class="text-muted ms-2">(${formatFileSize(file.size)})</small>
</div>
<button type="button" class="btn btn-sm btn-outline-danger remove-btn" data-index="${index}">
<i class="fas fa-times"></i>
</button>
`;
fileList.appendChild(fileItem);
});
fileListContainer.style.display = 'block';
// 添加删除按钮事件
document.querySelectorAll('.remove-btn').forEach(btn => {
btn.addEventListener('click', function() {
const index = parseInt(this.dataset.index);
removeFile(index);
});
});
} else {
fileListContainer.style.display = 'none';
}
}
function removeFile(index) {
// 创建一个新的DataTransfer对象
const dt = new DataTransfer();
// 将除了要删除的文件外的其他文件添加到DataTransfer
selectedFiles.forEach((file, i) => {
if (i !== index) {
dt.items.add(file);
}
});
// 更新文件输入
fileInput.files = dt.files;
// 重新处理文件选择
handleFileSelection();
}
function formatFileSize(bytes) {
if (bytes >= 1073741824) {
return (bytes / 1073741824).toFixed(2) + ' GB';
} else if (bytes >= 1048576) {
return (bytes / 1048576).toFixed(2) + ' MB';
} else if (bytes >= 1024) {
return (bytes / 1024).toFixed(2) + ' KB';
} else {
return bytes + ' 字节';
}
}
// 表单提交
uploadForm.addEventListener('submit', function(e) {
if (selectedFiles.length === 0) {
e.preventDefault();
alert('请选择要上传的文件');
return;
}
// 显示上传进度
uploadProgress.style.display = 'block';
uploadBtn.disabled = true;
// 模拟上传进度
let progress = 0;
const interval = setInterval(() => {
progress += 5;
if (progress <= 95) {
progressBar.style.width = progress + '%';
progressText.textContent = `上传中... ${progress}%`;
} else {
clearInterval(interval);
progressBar.style.width = '100%';
progressText.textContent = '上传完成,正在处理...';
}
}, 100);
});
});
</script>
</body>
</html>
传输模式说明
FTP传输模式:
| 模式常量 |
值 |
描述 |
适用文件类型 |
FTP_ASCII |
1 |
ASCII文本模式,自动转换行结束符 |
.txt, .html, .php, .css, .js, .json, .xml等文本文件 |
FTP_BINARY |
2 |
二进制模式,原样传输字节 |
.jpg, .png, .zip, .exe, .pdf, .mp4等二进制文件 |
注意:使用错误的模式可能导致文件损坏。文本文件使用ASCII模式可以确保行结束符在不同系统间正确转换。
与ftp_put()的区别
| 对比项 |
ftp_fput() |
ftp_put() |
| 输入源 |
已打开的文件指针(resource $handle) |
本地文件路径(string $local_file) |
| 灵活性 |
高(可以处理内存数据、字符串、临时文件等) |
低(只能从文件路径读取) |
| 内存使用 |
可以流式处理大文件,控制内存使用 |
自动处理,可能一次性读取整个文件到内存 |
| 断点续传 |
支持(通过$startpos参数) |
不支持 |
| 适用场景 |
需要自定义处理、大文件上传、从非文件源上传 |
简单文件上传、小文件传输 |
常见问题
- 文件句柄问题:文件句柄未以正确的模式打开(需读取模式)或已关闭
- 权限问题:FTP用户没有写入远程目录的权限
- 路径错误:远程文件路径不正确或目录不存在
- 磁盘空间:FTP服务器磁盘空间不足
- 网络问题:连接中断或超时
- 文件锁定:本地文件被其他进程锁定无法读取
- 防火墙:FTP被动模式被防火墙阻止
<?php
// 根据文件扩展名自动选择传输模式
function getTransferModeByExtension($filename) {
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$text_extensions = [
'txt', 'html', 'htm', 'php', 'css', 'js',
'json', 'xml', 'csv', 'ini', 'conf', 'log',
'md', 'markdown', 'yml', 'yaml'
];
$binary_extensions = [
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico',
'pdf', 'zip', 'rar', '7z', 'tar', 'gz',
'exe', 'dll', 'so', 'dmg', 'iso',
'mp3', 'mp4', 'avi', 'mkv', 'mov',
'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'
];
if (in_array($extension, $text_extensions)) {
return FTP_ASCII;
} elseif (in_array($extension, $binary_extensions)) {
return FTP_BINARY;
} else {
// 默认使用二进制模式
return FTP_BINARY;
}
}
// 根据MIME类型选择传输模式
function getTransferModeByMime($mime_type) {
if (strpos($mime_type, 'text/') === 0 ||
$mime_type === 'application/json' ||
$mime_type === 'application/xml' ||
$mime_type === 'application/javascript') {
return FTP_ASCII;
}
return FTP_BINARY;
}
// 使用示例
$filename = "document.pdf";
$mode1 = getTransferModeByExtension($filename);
$mime_type = "application/pdf";
$mode2 = getTransferModeByMime($mime_type);
echo "文件 {$filename} - 扩展名模式: " . ($mode1 == FTP_ASCII ? "ASCII" : "二进制") . "\n";
echo "MIME类型 {$mime_type} - MIME模式: " . ($mode2 == FTP_ASCII ? "ASCII" : "二进制") . "\n";
?>
- 设置超时时间:使用
ftp_set_option($conn, FTP_TIMEOUT_SEC, 300)增加超时时间
- 使用被动模式:
ftp_pasv($conn, true)避免防火墙问题
- 实现断点续传:使用
$startpos参数支持恢复上传
- 分块上传:将大文件分成多个部分上传
- 错误重试:实现自动重试机制,特别是网络不稳定的环境
- 进度监控:显示上传进度,提高用户体验
- 验证完整性:上传完成后比较本地和远程文件的大小和哈希值
相关函数
| 函数 |
描述 |
ftp_put() |
从本地路径上传文件到FTP服务器 |
ftp_nb_fput() |
非阻塞方式上传已打开的文件到FTP服务器 |
ftp_fget() |
从FTP服务器下载文件到已打开的文件指针 |
fopen() |
打开文件或URL |
fclose() |
关闭已打开的文件指针 |
tempnam() |
创建具有唯一文件名的临时文件 |
最佳实践:
- 使用
ftp_fput()处理大文件上传,避免内存溢出
- 二进制文件使用
FTP_BINARY模式,文本文件使用FTP_ASCII模式
- 上传完成后验证文件完整性(比较大小、计算哈希值)
- 使用临时文件处理敏感数据,上传完成后及时删除
- 实现完善的错误处理和日志记录机制
- 对于公开上传,添加文件类型检查和大小限制
- 考虑使用非阻塞函数(
ftp_nb_fput())实现更好的用户体验