第十二课:QSsh 库编译及客户端开发

news2024/9/27 21:19:11

功能描述:QSsh 库编译以及如何运用 QSsh 做应用开发

一、最终演示效果

本次制作的 Demo 是通过输入主机的 IP 、端口号、用户名和密码,能够实现 SSH 自动连接,从而对远程设备进行操作。

主机(H):输入远程设备的 IP

端口(P):默认端口 22

用户名(U):输入远程设备的普通用户,系统默认禁止 root 用户 SSH,可以通过修改配置文件使得root 用户登录,建议还是使用普通用户登录

密码(W):用户名对应的登录密码

小圆圈图标表示 SSH 的状态,蓝色代表连接,红色代表断开

快速连接/断开:点击“快速连接”,如果 SSH 连接成功,则小圆圈图标变成蓝色,主机、端口号、用户名、用户名、密码的输入框失能,不允许输入,指令输入框使能,可输入指令,“快速连接”按钮文字变成“断开”,如果连接失败,则保持状态不变;点迹“断开”,如果 SSH 断开成功,则小圆圈图标变成红色,端口号、用户名、用户名、密码的输入框使能,允许输入,指令输入框失能,不允许输入指令,“断开”按钮文字变成“快速连接”,如果断开失败,则保持状态不变

指令输入框:输入 linux 指令,按回车键执行该指令

清空:点击“清空”按钮,轻触信息输出框的全部内容

文本输出框:显示 SSH 登录输出信息、指令执行信息等内容

二、QSsh 库编译过程

QSsh 为 Qt 提供 SSH 和 SFTP 支持,使你能方便快捷地使用 SSH 和 SFTP,但 QSsh 不是随 Qt 安装包一同安装的,需要自行编译。

编译过程如下:

1.下载 QSsh 源码包
https://download.csdn.net/download/tanou3212/88220985https://download.csdn.net/download/tanou3212/88220985

2. 解压缩软件安装包,使用 QtCreator 打开 qssh.pro 文件,我使用的是 Qt 5.10.0 版本和 MinGW 编译器进行源码编译,其它版本的 Qt 和编译器设置类似

 3. 在 Debug 构建下,先点击“执行 qmake”,再“构建”

 4. 切换到 Release 构建下,再次点击“执行 qmake”,再“构建”

5. 经过第 3 步和第 4 步后, 在源码包下生成了一个 lib 文件夹,里面包含了libQSsh.a、QSsh.dll、libQSshd.a 和 QSshd.dll 四个文件,说明你已成功编译出 QSsh 的动态库了

三、运用 QSsh 做应用开发

1. QSsh 库的运用方法

将源码包主文件夹下的 “include” 的文件夹和 “lib” 文件夹拷贝到你的 Qt 工程目录下,然后在 .pro 文件中将 QSsh 的头文件路径和库文件路径添加到 INCLUDEPATH 和 LIBS 变量中

INCLUDEPATH = $$PWD/QSsh/include/ssh

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/QSsh/lib -lQSsh
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/QSsh/lib -lQSshd

2. QSsh 的应用开发

本次应用开发的目标如上图的演示效果,主要包含了 Ssh.cpp、Ssh.h、widget.h 和 widget.cpp 这四个文件,其中:

Ssh.h/Ssh.cpp:封装了 SSH 相关处理函数,供用户调用

widget.h/widget.cpp:整个界面的制作,并调用 Ssh 类中的处理函数

.pro 文件代码如下:

QT       += core gui network

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = QSshDemo

TEMPLATE = app

DEFINES += QT_DEPRECATED_WARNINGS

SOURCES += \
        main.cpp \
        widget.cpp \
    Ssh.cpp

HEADERS += \
        widget.h \
    Ssh.h

INCLUDEPATH = $$PWD/QSsh/include/ssh

win32:CONFIG(release, debug|release): LIBS += -L$$PWD/QSsh/lib -lQSsh
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/QSsh/lib -lQSshd

RESOURCES += \
    images.qrc

Ssh.h 文件代码如下:

#ifndef SSH_H
#define SSH_H

#include <sshconnection.h>
#include <sshremoteprocess.h>
#include <sftpchannel.h>
#include <QHostAddress>
#include <QThread>
#include <QTextCodec>

class Ssh : public QObject
{
    Q_OBJECT
public:
    /**
     * @brief Ssh
     * @param ip            IP 地址
     * @param port          端口号,默认 22
     * @param user          用户名
     * @param pwd           密码
     */
    explicit Ssh(QString ip, int port = 22, QString user = "yan",  QString pwd = "123123");

    /**
     * @brief init                              初始化 SSH,包括创建线程、创建 SSH 连接
     */
    void init();

    /**
     * @brief unInit                            退出线程
     */
    void unInit();

    /**
     * @brief isEnableSSH                       SSH 是否可用
     */
    bool isEnableSSH();

    /**
     *                                          释放内存
     */
    ~Ssh();

private:
    /**
     * @brief sendCmd                   向 SSH 写指令
     * @param message                   指令
     * @return                          写入成功的字节数
     */
    int sendCmd(QString message);

    /**
     * @brief getIpPort                 获取 IP 和 Port 的值
     * @return
     */
    QString getIpPort() {return strIp + ":" + QString::number(nPort);}

    /**
     * @brief convertCodeC              统一编码,防止中文乱码
     * @param ba
     * @return
     */
    QString convertCodeC(const QByteArray &ba);

    // 新建线程,用于 SSH
    QThread * thread = nullptr;
    // SSH 是否已连接
    bool bConnected = false;
    // SSH 是否可发送
    bool bSendable = false;

    // IP 地址
    QString strIp;
    // 端口号
    int nPort;
    // 用户名
    QString strUser;
    // 密码
    QString strPwd;

    // SSH 连接参数
    QSsh::SshConnectionParameters argParameters;
    // SSH Socket
    QSsh::SshConnection * sshSocket = nullptr;
    // Shell 脚本
    QSharedPointer<QSsh::SshRemoteProcess> shell;

signals:
    /**
     * @brief sigInit                   子线程和主线程交互
     */
    void sigInit();

    /**
     * @brief sigConnectStateChanged    SSH 连接状态改变
     * @param state                     true: 连接,  false: 断开
     * @param ip                        IP 地址
     * @param port                      端口号
     */
    void sigConnectStateChanged(bool state, QString ip, int port);

    /**
     * @brief sigDataArrived            SSH 接收数据再往外转发
     * @param msg                       数据
     * @param ip                        IP 地址
     * @param port                      端口号
     */
    void sigDataArrived(QString msg, QString ip, int port);

public slots:
    /**
     * @brief sigInitSlot
     */
    void sigInitSlot();

    /**
     * @brief resetConnection           重置 SSH 连接,即断开连接
     * @param ipPort                    IP 和端口,IP:端口
     */
    void resetConnection(QString ipPort);

    /**
     * @brief send                      向 SSH 写指令
     * @param message                   指令
     */
    void send(QString message);

    /**
     * @brief sendByQByteArray          向 SSH 写指令
     * @param ipPort                    IP:端口号
     * @param arrMsg                    指令
     */
    void sendByQByteArray(QString ipPort, QByteArray arrMsg);

    /**
     * @brief sshDisconnected           断开连接
     */
    void sshDisconnected();

    /**
     * @brief dataReceived              处理交互信息
     */
    void dataReceived();


private slots:
    /**
     * @brief createConnection          创建 SSH 连接
     */
    void createConnection();

    /**
     * @brief sshConnected              连接后处理,包括创建shell
     */
    void sshConnected();

    /**
     * @brief threadFinished            删除线程,释放内存
     */
    void threadFinished();

    /**
     * @brief sshConnectError           错误信息处理
     * @param sshError                  错误信息
     */
    void sshConnectError(QSsh::SshError sshError);

    /**
     * @brief shellStart                shell 开始
     */
    void shellStart();

    /**
     * @brief shellError                shell 发生错误
     */
    void shellError();
};

#endif // SSH_H

Ssh.cpp 文件代码如下: 

#include "ssh.h"
#include <QDebug>

Ssh::Ssh(QString ip, int port, QString user, QString pwd)
{
    strIp = ip;
    nPort = port;
    strUser = user;
    strPwd = pwd;
}

// 初始化 SSH,包括创建线程、创建 SSH 连接
void Ssh::init()
{
    thread = new QThread();
    connect(thread, SIGNAL(finished()), this, SLOT(threadFinished()));
    this->moveToThread(thread);
    thread->start();

    connect(this, SIGNAL(sigInit()), this, SLOT(sigInitSlot()));
    emit sigInit();
}

// 退出线程
void Ssh::unInit()
{
    thread->quit();
}

// SSH 是否可用
bool Ssh::isEnableSSH()
{
    if(bConnected && bSendable)
        return true;
    else
        return false;
}

Ssh::~Ssh()
{
    if(sshSocket)
    {
        delete sshSocket;
        sshSocket = nullptr;
    }
}

// 向 SSH 写指令
int Ssh::sendCmd(QString message)
{
    int nSize = 0;
    if(bConnected && bSendable)
    {
        nSize = shell->write(message.toUtf8().data());
    }
    else
    {
        qDebug() << "SSH 未连接或 shell 未连接:" << getIpPort();
    }

    return nSize;
}

// 统一编码,防止中文乱码
QString Ssh::convertCodeC(const QByteArray &ba)
{
    QTextCodec::ConverterState state;
    QString text = QTextCodec::codecForName("UTF-8")->toUnicode(ba.constData(), ba.size(), &state);
    if(state.invalidChars>0)
        text = QTextCodec::codecForName("GBK")->toUnicode(ba);
    else
        text = ba;
    return text;
}

