【实战指南】从零构建嵌入式远程Shell,提升跨地域协作效率(2

2024-10-14  本文已影响0人  开源519

【实战指南】从零构建嵌入式远程Shell,提升跨地域协作效率(2)

[TOC]

引言

  之前曾发布过一篇关于构建嵌入式远程Shell的文章,详细介绍了基础版本的实现方法,详见【实战指南】从零构建嵌入式远程Shell。然而,该版本在功能性和稳定性方面还有待提升。本文将在此基础上进行改进和优化,进一步完善远程Shell的功能。

概述

  本次改进主要集中在模块化设计与功能增强上。模块化方面,我们将系统拆分为LogManagerShellEnv两个独立的类,以提高代码的可维护性和复用性。在功能上,新增了对阻塞命令的支持以及提供用户主动结束Shell进程的能力等。

优化策略

  在第一版代码中,由于功能较为简单,未采用模块化设计;同时,其实现也相对单一,仅支持非阻塞命令的执行。基于此,第二版实现中增加了以下功能:

详细设计

  将远程Shell拆解为LogManagerShellEnv两大模块。其中,LogManager负责与远端的连接、shell进程管理等;而ShellEnv则专注于Shell命令的执行及其回执输出。

  1. LogManager
    LogManager 主要负责与远程端口的连接以及 shell 进程的管理功能。具体职责包括:

    ① 建立TCP连接。目前仅支持作为TCP服务端运行,允许远程客户端以客户端模式接入并输入指令。
    ② 解析远程指令。接收来自远程客户端的指令字节流,解析并判断其是否属于内建指令。
    ③ 增加内建指令集。增加一系列与RShellX使用相关的内建指令,提升工具的易用性和功能性。
    ④ 触发指令执行。对于内建指令直接进行处理;而对于其他外部命令,则交由ShellEnv模块执行。
    ⑤ 回收Shell进程。通过注册SIGCHLD信号处理函数,自动回收已完成任务的Shell子进程,确保系统资源的有效管理。

class LoginManager
{
public:
    LoginManager();
    ~LoginManager();
    static LoginManager* GetInstance();

    int Init();
    int BuildConnectAsTcpServer(short port);
    int ConnectLoop();

private:
    int Usage();
    int ExitShell();
    int ExitAll();
    int WriteStdin(const std::string& buf);
    int RegisterSignal();
    int ListenPipeEvent(int pipeFd);
    int ExecuteCmd(std::string& cmdBytes);
    // int Login(const char* username, const char* password);
    // int Logout();

private:
    bool mIsLogin;
    pid_t mCurPid;
    static pid_t mShellPid;
    int mStdin;
    int mStdout;
    int mStderr;
    int mInPipe[2];
    int mOutPipe[2];
    std::shared_ptr<PPipe> mPipePtr;
    std::shared_ptr<PSocket> mTcpSrvPtr;
    std::list<std::shared_ptr<PSocket>> mTcpClients;
};

LogManager 的接口设计,亦可以非常直观地理解其承担的主要职责。其中,一些细节的实现如下记录:

int LoginManager::BuildConnectAsTcpServer(short port)
{
    auto pEpoll = EpollEventHandler::GetInstance();
    mTcpSrvPtr = make_shared<PSocket>(AF_INET, SOCK_STREAM, 0, [&](int cli, void *arg) {
        PSocket* pSrvObj = (PSocket*)arg;
        if (pSrvObj == nullptr) {
            SPR_LOGE("PSocket is nullptr\n");
            return;
        }

        auto tcpClient = make_shared<PSocket>(cli, [&](int sock, void *arg) {
            PSocket* pCliObj = (PSocket*)arg;
            if (pCliObj == nullptr) {
                SPR_LOGE("PSocket is nullptr\n");
                return;
            }

            std::string rBuf;
            int rc = pCliObj->Read(sock, rBuf);
            if (rc <= 0) {
                mTcpClients.remove_if([sock, pEpoll, pCliObj](shared_ptr<PSocket>& v) {
                    pEpoll->DelPoll(pCliObj);
                    return (v->GetEpollFd() == sock);
                });
                return;
            }

            // SPR_LOGD("# RECV [%d]> %s shellpid = %d\n", sock, rBuf.c_str(), mShellPid);
            ExecuteCmd(rBuf);
        });

        tcpClient->AsTcpClient();
        pEpoll->AddPoll(tcpClient.get());
        mTcpClients.push_back(tcpClient);
        dup2(mTcpSrvPtr->GetEpollFd(), STDIN_FILENO);
        dup2(tcpClient->GetEpollFd(), STDOUT_FILENO);
        dup2(tcpClient->GetEpollFd(), STDERR_FILENO);

        const string welcomes = "Welcome to RShellX! >_<\n";
        if (write(STDOUT_FILENO, welcomes.c_str(), welcomes.size()) < 0) {
            SPR_LOGE("# Write welcome failed! %s", strerror(errno));
        }
    });

    mTcpSrvPtr->AsTcpServer(port, 5);
    pEpoll->AddPoll(mTcpSrvPtr.get());
    return 0;
}
int LoginManager::ExecuteCmd(string& cmdBytes)
{
    cmdBytes.erase(std::find_if(cmdBytes.rbegin(), cmdBytes.rend(), [](unsigned char ch) {
        return ch != '\r' && ch != '\n';
    }).base(), cmdBytes.end());

    if (cmdBytes == "Quit" || cmdBytes == "Ctrl C" || cmdBytes == "Ctrl Q") {
        return ExitShell();
    } else if (cmdBytes == "Quit all") {
        return ExitAll();
    } else if (cmdBytes == "Help" || cmdBytes == "help" || cmdBytes == "?") {
        return Usage();
    }

    // 上一个命令未执行完,输入作为参数传入上一个命令
    if (mShellPid > 0) {
        SPR_LOGD("Last shell %d is running, send [%s] as parameter\n", mShellPid, cmdBytes.c_str());
        // int rc = write(mInPipe[1], cmdBytes.c_str(), cmdBytes.size());
        // if (rc > 0) {
        //     SPR_LOGD("# SEND [%d]> %s\n", mInPipe[1], cmdBytes.c_str());
        // }
        return 0;
    }

    ShellEnv shellEnv(mInPipe[0], mOutPipe[1], mOutPipe[1]);
    mShellPid = shellEnv.Execute(cmdBytes);
    return 0;
}
int LoginManager::RegisterSignal()
{
    signal(SIGCHLD, [](int) {
        pid_t pid;
        int status;
        while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
            mShellPid = -1;
            SPR_LOGD("Shell [%d] exit!\n", pid);
        }
    });
    return 0;
}
  1. ShellEnv
    ShellEnv的功能比较简单,即创建子进程,并执行shell指令。
