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

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 构建嵌入式远程Shell的文章概述:- 目标:解决嵌入式软件测试中的远程调试难题,提供轻量级解决方案。- 功能:包括远程交互、命令执行与反馈,强调多客户端并发连接和稳定性。- 设计:基于Socket服务端架构,使用I/O多路复用和popen函数,确保命令执行与结果反馈。- 需求:支持命令解析、执行和结果回传,考虑网络不稳定情况下的连接保持。- 安全性:仅限内部调试,未详细讨论加密等安全措施。- 实现:关注点在Socket服务端程序,包括监听、数据过滤和命令处理。- 测试:通过 Telnet 和Windows网络助手验证连接和命令执行功能。

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

引言

  在嵌入式软件开发的测试验收环节,跨地域协作构成了一项显著挑战,尤其体现在远程调试过程中。当前流程通常要求研发人员依赖测试团队在目标设备上执行特定指令,这一协作模式耗时耗力。鉴于嵌入式系统的资源限制,部署第三方远程控制工具往往不可行。因此,构建一个轻量级远程Shell解决方案成为迫切需求。

概述

  远程Shell工具为研发人员提供了在本地环境中无缝连接至目标设备的能力,实现这一功能的核心在于能够远程输入Shell命令并实时获取执行结果。

  技术实现层面,该工具主要依托于socket通信机制与popen函数。socket用于建立两端之间的网络连接,确保数据包的可靠传输;popen则负责在目标设备上执行命令,并捕获输出结果。两者协同工作,共同构建了一个稳定高效的远程交互平台。

需求分析

  以研发调试的角度,对于远程shell功能主要概括如下:

  1. 支持远程交互
  • 创建服务端口,支持多客户端并发连接及交互。
  • 允许客户端通过网络进行远程接入,实现双向通信。
  1. 支持命令执行与反馈
  • 接收并解析客户端发送的指令,在本地Shell环境中准确执行。
  • 执行结果需实时反馈给对应的客户端,确保信息传输的及时性与准确性。
  1. 确保稳定性与安全性(*)
  • 在网络环境不稳定的情况下,系统应具备保持连接稳定性的能力,减少断线重连次数。
  • 实施数据加密等安全措施,保护传输过程中的信息不被第三方截获或篡改,确保数据安全。

[1] 注:* 表示非强制需求。

设计方案

  基于上述需求分析,以下是设计方案的大致流程:

  1. Socket服务端架构构建
  • ① 初始化Socket服务端:创建并绑定一个监听端口,确保其能接收来自多个客户端的连接请求。
  • ② I/O多路复用机制部署:采用高效事件驱动模型(如epoll),实现对多客户端并发请求的无阻塞监听与响应。
  • ③ 自动化客户端管理:开发机制以监控客户端状态,实现异常断开连接的自动识别与资源释放。
  1. 客户端指令处理与反馈
  • ① 实时数据接收与解析:设计模块化解析器,准确解读客户端发送的命令数据包。
  • ② 命令执行集成:利用popen或更安全的fork+exec组合,安全执行客户端提交的系统级命令。
  • ③ 结果封装与回传:构建反馈通道,将执行结果编码为数据流,通过Socket连接高效回传至对应客户端。
  1. 安全性
    本程序仅为调试用途,仅限授权调试人员使用,安全性暂不考虑。

实现细节

  实现主要聚焦于构建一个基于Socket的服务端程序,其核心功能包括监听客户端连接请求、接收并解析客户端数据、处理接收到的信息以及向客户端提供响应反馈。

  特别值得注意的是,在处理客户端发送的数据时,需过滤包含的\r\n字符序列,这是由于使用Telnet等工具进行输入时,数据中会自动附加此类字符。

  1. 代码实现
    篇幅有限,只列举主要的业务代码。其中包含的通用工具代码省略,可在文末查看获取完整代码方式。