void Ssh::sigInitSlot()
{
    argParameters.host = strIp;
    argParameters.port = nPort;
    argParameters.userName = strUser;
    argParameters.password = strPwd;
    argParameters.timeout = 10;
    // 密码方式连接
    argParameters.authenticationType = QSsh::SshConnectionParameters::AuthenticationTypePassword;
    // 连接
    createConnection();
}

// 重置 SSH 连接,即断开连接
void Ssh::resetConnection(QString ipPort)
{
    if(this->getIpPort() == ipPort)
    {
        this->sshDisconnected();
    }
}

// 向 SSH 写指令
void Ssh::send(QString message)
{
    sendCmd(message);
}

// 向 SSH 写指令
void Ssh::sendByQByteArray(QString ipPort, QByteArray arrMsg)
{
    if(ipPort.compare(getIpPort()))
        return;

    if(bConnected && bSendable)
        shell->write(arrMsg);
    else
        qDebug() << "发送失败,未建立连接:" << getIpPort();
}

// 断开连接
void Ssh::sshDisconnected()
{
    sshSocket->disconnectFromHost();
    // 此处断开需要时间,因此直接赋值false
    emit sigConnectStateChanged(false, strIp, nPort);
}

// 处理交互信息
void Ssh::dataReceived()
{
    QByteArray byteRecv = shell->readAllStandardOutput();
    QString strRecv = convertCodeC(byteRecv);

    if(strRecv.contains("password") || strRecv.contains(QString::fromLocal8Bit("密码")))
    {
        QString pwd = strPwd+"\n";
        shell->write(pwd.toUtf8().data());
    }

    if(strRecv.contains("yes/no/[fingerprint]"))
    {
        QString str = "yes\n";
        shell->write(str.toUtf8().data());
    }

    if(!strRecv.isEmpty()) //过滤空行
    {
        emit sigDataArrived(strRecv, strIp, nPort);
    }
}

// 创建 SSH 连接
void Ssh::createConnection()
{
    if(bConnected)
        return;

    if(!sshSocket)
    {
        sshSocket = new QSsh::SshConnection(argParameters);
        connect(sshSocket, SIGNAL(connected()), SLOT(sshConnected()));
        connect(sshSocket, SIGNAL(error(QSsh::SshError)), SLOT(sshConnectError(QSsh::SshError)));
    }
    sshSocket->connectToHost();
}

// 连接后处理,包括创建shell
void Ssh::sshConnected()
{
    shell = sshSocket->createRemoteShell();
    connect(shell.data(), SIGNAL(started()), SLOT(shellStart()));
    connect(shell.data(), SIGNAL(readyReadStandardOutput()), SLOT(dataReceived()));
    connect(shell.data(), SIGNAL(readyReadStandardError()), SLOT(shellError()));
    shell.data()->start();

    bConnected = true;
    emit sigConnectStateChanged(bConnected, strIp, nPort);
}

void Ssh::threadFinished()
{
    thread->deleteLater();
    this->deleteLater();
}

// 错误信息处理
void Ssh::sshConnectError(QSsh::SshError sshError)
{
    bSendable = false;
    bConnected = false;
    emit sigConnectStateChanged(bConnected, strIp, nPort);

    switch(sshError)
    {
    case QSsh::SshNoError:
        qDebug() << "sshConnectError SshNoError" << getIpPort();
        break;
    case QSsh::SshSocketError:
        qDebug() << "sshConnectError SshSocketError" << getIpPort();     // 拔掉网线是这种错误
        break;
    case QSsh::SshTimeoutError:
        qDebug() << "sshConnectError SshTimeoutError" << getIpPort();
        break;
    case QSsh::SshProtocolError:
        qDebug() << "sshConnectError SshProtocolError" << getIpPort();
        break;
    case QSsh::SshHostKeyError:
        qDebug() << "sshConnectError SshHostKeyError" << getIpPort();
        break;
    case QSsh::SshKeyFileError:
        qDebug() << "sshConnectError SshKeyFileError" << getIpPort();
        break;
    case QSsh::SshAuthenticationError:
        qDebug() << "sshConnectError SshAuthenticationError" << getIpPort();
        break;
    case QSsh::SshClosedByServerError:
        qDebug() << "sshConnectError SshClosedByServerError" << getIpPort();
        break;
    case QSsh::SshInternalError:
        qDebug() << "sshConnectError SshInternalError"<<getIpPort();
        break;
    default:
        break;
    }
}

// shell 开始
void Ssh::shellStart()
{
    bSendable = true;
    qDebug() << "shellStart Shell 已连接:" << getIpPort();
}

// shell 发生错误
void Ssh::shellError()
{
    qDebug() << "shellError Shell 发生错误:" << getIpPort();
}

widget.h 文件代码如下:  

#ifndef WIDGET_H
#define WIDGET_H

#include <QFrame>
#include <QFont>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QRegExp>
#include <QRegExpValidator>
#include <QTextBrowser>
#include <QMessageBox>

#include "Ssh.h"

