知识点
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);
代码及运行
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;
}
程序运行截图如下:
