FTP 和 SFTP 介绍及 C/C++ 实现分析

news2025/6/7 2:18:34

1. FTP 协议概述

        FTP(File Transfer Protocol)是一种用于在网络上进行文件传输的标准协议,诞生于 1971 年,是互联网上最早的应用层协议之一。它基于客户端 - 服务器模型,使用 TCP 作为传输层协议,默认通过 20 端口传输数据、21 端口传输控制命令。

        FTP 工作模式分为主动模式(Active Mode)和被动模式(Passive Mode):

  • 主动模式:客户端打开一个随机端口(>1023)向服务器的 21 端口发送连接请求,建立控制连接。当需要传输数据时,客户端在控制连接上发送 PORT 命令告知服务器自己的数据端口,服务器主动连接客户端的数据端口进行数据传输。
  • 被动模式:客户端打开一个随机端口与服务器的 21 端口建立控制连接。当需要传输数据时,客户端发送 PASV 命令,服务器返回一个随机的数据端口,客户端主动连接该端口进行数据传输。

        FTP 协议的主要缺点是缺乏安全性,所有数据(包括用户名、密码)都以明文形式传输,容易被窃听和中间人攻击。

2. SFTP 协议概述

        SFTP(SSH File Transfer Protocol)是一种基于 SSH 协议的安全文件传输协议,它在 SSH 协议的基础上提供了文件传输功能。与 FTP 不同,SFTP 不依赖于独立的端口,而是使用 SSH 的 22 端口(或其他自定义 SSH 端口)进行所有通信,所有数据都经过加密处理。

        SFTP 的主要优点:

  • 安全性高:基于 SSH 协议,使用加密通道传输数据,防止数据被窃听和篡改。
  • 集成认证:使用 SSH 的认证机制,无需额外的用户名密码管理。
  • 防火墙友好:只需要开放 SSH 端口(通常是 22),简化了网络配置。

        SFTP 与 FTP 的主要区别在于底层传输协议和安全性,SFTP 提供了更安全的文件传输环境,但性能可能略低于 FTP。

3. C/C++ 实现 FTP 客户端

        在 C/C++ 中实现 FTP 客户端需要使用 socket 编程来与 FTP 服务器进行通信。以下是一个简单的 C++ 实现框架:

#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

class FTPClient {
private:
    int controlSocket;
    std::string server;
    int port;
    std::string username;
    std::string password;

public:
    FTPClient(const std::string& server, int port = 21) 
        : server(server), port(port), controlSocket(-1) {}

    ~FTPClient() {
        disconnect();
    }

    bool connect() {
        controlSocket = socket(AF_INET, SOCK_STREAM, 0);
        if (controlSocket == -1) {
            std::cerr << "Failed to create socket" << std::endl;
            return false;
        }

        sockaddr_in serverAddr;
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_port = htons(port);
        serverAddr.sin_addr.s_addr = inet_addr(server.c_str());

        if (::connect(controlSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
            std::cerr << "Failed to connect to server" << std::endl;
            close(controlSocket);
            controlSocket = -1;
            return false;
        }

        std::string response = receiveResponse();
        if (response.substr(0, 3) != "220") {
            std::cerr << "Unexpected response from server: " << response << std::endl;
            disconnect();
            return false;
        }

        return true;
    }

    void disconnect() {
        if (controlSocket != -1) {
            close(controlSocket);
            controlSocket = -1;
        }
    }

    bool login(const std::string& username, const std::string& password) {
        this->username = username;
        this->password = password;

        sendCommand("USER " + username);
        std::string response = receiveResponse();
        if (response.substr(0, 3) != "331") {
            std::cerr << "Invalid username: " << response << std::endl;
            return false;
        }

        sendCommand("PASS " + password);
        response = receiveResponse();
        if (response.substr(0, 3) != "230") {
            std::cerr << "Invalid password: " << response << std::endl;
            return false;
        }

        return true;
    }

    bool getFile(const std::string& remoteFile, const std::string& localFile) {
        // 实现文件下载逻辑
        // 1. 建立数据连接(主动或被动模式)
        // 2. 发送RETR命令
        // 3. 接收文件数据
        // 4. 关闭数据连接
        return true;
    }

    bool putFile(const std::string& localFile, const std::string& remoteFile) {
        // 实现文件上传逻辑
        // 1. 建立数据连接
        // 2. 发送STOR命令
        // 3. 发送文件数据
        // 4. 关闭数据连接
        return true;
    }

private:
    void sendCommand(const std::string& command) {
        std::string cmd = command + "\r\n";
        send(controlSocket, cmd.c_str(), cmd.length(), 0);
    }

    std::string receiveResponse() {
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));
        int bytes = recv(controlSocket, buffer, sizeof(buffer) - 1, 0);
        if (bytes > 0) {
            return std::string(buffer);
        }
        return "";
    }
};

        这个框架实现了 FTP 客户端的基本功能,包括连接、登录和文件传输的基本结构。实际实现中,还需要处理更多细节,如:

  1. 解析服务器响应代码
  2. 实现主动和被动模式的数据连接
  3. 处理文件传输的二进制和 ASCII 模式
  4. 实现目录操作(LIST、CWD 等)
  5. 添加错误处理和超时机制