class Widget : public QFrame
{
    Q_OBJECT
public:
    Widget();

signals:
    /**
     * @brief sigSend   发送指令
     * @param cmd       指令
     */
    void sigSend(QString cmd);

    /**
     * @brief sigDisconnected  断开 SSH 连接
     */
    void sigDisconnected();

public slots:
    /**
     * @brief connectStateChanged           SSH 连接状态改变
     * @param state                         true: 连接,  false: 断开
     * @param ip                            IP 地址
     * @param port                          端口号
     */
    void connectStateChanged(bool state, QString ip, int port);

    /**
     * @brief dataArrived                   执行指令输出
     * @param msg                           信息
     * @param ip                            IP 地址
     * @param port                          端口号
     */
    void dataArrived(QString msg, QString ip, int port);

    /**
     * @brief sshConnect                    快速连接/断开
     */
    void sshConnect();

    /**
     * @brief executeCmd                    执行指令
     */
    void executeCmd();

    void clearText();
private:
    // 初始化界面
    void initUI();
    // 创建 ssh
    Ssh * ssh;
    // 字体
    QFont font;
    // 主机
    QLabel * hostLabel;
    QLineEdit * hostLineEdit;
    // 端口
    QLabel * portLabel;
    QLineEdit * portLineEdit;
    // 用户名
    QLabel * userNameLabel;
    QLineEdit * userNameLineEdit;
    // 密码
    QLabel * passwdLabel;
    QLineEdit * passwdLineEdit;
    // 连接状态
    QLabel * linkStateLabel;
    // 快速连接
    QPushButton * linkBtn;
    // 指令
    QLabel * cmdLabel;
    // 指令
    QLineEdit * cmdLineEdit;
    //清空
    QPushButton * clearBtn;
    // 输出信息
    QTextBrowser * textBrowser;
};

#endif // WIDGET_H

widget.cpp 文件代码如下: 

#include "widget.h"

Widget::Widget()
{
    setStyleSheet("background-color:#2B2C2E;color:rgba(255,255,255,0.85);");
    setFixedHeight(400);
    resize(700,400);
    initUI();
}

void Widget::connectStateChanged(bool state, QString ip, int port)
{
    Q_UNUSED(ip);
    Q_UNUSED(port);
    if(state)
    {
        linkStateLabel->setStyleSheet("background-color:transparent;"
                                      "background-image: url(:/Images/normalIcon.png);"
                                      "background-repeat: no-repeat;"
                                      "background-position: center center;");
        cmdLineEdit->setEnabled(true);

        linkBtn->setText("断开");
        linkStateLabel->setStyleSheet("background-image: url(:/Images/normalIcon.png);"
                                      "background-repeat: no-repeat;"
                                      "background-position: center center;");
        hostLineEdit->setDisabled(true);
        portLineEdit->setDisabled(true);
        userNameLineEdit->setDisabled(true);
        passwdLineEdit->setDisabled(true);

    }
    else
    {
        linkStateLabel->setStyleSheet("background-color:transparent;"
                                      "background-image: url(:/Images/breakIcon.png);"
                                      "background-repeat: no-repeat;"
                                      "background-position: center center;");
        cmdLineEdit->setDisabled(true);

        linkBtn->setText("快速连接");
        linkStateLabel->setStyleSheet("background-image: url(:/Images/breakIcon.png);"
                                      "background-repeat: no-repeat;"
                                      "background-position: center center;");
        hostLineEdit->setEnabled(true);
        portLineEdit->setEnabled(true);
        userNameLineEdit->setEnabled(true);
        passwdLineEdit->setEnabled(true);
    }
}

void Widget::dataArrived(QString msg, QString ip, int port)
{
    Q_UNUSED(ip);
    Q_UNUSED(port);
    textBrowser->append(msg);
}

void Widget::sshConnect()
{
    if(hostLineEdit->text().trimmed().isEmpty() || portLineEdit->text().trimmed().isEmpty() ||
            userNameLineEdit->text().trimmed().isEmpty() || passwdLineEdit->text().trimmed().isEmpty())
    {
        QMessageBox::warning(0,"警告","登录信息未填写完整");
        return;
    }

    if(linkBtn->text() == "快速连接")
    {
        ssh = new Ssh(hostLineEdit->text(), portLineEdit->text().toUInt(), userNameLineEdit->text(), passwdLineEdit->text());
        connect(ssh, SIGNAL(sigConnectStateChanged(bool,QString,int)), this, SLOT(connectStateChanged(bool,QString,int)));
        connect(ssh, SIGNAL(sigDataArrived(QString,QString,int)), this, SLOT(dataArrived(QString,QString,int)));
        connect(this, SIGNAL(sigSend(QString)), ssh, SLOT(send(QString)));
        connect(this, SIGNAL(sigDisconnected()), ssh, SLOT(sshDisconnected()));
        ssh->init();
    }
    else
    {
        emit sigDisconnected();
    }
}

