
简介本资源是基于Qt框架实现SSH安全连接与FTP文件传输的完整开源项目面向C/Qt中级开发者及嵌入式、远程运维类GUI应用开发者解决在Qt程序中集成安全远程通信与文件管理功能的技术难点。压缩包共182个文件含59个头文件.h与43个实现文件.cpp构成核心SSH协议栈与SFTP通道逻辑11个.pro工程配置支持多平台编译另有exe可执行示例、pdb调试符号及ui界面资源整体15.92MB结构清晰、模块职责分明。目前已有1130人学习下载。读者可直接复用libQSsh.a静态库、参考sftpchannel.cpp与sshconnection.cpp等关键源码理解加密握手与通道建立机制运行sftptest.cpp和remoteprocesstest.cpp掌握文件上传下载及远程命令执行全流程并通过qssh.pro工程快速构建自己的跨平台SSH客户端或自动化部署工具。1. 用 Qt 实现 SSH 连接不是调用系统 ssh 命令而是嵌入式协议栈级通信——QSsh-master 是一套基于 Qt 的纯 C SSH 客户端实现不依赖 OpenSSH 二进制也不走 shell 调用适合做 GUI FTP/SSH 混合文件管理器、嵌入式设备远程控制面板或需要细粒度会话控制的工业 HMI 场景很多刚接触 Qt 网络编程的开发者看到 “Qt SSH” 第一反应是QProcess::start(ssh userhost)但这条路在实际项目中很快会撞墙无法捕获密钥交换过程、不能拦截 SFTP 文件传输进度、GUI 主线程被阻塞、Windows 下路径空格和引号转义灾难、Mac/Linux 权限沙盒拦截、更别说做断线重连、多通道复用或自定义加密算法协商。QSsh-master.zip 正是为解决这类问题而生——它不是一个封装脚本而是一套完整实现 RFC 4251–4256 的 Qt 原生 SSH 协议栈底层用QSslSocket做加密通道上层提供QSSHClient、QSftpSession、QSSHChannel等类所有连接、认证、命令执行、文件上传下载都通过信号槽异步驱动天然适配 Qt 事件循环。它不提供图形界面组件但能无缝集成到 Qt Designer 设计的 UI 中比如拖一个QTreeWidget显示远程目录、用QProgressBar绑定 SFTP 上传进度、点击按钮触发execCommand(df -h)并实时刷新QTextEdit。对需要在国产化平台如龙芯麒麟部署轻量级远程运维工具、或在资源受限的工控终端上实现安全文件同步的团队来说这套代码比依赖外部 ssh-agent 或 OpenSSH daemon 更可控、更可审计、也更容易交叉编译。2. 编译 QSsh-master 的核心难点不在 Qt 版本兼容而在 OpenSSL 链接方式与跨平台符号导出策略2.1 为什么不能直接 qmake make——OpenSSL 版本与链接模型决定成败QSsh-master 依赖 OpenSSL 提供 AES、RSA、SHA 等密码学原语但它不使用 Qt 自带的 QSslSocket 加密层而是直接调用libssl.so/.dll/.dylib的 C API。这意味着编译前必须确认三点OpenSSL 版本 ≥ 1.1.1因使用EVP_PKEY_set1_RSA等新接口1.0.2 已弃用动态链接时确保运行时能找到对应.soLinux、.dllWindows或.dylibmacOSWindows 下需显式定义OPENSSL_NO_SSL3和OPENSSL_NO_TLS1_1现代 SSH 协议已禁用 SSLv3/TLS 1.1。提示Ubuntu 20.04 默认 OpenSSL 1.1.1f可直接sudo apt install libssl-dev但 Windows 用户若用 MSVC 编译强烈建议从 https://slproweb.com/products/Win32OpenSSL.html 下载 Win64 OpenSSL 1.1.1w Light 版安装后将C:\OpenSSL-Win64\include加入INCLUDEPATHC:\OpenSSL-Win64\lib\VC\static加入LIBS并链接libcrypto_static.lib和libssl_static.lib避免 DLL 分发问题。2.2 Qt 版本与模块声明的隐性约束QSsh-master 基于 Qt 5.12 开发标题中codeblock qt 5是重要线索要求启用以下模块QT core network widgets基础 UI 与网络QT sql部分示例含 SQLite 日志存储CONFIG c17使用std::optional处理密钥解析结果。若用 Qt 6.x 编译需手动修改两处将#include QTextCodec替换为#include QStringConverter将QTextCodec::codecForName(UTF-8)-toUnicode()改为QStringConverter(QStringConverter::Utf8).toUnicode()。# qssh.pro 关键片段Qt 5.15 兼容 QT core network widgets sql CONFIG c17 INCLUDEPATH $$PWD/src \ $$PWD/3rdparty/openssl/include LIBS -L$$PWD/3rdparty/openssl/lib -lssl -lcrypto win32: LIBS -lws2_32 -lgdi32 -lcrypt322.3 Linux 下静态编译的实操命令链规避 glibc 版本漂移在 Ubuntu 20.04 上构建可移植二进制需禁用系统 OpenSSL 动态链接改用静态库# 1. 下载并编译 OpenSSL 1.1.1w非系统源 wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz tar -xzf openssl-1.1.1w.tar.gz cd openssl-1.1.1w ./config --prefix$HOME/openssl-static no-shared -fPIC make make install # 2. 修改 .pro 文件中的 LIBS 路径 LIBS -L$${HOME}/openssl-static/lib -lssl -lcrypto -ldl -pthread # 3. qmake 时强制静态链接 Qt可选但推荐 qmake CONFIGstatic qssh.pro make -j$(nproc)编译成功后用ldd ./qssh检查输出中不应出现libssl.so或libcrypto.so—— 若存在说明仍链接了系统动态库需检查LIBS路径顺序或LD_LIBRARY_PATH干扰。3. 用 QSshClient 建立可信 SSH 连接的最小可行代码含密码与密钥双认证路径3.1 密码认证三步完成连接与命令执行附超时与错误隔离QSshClient 不继承QObject需手动管理生命周期且所有操作必须在connectToHost()后通过信号驱动// main.cpp 片段Qt 5.15 #include qsshclient.h #include QApplication #include QDebug int main(int argc, char *argv[]) { QApplication app(argc, argv); QSshClient client; // 步骤1设置连接参数不包含认证信息 client.setHostName(192.168.1.100); client.setPort(22); client.setUserName(admin); // 步骤2连接异步触发 connected() 信号 client.connectToHost(); // 步骤3绑定信号处理链 QObject::connect(client, QSshClient::connected, []() { qDebug() SSH connected, starting password auth; client.authenticateWithPassword(your_password); // 触发 authenticated() 或 error() }); QObject::connect(client, QSshClient::authenticated, []() { qDebug() Authentication success; // 执行命令 auto channel client.createShellChannel(); channel-write(ls -l /tmp\n); QObject::connect(channel, QSshChannel::readyRead, []() { qDebug() Remote output: channel-readAll(); }); }); QObject::connect(client, QSshClient::error, [](const QString err) { qDebug() SSH error: err; // 如 Authentication failed, Connection refused }); return app.exec(); // 必须进入事件循环 }参数说明authenticateWithPassword()内部调用sendUserAuthRequest()发送SSH_MSG_USERAUTH_REQUEST包服务端返回SSH_MSG_USERAUTH_SUCCESS时才触发authenticated()。若密码错误error()信号携带Authentication failed字符串不会抛异常这是 Qt 异步模型的设计前提。3.2 RSA 私钥认证加载 PEM 格式密钥并处理 passphrase支持无密码与有密码两种场景QSsh-master 对私钥格式要求严格仅支持 PEM 编码的 PKCS#1-----BEGIN RSA PRIVATE KEY-----或 PKCS#8-----BEGIN PRIVATE KEY-----不支持 OpenSSH 新格式-----BEGIN OPENSSH PRIVATE KEY-----。若密钥由ssh-keygen -t rsa -b 4096生成默认为新格式需转换# 转换 OpenSSH 格式为 PKCS#1Linux/macOS ssh-keygen -p -m PEM -f ~/.ssh/id_rsa # Windows 下可用 PuTTYgenLoad → Save private key选择 RSA 格式// 加载私钥并认证 QFile keyFile(/path/to/id_rsa); if (!keyFile.open(QIODevice::ReadOnly)) { qWarning() Cannot open private key; return; } QByteArray pemData keyFile.readAll(); keyFile.close(); // 若密钥有密码传入 passphrase若无密码传空字符串 bool ok; QSshKey key QSshKey::fromPem(pemData, your_passphrase, ok); if (!ok) { qWarning() Invalid private key format or wrong passphrase; return; } client.setPrivateKey(key); // 设置后调用 authenticate() 即走密钥流程 client.authenticate(); // 不再需要 password 参数注意QSshKey::fromPem()的passphrase参数为QString若密钥未加密必须传QString()空字符串传nullptr会导致崩溃。密钥加载失败时ok为false不会抛出异常需主动检查。3.3 连接超时与重试策略的硬编码位置QSsh-master 未暴露setConnectTimeout()接口超时逻辑固化在QSshTransport::startTimer()中默认 30 秒。若需修改必须编辑src/qsshtransport.cpp// src/qsshtransport.cpp 第 123 行附近 void QSshTransport::startTimer() { if (!m_timer) { m_timer new QTimer(this); connect(m_timer, QTimer::timeout, this, QSshTransport::onTimeout); } m_timer-start(30000); // ← 此处改为 5000 即 5 秒超时 }重试需在应用层实现监听error()信号判断错误字符串是否含Connection refused或Network is unreachable然后延迟后调用connectToHost()。4. 构建 Qt SFTP 文件浏览器的核心类链从连接到目录列表再到断点续传4.1 SFTP 会话初始化与目录遍历的信号驱动流程QSsh-master 将 SFTP 封装为QSftpSession其生命周期依附于QSshClient必须在authenticated()后创建QObject::connect(client, QSshClient::authenticated, []() { QSftpSession *sftp client.createSftpSession(); // 列出远程根目录 sftp-listDirectory(/); QObject::connect(sftp, QSftpSession::directoryListed, [](const QStringList files) { for (const QString file : files) { qDebug() Remote file: file; // 更新 QTreeWidget 模型 } }); QObject::connect(sftp, QSftpSession::listError, [](const QString err) { qDebug() SFTP list failed: err; // 如 No such file }); });关键约束listDirectory()只接受绝对路径如/home/user不支持相对路径..或.返回的files是文件名列表不含路径需自行拼接完整路径用于后续downloadFile()。4.2 断点续传下载的实现原理与字节偏移控制QSsh-master 的downloadFile()默认覆盖写入要实现断点续传需手动控制QSftpSession::readFile()的 offset 参数// 假设下载 /remote/file.zip 到本地 /local/file.zip QFile localFile(/local/file.zip); if (localFile.open(QIODevice::ReadWrite | QIODevice::Append)) { qint64 offset localFile.size(); // 已下载字节数 sftp-readFile(/remote/file.zip, offset, 65536); // 每次读 64KB QObject::connect(sftp, QSftpSession::dataReceived, [](const QByteArray data) { localFile.write(data); localFile.flush(); // 更新进度条offset data.size() offset data.size(); }); }参数说明readFile(const QString path, qint64 offset, quint32 length)中offset是从远程文件起始位置的字节偏移length是本次请求长度。服务端必须支持SSH_FXP_READ的 offset 参数OpenSSH 7.0 默认支持否则返回SSH_FX_FAILURE错误。4.3 上传进度绑定与自定义进度条更新逻辑uploadFile()本身不提供进度信号需用QSftpSession::writeFile()分块上传并手动计算QFile sourceFile(/local/large.bin); if (!sourceFile.open(QIODevice::ReadOnly)) return; quint64 totalSize sourceFile.size(); quint64 uploaded 0; const quint32 chunkSize 32768; while (uploaded totalSize) { QByteArray chunk sourceFile.read(chunkSize); sftp-writeFile(/remote/large.bin, chunk, uploaded); uploaded chunk.size(); int progress static_castint((uploaded * 100) / totalSize); progressBar-setValue(progress); // 绑定到 QProgressBar // 强制事件循环处理避免卡死 qApp-processEvents(); }注意writeFile()是同步阻塞调用大文件上传时必须放在子线程否则冻结 GUI。正确做法是将QFile读取与writeFile()封装进QThread子类用moveToThread()管理。5. 在 Ubuntu 20.04 上调试 “SSH 无法连接” 的五层排查法覆盖ubuntu ssh无法连接热搜场景5.1 网络层用nc和tcpdump验证端口可达性QSsh-master 报错Connection refused时先排除网络问题# 检查目标 IP 是否响应 22 端口 nc -zv 192.168.1.100 22 # 输出应为 Connection to 192.168.1.100 22 port [tcp/ssh] succeeded! # 若失败抓包确认 SYN 是否发出 sudo tcpdump -i any host 192.168.1.100 and port 22 # 在另一终端运行 QSsh 程序观察是否有 SYN 包发出常见陷阱Ubuntu 20.04 默认启用ufw防火墙需sudo ufw allow 22若目标是 Docker 容器检查-p 2222:22映射是否生效宿主机nc -zv localhost 2222应成功。5.2 SSH 服务层验证sshd状态与配置白名单即使端口通sshd可能拒绝连接# 登录目标服务器检查服务状态 sudo systemctl status ssh # 查看是否监听所有接口而非仅 127.0.0.1 sudo ss -tlnp | grep :22 # 检查 /etc/ssh/sshd_config 关键项 grep -E ^(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|AllowUsers) /etc/ssh/sshd_config # 必须有PasswordAuthentication yes密码登录或 PubkeyAuthentication yes密钥登录 # 若配置了 AllowUsers确认当前用户在列表中5.3 QSsh-master 协议层启用调试日志定位握手失败点在QSshTransport构造函数中取消注释调试宏// src/qsshtransport.cpp 第 45 行 #define DEBUG_SSH_TRANSPORT // ← 取消注释重新编译后运行日志输出类似[SSH] Sending KEXINIT [SSH] Received KEXINIT, algo: diffie-hellman-group14-sha256 [SSH] Sending KEXDH_INIT [SSH] Received KEXDH_REPLY, signature verified [SSH] Sending NEWKEYS若卡在Sending KEXINIT后无响应说明密钥交换算法不匹配若出现Invalid signature则服务端公钥指纹校验失败。5.4 Qt 事件循环层确认QEventLoop未被意外退出最隐蔽的错误是app.exec()未被调用或主线程提前return// 错误写法main() 直接返回事件循环未启动 int main(...) { QSshClient client; client.connectToHost(); // 无事件循环connectToHost() 不会真正发包 return 0; // 程序立即退出 } // 正确写法必须有 QApplication::exec() int main(...) { QApplication app(...); QSshClient client; client.connectToHost(); return app.exec(); // 进入循环处理 socket 事件 }5.5 OpenSSL 兼容层验证 TLS 握手是否被降级拦截某些企业网络设备会拦截并重写 SSH 流量伪装成 HTTPS导致QSshTransport在sendKexInit()后收不到响应。此时tcpdump会显示客户端发 SYNACK但服务端无任何回包。解决方案是强制禁用 TLS 降级// 在 connectToHost() 前插入 QSslConfiguration config QSslConfiguration::defaultConfiguration(); config.setProtocol(QSsl::TlsV1_2); // 仅允许 TLS 1.2 config.setPeerVerifyMode(QSslSocket::VerifyNone); // SSH 不需要证书验证 QSslConfiguration::setDefaultConfiguration(config);注意此设置影响整个 Qt 应用的 SSL 行为若同时使用QNetworkAccessManager需单独为其配置QSslConfiguration。用QSshClient::setDebugLevel(QSshClient::DebugLevel::Full)可在控制台打印完整 SSH 数据包十六进制流当遇到SSH_MSG_DISCONNECT错误码 3CONNECTION_LOST时说明 TCP 连接被中间设备强制关闭此时应检查防火墙策略或更换 SSH 端口避开深度包检测。本文还有配套的精品资源点击获取