class ShellEnv
{
public:
    explicit ShellEnv(int inFd, int outFd, int errFd);
    ~ShellEnv();

    int Execute(const std::string& cmd);
};

ShellEnv 中的 Execute 方法用于执行 shell 命令,其实现如下:

int ShellEnv::Execute(const std::string& cmd)
{
    size_t pos = cmd.find(' ');
    std::string cmdName = cmd.substr(0, pos);
    std::vector<std::string> tmpArgs = GeneralUtils::Split(cmd, ' ');
    char* args[tmpArgs.size() + 1];
    args[tmpArgs.size()] = nullptr;
    for (size_t i = 0; i < tmpArgs.size(); i++) {
        args[i] = const_cast<char*>(tmpArgs[i].c_str());
    }

    pid_t pid = fork();
    if (pid == 0) {
        int rc = execvp(cmdName.c_str(), args);
        if (rc < 0) {
            SPR_LOGE("execlp failed: %s\n", strerror(errno));
        }
        _exit(EXIT_FAILURE);
    } else {
        SPR_LOGD("ppid = %d, cpid = %d cmd: %s\n", getpid(), pid, cmd.c_str());
    }

    return pid;
}

验证

准备三个终端输入窗口,方便调试验证。

$ ./rshellx 8080
tcp客户端
$ echo "Hello" >> test.log
$ echo "I'm kangkang" >> test.log
$ echo "What's your name?" >> test.log

总结

上一篇 下一篇

猜你喜欢

热点阅读