void Widget::executeCmd()
{
    if(cmdLineEdit->text().trimmed().isEmpty())
        return;

    if(!ssh->isEnableSSH())
    {
        qDebug() << "SSH 不可用!";
        return;
    }

    QString cmd = cmdLineEdit->text().trimmed();
    // 如果是 ls 命令,去除文字颜色,否则显示一些无关的符号
    if((cmd.left(2) == "ls") || (cmd.left(2) == "ll"))
        cmd += " --color=none";
    cmd += " \r\n";
    emit sigSend(cmd);
    cmdLineEdit->clear();
}

void Widget::clearText()
{
    textBrowser->clear();
}

void Widget::initUI()
{
    font.setPixelSize(16);
    font.setFamily("黑体");

    hostLabel = new QLabel;
    hostLabel->setFont(font);
    hostLabel->setText("主机(H):");
    hostLabel->setFixedSize(90,32);
    hostLabel->setAlignment(Qt::AlignVCenter|Qt::AlignRight);

    hostLineEdit = new QLineEdit;
    font.setFamily("Times New Roman");
    hostLineEdit->setFixedSize(200,32);
    hostLineEdit->setFont(font);
    hostLineEdit->setAlignment(Qt::AlignCenter);
    hostLineEdit->setPlaceholderText("IP");
    hostLineEdit->setToolTip("请输入IP");
    QRegExp ipExp("((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)");
    QRegExpValidator * ipVal = new QRegExpValidator(ipExp);
    hostLineEdit->setValidator(ipVal);
    hostLineEdit->setStyleSheet("background-color:rgba(255,255,255,0.05); border-radius: 3px; margin:0px;");
    hostLineEdit->setText("192.168.1.100");

    font.setFamily("黑体");
    portLabel = new QLabel;
    portLabel->setFont(font);
    portLabel->setText("端口(P):");
    portLabel->setFixedSize(90,32);
    portLabel->setAlignment(Qt::AlignVCenter|Qt::AlignRight);

    portLineEdit = new QLineEdit;
    font.setFamily("Times New Roman");
    portLineEdit->setFixedSize(200,32);
    portLineEdit->setFont(font);
    portLineEdit->setAlignment(Qt::AlignCenter);
    portLineEdit->setPlaceholderText("端口号");
    portLineEdit->setToolTip("请输入端口号");
    QIntValidator * portVal = new QIntValidator(0,65535);
    portLineEdit->setValidator(portVal);
    portLineEdit->setStyleSheet("background-color:rgba(255,255,255,0.05); border-radius: 3px; margin:0px;");
    portLineEdit->setText("22");

    font.setFamily("黑体");
    userNameLabel = new QLabel;
    userNameLabel->setFont(font);
    userNameLabel->setText("用户名(U):");
    userNameLabel->setFixedSize(90,32);
    userNameLabel->setAlignment(Qt::AlignVCenter|Qt::AlignRight);
    userNameLabel->setStyleSheet("margin:0px;");

    userNameLineEdit = new QLineEdit;
    font.setFamily("Times New Roman");
    userNameLineEdit->setFixedSize(200,32);
    userNameLineEdit->setFont(font);
    userNameLineEdit->setAlignment(Qt::AlignCenter);
    userNameLineEdit->setPlaceholderText("用户名");
    userNameLineEdit->setToolTip("请输入用户名");
    QRegExp userNameExp("^[a-zA-Z0-9_][a-zA-Z0-9_]+$");
    QRegExpValidator * userNameVal = new QRegExpValidator(userNameExp);
    userNameLineEdit->setValidator(userNameVal);
    userNameLineEdit->setStyleSheet("background-color:rgba(255,255,255,0.05); border-radius: 3px; margin:0px;");
    userNameLineEdit->setText("yan");

    font.setFamily("黑体");
    passwdLabel = new QLabel;
    passwdLabel->setFont(font);
    passwdLabel->setText("密码(W):");
    passwdLabel->setFixedSize(90,32);
    passwdLabel->setAlignment(Qt::AlignVCenter|Qt::AlignRight);

    passwdLineEdit = new QLineEdit;
    font.setFamily("Times New Roman");
    passwdLineEdit->setFixedSize(200,32);
    passwdLineEdit->setFont(font);
    passwdLineEdit->setAlignment(Qt::AlignCenter);
    passwdLineEdit->setPlaceholderText("密码");
    passwdLineEdit->setEchoMode(QLineEdit::Password);
    passwdLineEdit->setToolTip("请输入密码");
    passwdLineEdit->setStyleSheet("background-color:rgba(255,255,255,0.05); border-radius: 3px;margin:0px;");
    passwdLineEdit->setText("123123");

    linkStateLabel = new QLabel;
    linkStateLabel->setFixedSize(90,32);
    linkStateLabel->setStyleSheet("background-image: url(:/Images/breakIcon.png);"
                                  "background-repeat: no-repeat;"
                                  "background-position: center center;");

    linkBtn = new QPushButton;
    font.setFamily("黑体");
    linkBtn->setFont(font);
    linkBtn->setText("快速连接");
    linkBtn->setStyleSheet("QPushButton{background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #5EDCF8, stop:0.5 #82E5FB, stop:1 #06C9F4); color:rgb(255,255,255); border-radius:8px; margin:0px;}"
                           "QPushButton:pressed{background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #c0c0c0, stop:1 #808080); color:rgb(255,255,255); border-radius:8px; margin:0px;}"
                           "QPushButton:disabled{background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #c0c0c0, stop:1 #808080); color:rgb(255,255,255); border-radius:8px; margin:0px;}");
    linkBtn->setFixedSize(112,32);
    connect(linkBtn, SIGNAL(clicked()), this, SLOT(sshConnect()));

    textBrowser = new QTextBrowser;
    font.setFamily("Times New Roman");
    textBrowser->setFont(font);
    textBrowser->setStyleSheet("background-color:rgba(255,255,255,0.05); border-radius: 3px;margin:0px;");

    cmdLabel = new QLabel;
    font.setFamily("黑体");
    cmdLabel->setFont(font);
    cmdLabel->setText("指令(回车执行):");
    cmdLabel->setFixedSize(140,32);
    cmdLabel->setAlignment(Qt::AlignVCenter|Qt::AlignRight);

    cmdLineEdit = new QLineEdit;
    font.setFamily("Times New Roman");
    cmdLineEdit->setFixedHeight(32);
    cmdLineEdit->setFont(font);
    cmdLineEdit->setAlignment(Qt::AlignCenter);
    cmdLineEdit->setPlaceholderText("指令");
    cmdLineEdit->setToolTip("请输入指令");
    cmdLineEdit->setStyleSheet("background-color:rgba(255,255,255,0.05); border-radius: 3px; margin:0px;");
    connect(cmdLineEdit, SIGNAL(returnPressed()), this, SLOT(executeCmd()));

    clearBtn = new QPushButton;
    font.setFamily("黑体");
    clearBtn->setFont(font);
    clearBtn->setText("清空");
    clearBtn->setStyleSheet("QPushButton{background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #5EDCF8, stop:0.5 #82E5FB, stop:1 #06C9F4); color:rgb(255,255,255); border-radius:8px; margin:0px;}"
                           "QPushButton:pressed{background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #c0c0c0, stop:1 #808080); color:rgb(255,255,255); border-radius:8px; margin:0px;}"
                           "QPushButton:disabled{background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #c0c0c0, stop:1 #808080); color:rgb(255,255,255); border-radius:8px; margin:0px;}");
    clearBtn->setFixedSize(60,32);
    connect(clearBtn, SIGNAL(clicked()), this, SLOT(clearText()));

    QHBoxLayout * hostLayout = new QHBoxLayout;
    hostLayout->setMargin(0);
    hostLayout->setContentsMargins(5,5,5,5);
    hostLayout->setSpacing(0);
    hostLayout->addWidget(hostLabel);
    hostLayout->addWidget(hostLineEdit);

    QHBoxLayout * portLayout = new QHBoxLayout;
    portLayout->setMargin(0);
    portLayout->setContentsMargins(5,5,5,5);
    portLayout->setSpacing(0);
    portLayout->addWidget(portLabel);
    portLayout->addWidget(portLineEdit);

    QHBoxLayout * userNameLayout = new QHBoxLayout;
    userNameLayout->setMargin(0);
    userNameLayout->setContentsMargins(5,5,5,5);
    userNameLayout->setSpacing(0);
    userNameLayout->addWidget(userNameLabel);
    userNameLayout->addWidget(userNameLineEdit);

    QHBoxLayout * passwdLayout = new QHBoxLayout;
    passwdLayout->setMargin(0);
    passwdLayout->setContentsMargins(5,5,5,5);
    passwdLayout->setSpacing(0);
    passwdLayout->addWidget(passwdLabel);
    passwdLayout->addWidget(passwdLineEdit);

    QHBoxLayout * linkLayout = new QHBoxLayout;
    linkLayout->setMargin(0);
    linkLayout->setContentsMargins(5,5,5,5);
    linkLayout->setSpacing(20);
    linkLayout->addWidget(linkStateLabel);
    linkLayout->addWidget(linkBtn,0,Qt::AlignVCenter|Qt::AlignRight);

    QHBoxLayout * cmdLayout = new QHBoxLayout;
    cmdLayout->setMargin(0);
    cmdLayout->setContentsMargins(5,5,5,5);
    cmdLayout->setSpacing(0);
    cmdLayout->addWidget(cmdLabel);
    cmdLayout->addWidget(cmdLineEdit);
    cmdLayout->addSpacing(10);
    cmdLayout->addWidget(clearBtn);

    QVBoxLayout * leftLayout = new QVBoxLayout;
    leftLayout->setMargin(0);
    leftLayout->setContentsMargins(0,0,0,0);
    leftLayout->setSpacing(0);
    leftLayout->addLayout(hostLayout);
    leftLayout->addStretch(1);
    leftLayout->addLayout(portLayout);
    leftLayout->addStretch(1);
    leftLayout->addLayout(userNameLayout);
    leftLayout->addStretch(1);
    leftLayout->addLayout(passwdLayout);
    leftLayout->addStretch(1);
    leftLayout->addLayout(linkLayout);

    QHBoxLayout * topLayout = new QHBoxLayout;
    topLayout->setMargin(0);
    topLayout->setContentsMargins(0,0,0,0);
    topLayout->setSpacing(20);
    topLayout->addLayout(leftLayout);
    topLayout->addWidget(textBrowser);

    QVBoxLayout * mainLayout = new QVBoxLayout;
    mainLayout->setMargin(0);
    mainLayout->setContentsMargins(24,24,24,24);
    mainLayout->setSpacing(10);
    mainLayout->addLayout(topLayout,0);
    mainLayout->addLayout(cmdLayout);
    setLayout(mainLayout);
}