4. C/C++ 实现 SFTP 客户端

在 C/C++ 中实现 SFTP 客户端通常需要使用第三方库,因为 SFTP 协议基于 SSH,实现复杂度较高。常用的库有:

  • libssh2:一个功能齐全的 SSH2 协议实现库,支持 SFTP 功能。
  • libssh:另一个 SSH 协议实现库,提供了更高级的 API。

以下是使用 libssh2 实现 SFTP 客户端的示例框架:

#include <iostream>
#include <string>
#include <libssh2.h>
#include <libssh2_sftp.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

class SFTPClient {
private:
    int socket;
    LIBSSH2_SESSION *session;
    LIBSSH2_SFTP *sftp_session;
    std::string server;
    int port;
    std::string username;
    std::string password;

public:
    SFTPClient(const std::string& server, int port = 22) 
        : server(server), port(port), socket(-1), session(nullptr), sftp_session(nullptr) {
        // 初始化libssh2库
        libssh2_init(0);
    }

    ~SFTPClient() {
        disconnect();
        // 清理libssh2库
        libssh2_exit();
    }

    bool connect() {
        // 创建TCP连接
        socket = ::socket(AF_INET, SOCK_STREAM, 0);
        if (socket == -1) {
            std::cerr << "Failed to create socket" << std::endl;
            return false;
        }

        sockaddr_in serverAddr;
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_port = htons(port);
        serverAddr.sin_addr.s_addr = inet_addr(server.c_str());

        if (::connect(socket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
            std::cerr << "Failed to connect to server" << std::endl;
            close(socket);
            socket = -1;
            return false;
        }

        // 初始化SSH会话
        session = libssh2_session_init();
        if (!session) {
            std::cerr << "Failed to initialize SSH session" << std::endl;
            disconnect();
            return false;
        }

        // 启动SSH连接
        if (libssh2_session_handshake(session, socket)) {
            std::cerr << "SSH handshake failed" << std::endl;
            disconnect();
            return false;
        }

        return true;
    }

    void disconnect() {
        // 关闭SFTP会话
        if (sftp_session) {
            libssh2_sftp_shutdown(sftp_session);
            sftp_session = nullptr;
        }

        // 关闭SSH会话
        if (session) {
            libssh2_session_disconnect(session, "Normal shutdown");
            libssh2_session_free(session);
            session = nullptr;
        }

        // 关闭TCP连接
        if (socket != -1) {
            close(socket);
            socket = -1;
        }
    }

    bool login(const std::string& username, const std::string& password) {
        this->username = username;
        this->password = password;

        // 进行密码认证
        if (libssh2_userauth_password(session, username.c_str(), password.c_str())) {
            std::cerr << "Authentication failed" << std::endl;
            return false;
        }

        // 初始化SFTP会话
        sftp_session = libssh2_sftp_init(session);
        if (!sftp_session) {
            std::cerr << "SFTP initialization failed" << std::endl;
            return false;
        }

        return true;
    }

    bool getFile(const std::string& remoteFile, const std::string& localFile) {
        // 打开远程文件
        LIBSSH2_SFTP_HANDLE *sftp_handle = libssh2_sftp_open(
            sftp_session, remoteFile.c_str(), LIBSSH2_FXF_READ, 0);
        if (!sftp_handle) {
            std::cerr << "Failed to open remote file" << std::endl;
            return false;
        }

        // 打开本地文件
        FILE *local_file = fopen(localFile.c_str(), "wb");
        if (!local_file) {
            std::cerr << "Failed to open local file" << std::endl;
            libssh2_sftp_close(sftp_handle);
            return false;
        }

        // 读取远程文件并写入本地文件
        char buffer[1024];
        int rc;
        while ((rc = libssh2_sftp_read(sftp_handle, buffer, sizeof(buffer))) > 0) {
            fwrite(buffer, 1, rc, local_file);
        }

        // 关闭文件
        fclose(local_file);
        libssh2_sftp_close(sftp_handle);

        return true;
    }

    bool putFile(const std::string& localFile, const std::string& remoteFile) {
        // 打开本地文件
        FILE *local_file = fopen(localFile.c_str(), "rb");
        if (!local_file) {
            std::cerr << "Failed to open local file" << std::endl;
            return false;
        }

        // 获取文件大小
        fseek(local_file, 0, SEEK_END);
        size_t file_size = ftell(local_file);
        rewind(local_file);

        // 打开远程文件
        LIBSSH2_SFTP_HANDLE *sftp_handle = libssh2_sftp_open(
            sftp_session, remoteFile.c_str(), 
            LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
            LIBSSH2_SFTP_S_IRUSR | LIBSSH2_SFTP_S_IWUSR |
            LIBSSH2_SFTP_S_IRGRP | LIBSSH2_SFTP_S_IROTH);
        if (!sftp_handle) {
            std::cerr << "Failed to open remote file" << std::endl;
            fclose(local_file);
            return false;
        }

        // 读取本地文件并写入远程文件
        char buffer[1024];
        size_t total_sent = 0;
        while (total_sent < file_size) {
            size_t to_send = (file_size - total_sent) < sizeof(buffer) ? 
                             (file_size - total_sent) : sizeof(buffer);
            size_t bytes_read = fread(buffer, 1, to_send, local_file);
            
            if (bytes_read <= 0) break;
            
            int rc = libssh2_sftp_write(sftp_handle, buffer, bytes_read);
            if (rc < 0) {
                std::cerr << "Error writing to remote file" << std::endl;
                break;
            }
            
            total_sent += bytes_read;
        }

        // 关闭文件
        fclose(local_file);
        libssh2_sftp_close(sftp_handle);

        return total_sent == file_size;
    }
};
5. 实现注意事项和优化建议