int main(int argc, const char *argv[])
{
    if (argc < 2) {
        SPR_LOGE("Usage: ./rshellx <port>\n");
        return -1;
    }
    short port = atoi(argv[1]);
    if (port <= 0) {
        SPR_LOGE("Invalid port: %d\n", port);
        return -1;
    }
    auto pEpoll = make_shared<EpollEventHandler>();
    std::list<std::shared_ptr<PSocket>> clients;
    auto tcpServer = 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;
            }
            // Mimics the input display format of a remote terminal session.
            // Example:
            // # pwd
            // /tmp/Release/Bin
            std::string rBuf;
            int rc = pCliObj->Read(sock, rBuf);
            if (rc > 0) {
                SPR_LOGD("# RECV [%d]> %s\n", sock, rBuf.c_str());
                rBuf.erase(std::find_if(rBuf.rbegin(), rBuf.rend(), [](unsigned char ch) {
                    return ch != '\r' && ch != '\n';
                }).base(), rBuf.end());
                std::string out;
                int result = GeneralUtils::SystemCmd(out, rBuf.c_str());
                if (result == -1) {
                    out = "Invaild parameter! CMD: " + rBuf;
                }
                if (out.empty()) {
                    out = "No return. CMD: " + rBuf;
                }
                out += "\r\n# ";
                rc = pCliObj->Write(sock, out);
                if (rc > 0) {
                    // SPR_LOGD("# SEND [%d]> %s\n", sock, out.c_str());
                } else {
                    SPR_LOGE("# SEND failed!\n");
                }
            }
            if (rc <= 0) {
                clients.remove_if([sock, pEpoll, pCliObj](shared_ptr<PSocket>& v) {
                    pEpoll->DelPoll(pCliObj);
                    return (v->GetEpollFd() == sock);
                });
            }
        });
        tcpClient->AsTcpClient();
        pEpoll->AddPoll(tcpClient.get());
        clients.push_back(tcpClient);
    });
    tcpServer->AsTcpServer(port, 5);
    pEpoll->AddPoll(tcpServer.get());
    pEpoll->EpollLoop(true);
    return 0;
}

测试

  1. 在目标设备启动服务端程序rshellx
    在目标设备启动rshellx,并输入端口号。
$ ./rshellx 8080
  76 EpollEvent D: Add epoll fd 4
  1. Linux终端Telnet测试
    在本地终端通过Telnet工具连接远程终端rshellx
~$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
ls
bindermanagersrv
debugbinder
debugcore
debugmsg
debugsrv
default.prop
infrawatch
init.conf
logmanagersrv
logshow
mediatorsrv
powermanagersrv
propertiessrv
property_get
property_set
rshellx
sample_sqlite
sample_tcpclient
sample_tcpserver
servicemanagersrv
sparrowsrv
system.prop
vendor.prop
# pwd
/home/dx/hdd1/Gitee/Sparrow/Release/Bin
# lsb_release -a
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.2 LTS
Release:        22.04
Codename:       jammy
  1. Windwos网络助手测试


总结

  • 远程shell很方便跨地域调试。由于涉及到安全问题,它应该仅在调试阶段被使用。由指定人员在目标设备手动拉起,然后研发再远程连接。
  • 当前rshellx仅实现了客户端命令透传功能,后续还可以扩展指令集,方便通过指令查看设备对应的状态、数据等关键信息。
  • 在开发过程中,一些轻便的小工具对日常调试的作用帮助很大,但是由于其不作为客户需求,往往不被重视。一个正式、完整的系统应该具备齐全的调试工具,其对于日常调试的帮助很大。
相关文章
|
1月前
|
Shell
shell脚本实战示例
shell脚本实战示例
65 6
|
1月前
|
Ubuntu Linux Shell
【Linux操作系统】探秘Linux奥秘:shell 编程的解密与实战
【Linux操作系统】探秘Linux奥秘:shell 编程的解密与实战
84 0
|
1月前
|
Shell 索引
shell脚本入门到实战(四)- 数组
shell脚本入门到实战(四)- 数组
|
1月前
|
Shell
shell脚本入门到实战(三) - 变量
shell脚本入门到实战(三) - 变量
|
1月前
|
Shell Linux 人机交互
shell脚本入门到实战(二)--shell输入和格式化输出
shell脚本入门到实战(二)--shell输入和格式化输出
136 0
|
9月前
|
Shell 测试技术
Shell编程实战的命令测试
Shell编程实战的命令测试
40 1
|
1月前
|
分布式计算 Java 大数据
【大数据技术Hadoop+Spark】HDFS Shell常用命令及HDFS Java API详解及实战(超详细 附源码)
【大数据技术Hadoop+Spark】HDFS Shell常用命令及HDFS Java API详解及实战(超详细 附源码)
464 0
|
9月前
|
监控 Shell 数据处理
shell编程实战需求分析
shell编程实战需求分析
59 1
|
1月前
|
监控 Shell
Shell脚本实战教学
Shell脚本实战教学
40 5
|
1月前
|
Shell 应用服务中间件 Linux
shell脚本入门到实战(一)
shell脚本入门到实战(一)