完整的代码已经贴上,每个函数的备注写的非常清楚,如有不清楚的地方可以私信我。

完整代码压缩包下载地址:

https://download.csdn.net/download/tanou3212/88221081https://download.csdn.net/download/tanou3212/88221081

四、为 root 用户开启 SSH 登录

Linux 默认不允许以 root 用户进行 SSH 登录,如需以 root 用户 SSH,需按照以下配置为 root 用户开启 SSH 登录权限

第 1 步:在远程设备(即 SSH 登录的设备)上,以 root 权限编辑 /etc/ssh/sshd_config 文件

#sudo gedit /etc/ssh/sshd_config

第 2 步:找到 PermitRootLogin prohibit-password 或者 PermitRootLogin without-password,用 “#” 注释掉这行,在其后添加一行 PermitRootLogin yes

#PermitRootLogin prohibit-password
PermitRootLogin yes

 第 3 步:重启ssh服务

sudo service ssh restart

现在 root 用户可以登录 SSH 了

写在最后的话

如果出现中文乱码的问题,请参考我的另外一篇博客《第十课:Qt 字符编码和中文乱码相关问题》 ,百分百能解决你的问题!

如果你没有积分或者不是会员,请联系我,私发你压缩包!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/889453.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分享twinmotion获得逼真效果的渲染技巧

正如所引用的那样&#xff0c;“渲染与软件无关。它关乎艺术家的眼睛、看到脑海中最终图像的能力&#xff0c;以及将这一愿景变为现实的技能。一些流行的渲染软件包括 V-Ray、Arnold、Unreal Engine、Blender 和 Octane Render。 其中之一是 TwinMotion&#xff0c;这是一款功…

自然语言处理技术:NLP句法解析树与可视化方法

自然语言处理(Natural Language Processing,NLP)句法解析树是一种表示自然语言句子结构的图形化方式。它帮助将句子中的每个词汇和短语按照语法规则连接起来,形成一个树状结构,以便更好地理解句子的语法结构和含义。句法解析树对于理解句子的句法关系、依存关系以及语义角…

自动驾驶卡车量产-第一章-用户需求

1、中国干线物流行业现状 万亿级市场&#xff0c;规模巨大。由中重卡承运的干线运输占到整体公路货运市场的82%&#xff0c;全国中重卡保有量约730 万台1&#xff0c;市场规模达4.6 万亿元1&#xff0c;体量全球第一&#xff0c;超过同城物流及乘用出租市场规模之和。同样&…

机器学习与模式识别3(线性回归与逻辑回归)

一、线性回归与逻辑回归简介 线性回归主要功能是拟合数据&#xff0c;常用平方误差函数。 逻辑回归主要功能是区分数据&#xff0c;找到决策边界&#xff0c;常用交叉熵。 二、线性回归与逻辑回归的实现 1.线性回归 利用回归方程对一个或多个特征值和目标值之间的关系进行建模…

【Unity每日一记】资源加载相关你掌握多少?

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

pandas数据分析38——数据框表格拓展以及缩回对齐

案例背景 需求是这个样的&#xff1a; 把这个表格进行拓展。 代码实现&#xff1a; df pd.DataFrame(np.array([[1, 2, 3,4], [a,b, c,d], [小明,小红, 小马,小天]])) df 方法一&#xff1a;自定义函数&#xff1a; def expand_dataframe(df):m, n df.shapenew_df pd.Dat…

AutoSAR配置与实践(基础篇)2.4 RTE对Ports的支持 – C/S介绍

AutoSAR配置与实践(基础篇)2.4 RTE对Ports的支持 – C/S介绍 传送门 点击返回 ->AUTOSAR配置与实践总目录 <C/S篇前言> C/S接口算是内容稍多的章节,特别是异步C/S。因此打算分为两篇文章介绍。第一篇基础,第二篇深入。所介绍的内容,是结合Autosar标准文档作为基…

