Qt笔记-使用SSH2进行远程连接linux服务器并上传文件

知识点

SSH的全称是Secure Shell,安全外壳协议,从中可以知道,其实就是套了个壳。

以前说过一个过时的协议FTP。但当加上这个SSH后就变成了SFTP,简单理解就说FTP套了一个SSH。

这里以SSH2开源库为例,演示了使用C++ Qt框架,连接服务器并上传文件的过程。

还有个重要的知识点:QTcpSocket中的socketDescriptor()返回的是底层操作系统的原始套接字描述符(socket descriptor)。可以将这个描述符给到SSH2框架中,进行外壳安全。

流程

使用SSH2的通用流程如下:

A. 初始化 libssh2

cpp 复制代码
    // 初始化 libssh2
    int rc = libssh2_init(0);

B. 建立 TCP 连接

cpp 复制代码
    // 建立 TCP 连接
    m_socket->connectToHost(host, port);

C. 初始化 SSH 会话

cpp 复制代码
    // 初始化 SSH 会话
    m_session = libssh2_session_init();

D. 进行 SSH 握手

cpp 复制代码
    // 进行 SSH 握手
    int rc = libssh2_session_handshake(m_session, m_socket->socketDescriptor());

E. 密码认证

cpp 复制代码
    // 密码认证
    rc = libssh2_userauth_password(m_session, username.toUtf8().constData(), password.toUtf8().constData());

F. 初始化 SFTP 会话

cpp 复制代码
    // 初始化 SFTP 会话
    LIBSSH2_SFTP *sftp = libssh2_sftp_init(m_session);

G. 创建远程文件(写模式,权限 0644)

cpp 复制代码
    // 创建远程文件(写模式,权限 0644)
    LIBSSH2_SFTP_HANDLE *sftpHandle = libssh2_sftp_open(sftp,
        remoteFilePath.toUtf8().constData(),
        LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
        0644);

H. 上传写入远程文件

cpp 复制代码
        // 写入远程文件
        ssize_t bytesWritten = libssh2_sftp_write(sftpHandle, buffer, bytesRead);

I. 清理资源

cpp 复制代码
    // 清理资源
    libssh2_sftp_close(sftpHandle);
    libssh2_sftp_shutdown(sftp);
    libssh2_session_disconnect(m_session, "文件上传完成");
    libssh2_session_free(m_session);

代码及运行

SFTPDemo.pro

bash 复制代码
QT -= gui

QT += network

CONFIG += c++11 console
CONFIG -= app_bundle

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

INCLUDEPATH += D:\Github\libssh2-1.11.1\libssh2-1.11.1\include

SOURCES += \
        SftpUploader.cpp \
        main.cpp

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target


# 区分debug和release模式,链接不同版本的库
CONFIG(debug, debug|release) {
    # Debug模式配置
    message("Configuring for Debug mode")

    # Debug库路径(通常包含debug目录或带d后缀的库)
    LIBS += -L"D:/Github/libssh2-1.11.1/libssh2-1.11.1/build/src/Debug"  # 替换为debug库路径

    # Debug版本库(示例:libssh的debug版本可能名为libssh_d或ssh_d)
    LIBS += -llibssh2  # 假设debug库带d后缀
} else {
    # Release模式配置
    message("Configuring for Release mode")

    # Release库路径
    LIBS += -L"D:/Github/libssh2-1.11.1/libssh2-1.11.1/build/src/Release"  # 替换为release库路径

    # Release版本库(无后缀)
    LIBS += -llibssh2
}

HEADERS += \
    SftpUploader.h

SftpUploader.h

cpp 复制代码
#ifndef SFTP_UPLOADER_H
#define SFTP_UPLOADER_H

#include <QObject>
#include <QTcpSocket>
#include <libssh2.h>
#include <libssh2_sftp.h>

class SftpUploader : public QObject
{
    Q_OBJECT
public:
    explicit SftpUploader(QObject *parent = nullptr);
    ~SftpUploader();

