【实战指南】从零构建嵌入式远程Shell,提升跨地域协作效率
引言
在嵌入式软件开发的测试验收环节,跨地域协作构成了一项显著挑战,尤其体现在远程调试过程中。当前流程通常要求研发人员依赖测试团队在目标设备上执行特定指令,这一协作模式耗时耗力。鉴于嵌入式系统的资源限制,部署第三方远程控制工具往往不可行。因此,构建一个轻量级远程Shell解决方案成为迫切需求。
概述
远程Shell工具为研发人员提供了在本地环境中无缝连接至目标设备的能力,实现这一功能的核心在于能够远程输入Shell
命令并实时获取执行结果。
技术实现层面,该工具主要依托于socket
通信机制与popen
函数。socket
用于建立两端之间的网络连接,确保数据包的可靠传输;popen
则负责在目标设备上执行命令,并捕获输出结果。两者协同工作,共同构建了一个稳定高效的远程交互平台。
需求分析
以研发调试的角度,对于远程shell
功能主要概括如下:
- 支持远程交互
- 创建服务端口,支持多客户端并发连接及交互。
- 允许客户端通过网络进行远程接入,实现双向通信。
- 支持命令执行与反馈
- 接收并解析客户端发送的指令,在本地
Shell
环境中准确执行。 - 执行结果需实时反馈给对应的客户端,确保信息传输的及时性与准确性。
- 确保稳定性与安全性(*)
- 在网络环境不稳定的情况下,系统应具备保持连接稳定性的能力,减少断线重连次数。
- 实施数据加密等安全措施,保护传输过程中的信息不被第三方截获或篡改,确保数据安全。
[1] 注:* 表示非强制需求。
设计方案
基于上述需求分析,以下是设计方案的大致流程:
- Socket服务端架构构建
- ① 初始化
Socket
服务端:创建并绑定一个监听端口,确保其能接收来自多个客户端的连接请求。 - ② I/O多路复用机制部署:采用高效事件驱动模型(如epoll),实现对多客户端并发请求的无阻塞监听与响应。
- ③ 自动化客户端管理:开发机制以监控客户端状态,实现异常断开连接的自动识别与资源释放。
- 客户端指令处理与反馈
- ① 实时数据接收与解析:设计模块化解析器,准确解读客户端发送的命令数据包。
- ② 命令执行集成:利用popen或更安全的fork+exec组合,安全执行客户端提交的系统级命令。
- ③ 结果封装与回传:构建反馈通道,将执行结果编码为数据流,通过Socket连接高效回传至对应客户端。
- 安全性
本程序仅为调试用途,仅限授权调试人员使用,安全性暂不考虑。
实现细节
实现主要聚焦于构建一个基于Socket
的服务端程序,其核心功能包括监听客户端连接请求、接收并解析客户端数据、处理接收到的信息以及向客户端提供响应反馈。
特别值得注意的是,在处理客户端发送的数据时,需过滤包含的\r\n
字符序列,这是由于使用Telnet
等工具进行输入时,数据中会自动附加此类字符。
- 代码实现
篇幅有限,只列举主要的业务代码。其中包含的通用工具代码省略,可在文末查看获取完整代码方式。
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; }
测试
- 在目标设备启动服务端程序rshellx
在目标设备启动rshellx
,并输入端口号。
$ ./rshellx 8080 76 EpollEvent D: Add epoll fd 4
- 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
- Windwos网络助手测试
总结
- 远程
shell
很方便跨地域调试。由于涉及到安全问题,它应该仅在调试阶段被使用。由指定人员在目标设备手动拉起,然后研发再远程连接。 - 当前
rshellx
仅实现了客户端命令透传功能,后续还可以扩展指令集,方便通过指令查看设备对应的状态、数据等关键信息。 - 在开发过程中,一些轻便的小工具对日常调试的作用帮助很大,但是由于其不作为客户需求,往往不被重视。一个正式、完整的系统应该具备齐全的调试工具,其对于日常调试的帮助很大。