知识点
SSH的全称是Secure Shell,安全外壳协议,从中可以知道,其实就是套了个壳。
以前说过一个过时的协议FTP。但当加上这个SSH后就变成了SFTP,简单理解就说FTP套了一个SSH。
这里以SSH2开源库为例,演示了使用C++ Qt框架,连接服务器并上传文件的过程。
还有个重要的知识点:QTcpSocket中的socketDescriptor()返回的是底层操作系统的原始套接字描述符(socket descriptor)。可以将这个描述符给到SSH2框架中,进行外壳安全。
流程
使用SSH2的通用流程如下:
A. 初始化 libssh2
// 初始化 libssh2 int rc = libssh2_init(0);B. 建立 TCP 连接
// 建立 TCP 连接 m_socket->connectToHost(host, port);C. 初始化 SSH 会话
// 初始化 SSH 会话 m_session = libssh2_session_init();D. 进行 SSH 握手
// 进行 SSH 握手 int rc = libssh2_session_handshake(m_session, m_socket->socketDescriptor());E. 密码认证
// 密码认证 rc = libssh2_userauth_password(m_session, username.toUtf8().constData(), password.toUtf8().constData());F. 初始化 SFTP 会话
// 初始化 SFTP 会话 LIBSSH2_SFTP *sftp = libssh2_sftp_init(m_session);G. 创建远程文件(写模式,权限 0644)
// 创建远程文件(写模式,权限 0644) LIBSSH2_SFTP_HANDLE *sftpHandle = libssh2_sftp_open(sftp, remoteFilePath.toUtf8().constData(), LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC, 0644);H. 上传写入远程文件
// 写入远程文件 ssize_t bytesWritten = libssh2_sftp_write(sftpHandle, buffer, bytesRead);I. 清理资源
// 清理资源 libssh2_sftp_close(sftpHandle); libssh2_sftp_shutdown(sftp); libssh2_session_disconnect(m_session, "文件上传完成"); libssh2_session_free(m_session);代码及运行
SFTPDemo.pro
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.hSftpUploader.h
#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_HSftpUploader.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
#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; }程序运行截图如下: