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。

返回值

  • 成功时返回 true
  • 失败时返回 false

示例

示例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参数) 不支持
适用场景 需要自定义处理、大文件上传、从非文件源上传 简单文件上传、小文件传输

常见问题

  1. 文件句柄问题:文件句柄未以正确的模式打开(需读取模式)或已关闭
  2. 权限问题:FTP用户没有写入远程目录的权限
  3. 路径错误:远程文件路径不正确或目录不存在
  4. 磁盘空间:FTP服务器磁盘空间不足
  5. 网络问题:连接中断或超时
  6. 文件锁定:本地文件被其他进程锁定无法读取
  7. 防火墙: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())实现更好的用户体验