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;
}

程序运行截图如下:

相关推荐
kaoa0003 小时前
Linux入门攻坚——62、memcached使用入门
linux·运维·memcached
AI_56783 小时前
Selenium+Python可通过 元素定位→操作模拟→断言验证 三步实现Web自动化测试
服务器·人工智能·python
model20054 小时前
alibaba linux3 系统盘清理
linux·运维·服务器
WG_174 小时前
Linux:动态库加载总结_进程间通信+进程池 + 进程IPC(27/28/29/30/31/32)
linux·运维·服务器
小赵还有头发4 小时前
安装 RealSense SDK (驱动层)
linux
生擒小朵拉5 小时前
ROS1学习笔记(二)
笔记·学习
Root_Hacker6 小时前
include文件包含个人笔记及c底层调试
android·linux·服务器·c语言·笔记·安全·php
REDcker6 小时前
RESTful API设计规范详解
服务器·后端·接口·api·restful·博客·后端开发
微学AI6 小时前
内网穿透的应用-告别局域网束缚!MonkeyCode+cpolar 解锁 AI 编程新体验
linux·服务器·网络
sunnyday04266 小时前
基于Netty构建WebSocket服务器实战指南
服务器·spring boot·websocket·网络协议