        在实现 FTP 和 SFTP 客户端时,需要注意以下几点:

  1. 错误处理:网络操作容易出错,需要完善的错误处理机制,包括超时处理、连接断开重连等。

  2. 线程安全:在多线程环境中使用时,需要考虑线程安全问题,特别是 libssh2 库不是线程安全的,需要适当的同步机制。

  3. 性能优化:对于大文件传输,可以考虑使用异步 I/O 或多线程来提高性能。

  4. 安全性:在处理敏感信息(如密码)时,需要注意内存安全,避免信息泄露。

  5. 跨平台兼容性:确保代码在不同操作系统上都能正常工作,注意处理不同系统的路径分隔符差异等问题。

  6. 日志和调试:添加详细的日志记录功能,方便调试和问题排查。

        总之,实现 FTP 和 SFTP 客户端是一个复杂的任务,特别是 SFTP 由于涉及加密和 SSH 协议,推荐使用成熟的第三方库来简化开发。在选择库时,需要考虑功能完整性、性能、稳定性和社区支持等因素。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2400896.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

leetcode hot100刷题日记——36.最长连续序列

解答&#xff1a; 实际上在哈希表中存储不重复的数字。 然后遍历哈希表&#xff0c;找间隔&#xff0c;更新最大间隔。 class Solution { public:int longestConsecutive(vector<int>& nums) {unordered_set<int>hash;for(int num:nums){hash.insert(num);}in…

CentOS7关闭防火墙、Linux开启关闭防火墙

文章目录 一、firewalld开启、关闭防火墙1、查看防火墙状态 一、firewalld开启、关闭防火墙 以下命令在linux系统CentOS7中操作开启关闭防火墙 # 查询防火墙状态 systemctl status firewalld.service # 开启防火墙 systemctl start firewalld.service # 开机自启动防火墙 syste…

PyTorch——搭建小实战和Sequential的使用(7)

import torch from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linearclass TY(nn.Module):def __init__(self):"""初始化TY卷积神经网络模型模型结构&#xff1a;3层卷积池化&#xff0c;2层全连接设计目标&#xff1a;处理32x32像素的…

Python 开发效率秘籍:PyCharm、VS Code 与 Anaconda 配置与实战全解

目录 一、IDE(集成开发环境)是什么?二、Python IDE有哪些&#xff0c;哪款适合初学者&#xff1f;三、Visual Studio Code下载和安装教程3.1 VS Code下载和安装3.2 VS Code运行Python程序 四、PyCharm下载和安装教程4.1 PyCharm下载4.2 PyCharm安装4.3 运行PyCharm4.4 创建工程…

大话软工笔记—组合要素1之要素

1. 要素来源 对象是要素的来源&#xff0c;要素是从对象分解而来的。可将对象分为优化类和非优化类&#xff0c;如下图所示。 对象分类图 2. 要素的概念 2.1 要素的定义 要素&#xff0c;是构成事物必不可少的因素&#xff0c;要素的集合体构成了对象。 2.2 要素的内容 要…

平台化 LIMS 系统架构 跨行业协同与资源共享的实现路径

在科技快速发展的今天&#xff0c;质检行业正面临着效率、合规和数据安全的多重挑战。新一代质检 LIMS 系统以智能化与平台化为核心&#xff0c;为实验室管理提供了全新的解决方案。 一、智能化&#xff1a;从数据采集到分析的全流程升级 传统质检流程中&#xff0c;人工数据录…

RedisTemplate查询不到redis中的数据问题(序列化)

RedisTemplate查询不到redis中的数据问题(序列化) 一.问题描述 存入Redis中的值取出来却为null,问题根本原因就是RedisTemplate和StringRedisTemplate的序列化问题、代码示例&#xff1a; SpringBootTest class Redis02SpringbootApplicationTests {Autowiredprivate RedisTe…

SkyWalking如何实现跨线程Trace传递

一、概述 SkyWalking 的中构建 Trace 信息时会借助 ThreadLocal来存储一些上下文信息&#xff0c;当遇到跨线程的时候&#xff0c;如果 Trace 的上下文信息没有传递到新线程的ThreadLocal 中&#xff0c;那么链路就断开了。那么SkyWalking是如何解决这个问题的呢&#xff1f; …

使用 Spring Boot 3.3 和 JdbcTemplate 操作 MySQL 数据库

在现代的 Java 应用开发中&#xff0c;Spring Boot 提供了强大的工具来简化数据库操作。JdbcTemplate 是 Spring 提供的一个核心类&#xff0c;用于简化 JDBC 操作&#xff0c;减少样板代码。本文将介绍如何在 Spring Boot 3.3 项目中使用 JdbcTemplate 来操作 MySQL 数据库&am…

Axure高保真LayUI框架 V2.6.8元件库

点击下载《Axure高保真LayUI框架 V2.6.8元件库》 原型效果&#xff1a;https://axhub.im/ax9/bf36e6dd89bc4c9f/#g1 摘要 本文详细阐述了在 Axure 环境下打造的一套高度还原 LayUI 框架的组件元件集。通过对 LayUI 框架组件的深入剖析&#xff0c;结合 Axure 的强大功能&…

通讯录实现(Linux+Cpp)

通讯录实现&#xff08;LinuxCpp&#xff09; 产品底层思考&#xff1a; 人员如何存储 -> 链表 &#xff08;增删改 但是排序不适合&#xff09; 文件存储 -> 人员数据的格式 name:xxx,phone:xxx 人员信息 -> 姓名、电话 引出2 name: xxx,phone: xxx,age: xxx,addr…

质检 LIMS 系统数据防护指南 三级等保认证与金融级加密方案设计

面对频发的数据泄露事件&#xff0c;企业亟需构建一套 “防得住、追得回、打得赢” 的防护体系。质检 LIMS 系统通过三级等保认证与金融级加密的结合&#xff0c;为这一目标提供了可行路径。 一、金融级加密&#xff1a;构建数据防护的 “铜墙铁壁” 金融级加密技术通过协议加密…

Spring Boot 从Socket 到Netty网络编程(上):SOCKET 基本开发(BIO)与改进(NIO)

前言 无论是软件还是硬件的本质都是要解决IO问题&#xff08;输入、输出&#xff09;&#xff0c;再说回网络编程本质上都是基于TCP/UP的开发&#xff0c;socket是在此基础上做的扩展与封装&#xff0c;而Netty又是对socket做的封装。本文旨在通过相关案例对socket进行探讨。 一…

79. Word Search

题目描述 79. Word Search 回溯 代码一&#xff0c;使用used数组 class Solution {vector<pair<int,int>> directions{{0,1},{0,-1},{1,0},{-1,0}};vector<vector<bool>> used; public:bool exist(vector<vector<char>>& board, st…

结构性设计模式之Facade(外观)设计模式

结构性设计模式之Facade&#xff08;外观&#xff09;设计模式 前言&#xff1a; 外观模式&#xff1a;用自己的话理解就是用户看到是一个总体页面&#xff0c;比如xx报名系统页面。里面有历年真题模块、报名模块、教程模块、首页模块… 做了一个各个模块的合并&#xff0c;对…

ICML 2025 Spotlight | 机器人界的「Sora」!让机器人实时进行未来预测和动作执行!

标题&#xff1a;Video Prediction Policy: A Generalist Robot Policy with Predictive Visual Representations 作者&#xff1a;Yucheng Hu, Yanjiang Guo, Pengchao Wang, Xiaoyu Chen, Yen-Jen Wang, Jianke Zhang, Koushil Sreenath, Chaochao Lu, Jianyu Chen 机构&am…

AI生态警报:MCP协议风险与应对指南(下)——MCP Host安全

AI生态警报&#xff1a;MCP协议风险与应对指南&#xff08;上&#xff09;——架构与供应链风险https://blog.csdn.net/WangsuSecurity/article/details/148335401?sharetypeblogdetail&sharerId148335401&sharereferPC&sharesourceWangsuSecurity&spm1011.24…

基于VLC的Unity视频播放器(四)

上篇文章中提到的问题 播放某个m3u8地址时会嘎掉&#xff0c;想办法解决了一下&#xff0c;很粗暴的&#xff0c;先SetFormat&#xff0c;再Stop&#xff0c;最后再Play&#xff0c;能用…… if (player ! null && player.GetSize() 0) {player.GetSize((w, h) >…

pixel刷入Android15 userdebug版本

最近入手一个pixel7,想着刷个userdebug版本&#xff0c;就不用模拟器调试开发了&#xff0c;结果按照网上的教程&#xff0c;每次刷机后都是卡在goole logo界面&#xff0c;卡了一天多我才找到问题所在&#xff0c;想着记录下&#xff0c;给自己做个备份。 1. 前期准备&#x…

C# 一个解决方案放一个dll项目,一个dll测试项目 ,调试dll项目的源码

一个解决方案&#xff08;sln&#xff09;中放入2个项目(project&#xff0c;通常是一个文件夹)&#xff0c;一个dll项目&#xff0c;一个dll测试项目 右键dll测试项目&#xff0c;设为启动项目。 在dll测试项目添加引用 1&#xff09;右键测试项目 → 添加 → 引用 → 项目…