    // 上传文件到 SFTP 服务器
    bool uploadFile(const QString &host, int port,
                   const QString &username, const QString &password,
                   const QString &localFilePath, const QString &remoteDir);

private:
    // 创建远程目录(递归创建多级目录)
    bool createRemoteDir(LIBSSH2_SFTP *sftp, const QString &remoteDir);

    LIBSSH2_SESSION *m_session;  // SSH 会话
    QTcpSocket *m_socket;        // TCP 连接
};

#endif // SFTP_UPLOADER_H

SftpUploader.cpp

cpp 复制代码
#include "SftpUploader.h"
#include <QFile>
#include <QDebug>
#include <QDir>

SftpUploader::SftpUploader(QObject *parent) : QObject(parent),
    m_session(nullptr), m_socket(new QTcpSocket(this))
{
    // 初始化 libssh2
    int rc = libssh2_init(0);
    if (rc != 0) {
        qCritical() << "libssh2 初始化失败: " << rc;
    }
}

SftpUploader::~SftpUploader()
{
    // 清理资源
    if (m_session) {
        libssh2_session_disconnect(m_session, "Normal shutdown");
        libssh2_session_free(m_session);
    }
    libssh2_exit();
}

bool SftpUploader::createRemoteDir(LIBSSH2_SFTP *sftp, const QString &remoteDir)
{
    if (remoteDir.isEmpty() || remoteDir == "/") return true;

    // 按 '/' 分割路径,递归创建
    QStringList dirs = remoteDir.split('/', QString::SkipEmptyParts);
    QString currentPath;

    foreach (const QString &dir, dirs) {
        currentPath += "/" + dir;

        // 检查目录是否存在
        LIBSSH2_SFTP_HANDLE *handle = libssh2_sftp_opendir(sftp, currentPath.toUtf8().constData());
        if (handle) {
            libssh2_sftp_closedir(handle);
            continue; // 目录已存在,继续下一级
        }

        // 目录不存在,创建目录(权限 0755)
        int rc = libssh2_sftp_mkdir(sftp, currentPath.toUtf8().constData(), 0755);
        if (rc != 0) {
            qCritical() << "创建远程目录失败: " << currentPath << " 错误码: " << rc;
            return false;
        }
    }
    return true;
}