自动驾驶,一次道阻且长的远征|数据猿直播干货分享

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 在6月的世界人工智能大会上&#xff0c;马斯克在致辞中宣称&#xff0c;到2023年底&#xff0c;特斯拉便可实现L4级或L5级的完全自动驾驶&#xff08;FSD&#xff09;。两个月之后&#xff0c;马斯克又在X社交平台上发言&am…

Qt应用开发(基础篇)——选项卡窗口 QTabWidget

一、前言 QTabWidget类继承于QWidget&#xff0c;是一个拥有选项卡的窗口部件。 QTabWidget类有一个选项卡栏QTabBar和一个页面区域&#xff0c;用来显示和选项卡相关联的界面。用户通过点击选项卡或者自定义快捷方式(ALTKey)切换页面。 二、QTabWidget类 1、count 该属…

DockerFile的入门与使用

什么是 DockerFile&#xff1f; Dockerfile 是一个用来构建镜像的文本文件&#xff0c;文本内容包含了一条条构建镜像所需的指令和说明。 参考Tomcat的dockerFile文件 DockerFile指令 FROM 指定父镜像: 基于哪个镜像image构建 指定基础镜像&#xff0c;必须为第一个命令MAIN…

【深入理解C语言】-- 关键字2

&#x1f407; &#x1f525;博客主页&#xff1a; 云曦 &#x1f4cb;系列专栏&#xff1a;深入理解C语言 &#x1f4a8;吾生也有涯&#xff0c;而知也无涯 &#x1f49b; 感谢大家&#x1f44d;点赞 &#x1f60b;关注&#x1f4dd;评论 文章目录 前言一、关键字 - static&…

规则的加载与管理者——KieContainer的获取与其类型的区别(虽然标题是KieContainer,其实说的还是KieServices)

之前梳理了一下有关KieServices的获取&#xff0c;与获取中的代码走向&#xff0c;详情请见&#xff1a; “万恶”之源的KieServices&#xff0c;获取代码就一行&#xff0c;表面代码越少里面东西就越多&#xff0c;本以为就是个简单的工厂方法&#xff0c;没想到里面弯弯绕绕…

vue3跳转页面后 海康监控实例不销毁

第一个页面是这样的 跳转到新的页面 只有海康的监控没有消失 使用控制台审查元素也审查不到 解决方法&#xff1a;在vue3的销毁周期把海康的监控销毁掉 import { reactive, onDeactivated} from "vue"; const state reactive({oWebControl: null as any, //监控绑…

数字乡村三维可视化监控管理平台

数字乡村是伴随网络化、信息化和数字化在农业农村经济社会发展中的应用&#xff0c;既是乡村振兴的战略方向&#xff0c;也是建设数字中国的重要内容。为了进一步提升乡村治理智能化、专业化水平&#xff0c;解决建设顶层缺失、数据孤岛等问题&#xff0c;数字孪生技术被广泛应…

16.4 【Linux】特殊文件与程序

16.4.1 具有 SUID/SGID 权限的指令执行状态 SUID 的权限其实与程序的相关性非常的大&#xff01;为什么呢&#xff1f;先来看看 SUID 的程序是如何被一般使用者执行&#xff0c;且具有什么特色呢&#xff1f; SUID 权限仅对二进制程序&#xff08;binary program&#xff09;…

LL库实现SPI MDA发送方式驱动WS2812

1&#xff0c;首先打卡STM32CubeMX&#xff0c;配置一下工程&#xff0c;这里使用的芯片是STM32F030F4P6。 时钟 SPI外设 SPI DMA 下载接口&#xff0c;这个不配置待会下程序后第二次就不好下载调试了。 工程配置&#xff0c;没啥说的 选择生成所有文件 将驱动都改为LL库 然后直…

python知识:什么是字符编码?

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 我们的MySQL使用latin1的默认字符集&#xff0c; 也就是说&#xff0c;对汉字字段直接使用GBK内码的编码进行存储&#xff0c; 当需要对一些有汉字的字段进行拼音排序时&#xff08;特别涉及到类似于名字这样的字段时…

【Mysql 连接报错】

文章目录 遇到问题查看用户信息修改加密规则成功连入mysql 遇到问题 socket: auth failed …/…/lualib/skynet/socketchannel.lua:482: errno:1251, msg:Client does not support authentication protocol requested by server; consider upgrading MySQL client,sqlstate:080…

高效解决Anaconda Prompt报错Did not find VSINSTALLDIR这类问题

文章目录 回忆问题解决问题step1step2 回忆问题 类似于划红线部分然后还有很多行的报错信息&#xff0c;最后一行肯定是红色划线部分 解决问题 step1 找到 D:\Anaconda\envs\pytorch\etc\conda\activate.d在这个文件夹内会有两个文件&#xff0c;删除 vs2017_compiler_v…

Java版本+企业电子招投标系统源代码+支持二开+Spring cloud tbms

​ 项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以…