嵌入式 Telnet 调试 Shell 重构: 纯 POSIX 轻量化实现

背景

在嵌入式 Linux 产品开发中,telnet 调试 shell 是一个常见需求:通过网络连接到设备,执行诊断命令、查看运行状态、修改配置参数。本文为 C++17 header-only 纯 POSIX 实现的过程,最终产物是 telsh 项目。

方案

设计原则

  • C++17,零 boost 依赖,纯 POSIX socket
  • Header-only,方便嵌入式项目集成
  • 零堆分配(固定容量数组),适合资源受限环境
  • 每个 session 独立状态,无全局变量

架构

lua 复制代码
telsh/
  include/
    osp/                        -- 复用自 newosp 项目
      platform.hpp              -- 平台检测、断言
      vocabulary.hpp            -- FixedFunction、FixedString
      log.hpp                   -- 日志宏
    telsh/
      command_registry.hpp      -- 命令注册 (64 条, 零堆分配)
      telnet_session.hpp        -- 会话管理 (IAC/认证/历史)
      telnet_server.hpp         -- 服务器 (固定 session 池)
  tests/                        -- Catch2 测试 (28 cases)
  examples/                     -- 示例

关键设计决策

1. 统一命令签名

cpp 复制代码
//统一函数指针签名
using CmdFn = int (*)(int argc, char* argv[], void* ctx);

所有命令参数都是字符串 (argv),由命令自行解析类型。void* ctx 传递用户上下文,替代成员函数绑定。编译期类型安全,零堆分配。

注册接口:

cpp 复制代码
// 自由函数
registry.Register("reboot", "Reboot device", my_reboot_fn);

// 带上下文的回调 (替代成员函数绑定)
Counter counter;
registry.Register("count", "Show counter", count_fn, &counter);

// 宏自动注册
TELSH_CMD(hello, "Print greeting") {
    (void)ctx;
    telsh::TelnetServer::Printf("Hello, %s!\r\n", argv[1]);
    return 0;
}

2. Per-session IAC 状态机

cpp 复制代码
class TelnetSession {
  // IAC 状态是成员变量,不是全局 static
  enum class IacPhase : uint8_t { kNormal, kIac, kNego, kSub };
  struct IacState {
    IacPhase phase = IacPhase::kNormal;
    uint8_t prev_byte = 0;
  };
  IacState iac_;  // 每个 session 独立
};

3. 固定 session 池替代裸 new

cpp 复制代码
class TelnetServer {
  static constexpr uint32_t kMaxSessions = 8;
  struct SessionSlot {
    TelnetSession session;
    std::thread thread;
    std::atomic<bool> active{false};
  };
  SessionSlot slots_[kMaxSessions];  // 栈上固定数组
};
  • 不动态分配,session 数量编译期确定
  • std::thread joinable(不 detach),生命周期可控
  • Stop() 时关闭 socket 解除 recv() 阻塞,然后 join 所有线程

4. 命令行原地解析

cpp 复制代码
// ShellSplit: 原地修改 cmdline,返回 argc/argv
// 支持单引号、双引号
inline int ShellSplit(char* cmdline, char* argv[], int max_args);

不分配内存,不依赖 boost::split,直接在输入缓冲区上操作。

从 newosp 复用的组件

telsh 从 newosp 项目拷贝了 3 个 header-only 文件作为基础设施:

  • platform.hpp (169 行) -- 平台检测、OSP_ASSERT 宏、编译器提示
  • vocabulary.hpp (858 行) -- FixedFunctionFixedStringScopeGuard
  • log.hpp (391 行) -- OSP_LOG_INFO/WARN/ERROR 日志宏

这些文件无外部依赖,可以独立使用。这也是 newosp header-only 设计的优势:任何模块都可以单独拷贝到其他项目中使用。

使用示例

cpp 复制代码
#include "telsh/telnet_server.hpp"

// 用宏注册命令
TELSH_CMD(hello, "Print greeting") {
    telsh::TelnetServer::Printf("Hello!\r\n");
    return 0;
}

int main() {
    telsh::ServerConfig config;
    config.port = 2500;
    config.username = "admin";
    config.password = "1234";

    telsh::TelnetServer server(
        telsh::CommandRegistry::Instance(), config);
    server.Start();

    // ... 主循环 ...
    server.Stop();
}
bash 复制代码
$ telnet 127.0.0.1 2500
username: admin
password: ****
Login OK.
telsh> help
Available commands:
  hello            - Print greeting
telsh> hello
Hello!
telsh> exit
Bye.

项目地址:

相关推荐
橘色的喵1 小时前
ztask: 一个C++14编写的、 类型安全、RAII 与模板化任务调度器
c++
小龙报1 小时前
【51单片机】不止是调光!51 单片机 PWM 实战:呼吸灯 + 直流电机正反转 + 转速控制
数据结构·c++·stm32·单片机·嵌入式硬件·物联网·51单片机
彩妙不是菜喵1 小时前
C++:深入浅出讲解=>多态
开发语言·c++
lightqjx2 小时前
【C++】C++11 - Lambda表达式+包装器
开发语言·c++·c++11·lambda·包装器
载数而行5202 小时前
算法系列1之最小生成树
c语言·数据结构·c++·算法·贪心算法
额,不知道写啥。2 小时前
HAO的DP
c++·算法·深度优先·动态规划
我命由我123453 小时前
C++ EasyX 开发,MessageBox 函数参数问题:“const char *“ 类型的实参与 “LPCWSTR“ 类型的形参不兼容
c语言·开发语言·c++·后端·学习·visualstudio·visual studio
Trouvaille ~3 小时前
【Linux】高并发服务器的起点:五种 IO 模型与非阻塞 IO 本质解析
linux·运维·服务器·c++·操作系统·io模型·同步异步
ADDDDDD_Trouvaille3 小时前
2026.2.19——OJ89-91题
c++·算法