bool SftpUploader::uploadFile(const QString &host, int port,
                             const QString &username, const QString &password,
                             const QString &localFilePath, const QString &remoteDir)
{
    // 1. 建立 TCP 连接
    m_socket->connectToHost(host, port);
    if (!m_socket->waitForConnected(5000)) {
        qCritical() << "TCP 连接失败: " << m_socket->errorString();
        return false;
    }

    // 2. 初始化 SSH 会话
    m_session = libssh2_session_init();
    if (!m_session) {
        qCritical() << "SSH 会话初始化失败";
        m_socket->close();
        return false;
    }

    // 3. 进行 SSH 握手
    int rc = libssh2_session_handshake(m_session, m_socket->socketDescriptor());
    if (rc != 0) {
        qCritical() << "SSH 握手失败: " << libssh2_session_last_error(m_session, nullptr, nullptr, 0);
        libssh2_session_free(m_session);
        m_session = nullptr;
        m_socket->close();
        return false;
    }

    // 4. 密码认证
    rc = libssh2_userauth_password(m_session, username.toUtf8().constData(), password.toUtf8().constData());
    if (rc != 0) {
        qCritical() << "SSH 认证失败: " << libssh2_session_last_error(m_session, nullptr, nullptr, 0);
        libssh2_session_disconnect(m_session, "认证失败");
        libssh2_session_free(m_session);
        m_session = nullptr;
        m_socket->close();
        return false;
    }

    // 5. 初始化 SFTP 会话
    LIBSSH2_SFTP *sftp = libssh2_sftp_init(m_session);
    if (!sftp) {
        qCritical() << "SFTP 初始化失败: " << libssh2_session_last_error(m_session, nullptr, nullptr, 0);
        libssh2_session_disconnect(m_session, "SFTP 初始化失败");
        libssh2_session_free(m_session);
        m_session = nullptr;
        m_socket->close();
        return false;
    }

    // 6. 创建远程目录
    if (!createRemoteDir(sftp, remoteDir)) {
        libssh2_sftp_shutdown(sftp);
        libssh2_session_disconnect(m_session, "创建目录失败");
        libssh2_session_free(m_session);
        m_session = nullptr;
        m_socket->close();
        return false;
    }

    // 7. 打开本地文件
    QFile localFile(localFilePath);
    if (!localFile.open(QIODevice::ReadOnly)) {
        qCritical() << "打开本地文件失败: " << localFile.errorString();
        libssh2_sftp_shutdown(sftp);
        libssh2_session_disconnect(m_session, "打开本地文件失败");
        libssh2_session_free(m_session);
        m_session = nullptr;
        m_socket->close();
        return false;
    }

    // 8. 构建远程文件路径
    QString fileName = QFileInfo(localFile).fileName();
    QString remoteFilePath = remoteDir + "/" + fileName;
    if (remoteFilePath.startsWith("//")) {
        remoteFilePath = remoteFilePath.mid(1); // 处理路径拼接可能出现的双斜杠
    }

    // 9. 创建远程文件(写模式,权限 0644)
    LIBSSH2_SFTP_HANDLE *sftpHandle = libssh2_sftp_open(sftp,
        remoteFilePath.toUtf8().constData(),
        LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
        0644);
    if (!sftpHandle) {
        qCritical() << "创建远程文件失败: " << remoteFilePath
                   << " 错误: " << libssh2_sftp_last_error(sftp);
        localFile.close();
        libssh2_sftp_shutdown(sftp);
        libssh2_session_disconnect(m_session, "创建远程文件失败");
        libssh2_session_free(m_session);
        m_session = nullptr;
        m_socket->close();
        return false;
    }

    // 10. 上传文件内容
    const int BUFFER_SIZE = 1024 * 8;
    char buffer[BUFFER_SIZE];
    qint64 bytesRead;
    bool uploadSuccess = true;

    while ((bytesRead = localFile.read(buffer, BUFFER_SIZE)) > 0) {
        // 写入远程文件
        ssize_t bytesWritten = libssh2_sftp_write(sftpHandle, buffer, bytesRead);
        if (bytesWritten != bytesRead) {
            qCritical() << "文件写入失败,已写: " << bytesWritten << " 需写: " << bytesRead;
            uploadSuccess = false;
            break;
        }
    }

    // 11. 清理资源
    localFile.close();
    libssh2_sftp_close(sftpHandle);
    libssh2_sftp_shutdown(sftp);
    libssh2_session_disconnect(m_session, "文件上传完成");
    libssh2_session_free(m_session);
    m_session = nullptr;
    m_socket->close();

    if (uploadSuccess && localFile.error() == QFile::NoError) {
        qInfo() << QString::fromLocal8Bit("文件上传成功: ") << remoteFilePath;
        return true;
    } else {
        qCritical() << QString::fromLocal8Bit("文件上传失败: ") << localFile.errorString();
        return false;
    }
}

main.cpp

cpp 复制代码
#include <QCoreApplication>
#include"SftpUploader.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 配置服务器信息(替换为实际信息)
    QString host = "xx.xx.xx.xx";      // Linux 服务器 IP
    int port = 22;                       // SSH 默认端口
    QString username = "root";  // 用户名
    QString password = "root";  // 密码
    QString localFilePath = "D:/test.py"; // 本地文件路径
    QString remoteDir = "/var/www/html/Demonstration.8/public/md"; // 远程目录

    // 执行上传
    SftpUploader uploader;
    bool success = uploader.uploadFile(host, port, username, password, localFilePath, remoteDir);

    return success ? 0 : 1;
}

程序运行截图如下:

相关推荐
用户9718356334661 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪3 小时前
linux 拷贝文件或目录到指定的位置
linux
大树8819 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠19 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush419 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行52019 小时前
Linux 11 动态监控指令top
linux
小宇宙Zz19 小时前
Maven依赖冲突
java·服务器·maven
不会C语言的男孩21 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
闪闪发亮的小星星21 小时前
高斯光以及高斯光公式解释
笔记
古城小栈21 小时前
Unix 与 Linux 异同小叙
linux·服务器·unix