Skip to content

5. 断开连接

本期将介绍上位机如何主动断开与控制器建立的 SDK 连接,并分别演示正常退出和业务异常退出两种场景下的连接清理方式。

程序通过 connect_robot 成功连接控制器后,会获得一个连接句柄 fd。当业务执行结束或发生可捕获异常时,应在程序退出前显式调用 disconnect_robot,释放本次连接使用的通信资源。

本 Demo 支持以下两种运行方式:

text
demo.exe             正常退出,在正常业务路径中显式断开连接
demo.exe --abnormal  模拟业务异常,在异常处理路径中显式断开连接

--abnormal 只用于模拟上层业务抛出异常,不表示 SDK 连接本身发生异常。两种场景都会真实连接控制器,也都会真实调用 disconnect_robot

⚠️ 使用注意

本 Demo 只演示建立和断开 SDK 连接,不会发送机器人运动指令。使用时仍需注意:

  • 连接配置: 控制器 IP 地址和 SDK 服务端口必须与实际配置一致
  • 显式清理: 正常路径和可捕获异常路径都应在退出前调用断开接口
  • 返回结果: 不能因为程序即将退出就忽略 disconnect_robot 的返回值
  • 运动安全: 断开 SDK 连接不等于急停、伺服下电或运动停止,不能将其作为安全停止手段
  • 不可恢复场景: 断电、进程被强制结束或操作系统异常终止时,程序无法继续执行,因此无法保证调用 SDK 断开接口

实际项目应将连接清理纳入统一的资源管理流程,同时通过急停、安全回路和控制器自身机制保障机器人运动安全。

1、配置控制器连接参数

首先配置控制器 IP 地址和 SDK 服务端口。程序会使用这两个参数建立真实连接,因此运行前必须确认目标控制器无误。

cpp
/**
 * @name 用户必须根据运行环境修改
 * 以下配置决定 SDK 能否连接到实际控制器,运行前必须核对。
 * @{
 */
const std::string robot_ip = "192.168.3.243";  ///< 用户实际控制器的 IP 地址。
const std::string robot_port = "6001";         ///< 用户实际控制器的 SDK 服务端口,必须与控制器配置一致。
/** @} */

connect_robot 返回的是连接句柄,而不是普通的 Result。因此,程序通过判断句柄是否大于 0 来确认连接是否建立成功。只有获得有效句柄后,才能调用 disconnect_robot

2、封装控制器连接断开函数

正常业务结束、标准异常和未知异常等退出路径都需要执行相同的断开操作。程序将断开逻辑封装为 disconnect_controller,统一调用 SDK 接口、检查返回值并输出当前退出路径的处理结果。

cpp
/**
 * @brief 主动断开控制器连接,并统一检查 SDK 返回结果。
 * @param[in] fd 控制器连接句柄。
 * @param[in] exitPath 当前退出路径的说明文字,用于输出处理结果。
 * @retval true 控制器连接已成功断开。
 * @retval false SDK 返回失败,或断开过程中捕获到异常。
 *
 * @details
 * `noexcept` 保证清理函数不会继续传播异常,使正常和异常退出路径都能稳定结束。
 */
bool disconnect_controller(
    SOCKETFD fd,
    const std::string& exitPath) noexcept
{
    try
    {
        // 真实调用 SDK 断开接口并检查返回值,不能因程序即将退出就假定连接已关闭。
        const Result result = disconnect_robot(fd);
        if (result != SUCCESS)
        {
            std::cerr << exitPath << ":关闭控制器连接失败,错误码:"
                      << static_cast<int>(result) << std::endl;
            return false;
        }

        std::cout << exitPath << ":控制器连接已关闭" << std::endl;
        return true;
    }
    catch (const std::exception& error)
    {
        // SDK 若抛出标准异常,在此转换为 false,由调用方统一返回失败退出码。
        std::cerr << exitPath << ":disconnect_robot 抛出异常:"
                  << error.what() << std::endl;
        return false;
    }
    catch (...)
    {
        // 未知异常也在此截断,防止断开操作破坏原有异常处理流程。
        std::cerr << exitPath << ":disconnect_robot 抛出未知异常" << std::endl;
        return false;
    }
}

函数使用 exitPath 区分“正常退出路径”和“异常退出路径”,便于从日志中判断本次断开操作发生在哪个阶段。

3、定义退出场景并封装参数解析函数

为了明确区分正常退出和异常退出,程序使用 ExitScenario 枚举表示本次需要运行的演示场景。

cpp
/**
 * @brief Demo 支持的退出场景。
 */
enum class ExitScenario
{
    normal,    ///< 正常业务路径主动断开连接。
    abnormal   ///< 模拟业务异常后主动断开连接。
};

使用枚举而不是普通整数或字符串,可以让场景含义更加清晰,并避免在后续业务函数中重复比较命令行文本。

命令行参数由 parse_scenario 统一解析:不传参数时选择正常场景,只传入 --abnormal 时选择异常场景,其他参数均视为无效。

单独封装参数解析函数,是为了在连接控制器之前完成输入校验。参数无效时程序可以直接退出,避免先建立真实控制器连接,再发现本次运行方式无法识别。

cpp
/**
 * @brief 解析命令行参数并确定本次 Demo 的退出场景。
 * @param[in] argc 命令行参数数量。
 * @param[in] argv 命令行参数数组。
 * @param[out] scenario 解析成功后保存选择的退出场景。
 * @retval true 参数有效,已确定退出场景。
 * @retval false 参数无效,不应继续连接控制器。
 */
bool parse_scenario(
    int argc,
    char* argv[],
    ExitScenario& scenario)
{
    if (argc == 1)
    {
        // 未传入参数时,默认演示正常主动断开流程。
        scenario = ExitScenario::normal;
        return true;
    }

    if (argc == 2 && std::string(argv[1]) == "--abnormal")
    {
        // --abnormal 只选择异常测试路径,不表示 SDK 连接本身已经异常。
        scenario = ExitScenario::abnormal;
        return true;
    }

    std::cerr << "参数错误。用法:demo.exe [--abnormal]" << std::endl;
    return false;
}

参数解析结果如下:

  • demo.exe:选择 ExitScenario::normal
  • demo.exe --abnormal:选择 ExitScenario::abnormal
  • 其他参数:输出正确用法并返回失败,不连接控制器

4、封装正常和异常断开演示函数

run_disconnect_demo 根据选择的场景执行正常业务路径或模拟异常路径。

cpp
/**
 * @brief 执行选定的正常或异常断开演示场景。
 * @param[in] fd 控制器连接句柄。
 * @param[in] scenario 本次需要执行的退出场景。
 * @return 正常路径成功断开连接时返回 0;正常路径断开失败时返回 1。
 * @throws std::runtime_error 当选择异常场景时,抛出模拟业务异常。
 */
int run_disconnect_demo(SOCKETFD fd, ExitScenario scenario)
{
    if (scenario == ExitScenario::abnormal)
    {
        // 故意抛出可捕获的业务异常,用于验证 catch 路径仍会关闭真实连接。
        std::cout << "开始模拟业务异常" << std::endl;
        throw std::runtime_error("模拟异常退出");
    }

    std::cout << "开始执行正常断开流程" << std::endl;

    // 正常路径在返回前主动关闭连接,断开失败时使用非零退出码报告结果。
    if (!disconnect_controller(fd, "正常退出路径"))
    {
        return 1;
    }

    return 0;
}

正常场景会直接调用 disconnect_controller,断开成功时返回 0,断开失败时返回 1。

异常场景不会在此函数内断开连接,而是故意抛出 std::runtime_error。异常会返回 main 中的 catch 路径,由异常处理代码统一调用 disconnect_controller,用于验证异常退出时的真实连接清理过程。

5、主程序连接控制器并处理退出路径

主函数首先解析命令行参数,参数有效时才连接控制器。连接成功后的场景执行代码全部放入 try 中,保证可捕获的标准异常和未知异常都能进入对应的清理路径。

cpp
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
#include <cpp_interface/nrc_interface.h>
#include "../demo_utils.h"

/**
 * @brief Demo 程序入口:解析退出场景、连接控制器并验证显式断开流程。
 * @param[in] argc 命令行参数数量。
 * @param[in] argv 命令行参数数组。
 * @return 正常退出且断开成功时返回 0;参数无效、连接失败、业务异常或断开失败时返回 1。
 */
int main(int argc, char* argv[])
{
    demo::enable_console_utf8();

    ExitScenario scenario = ExitScenario::normal;
    if (!parse_scenario(argc, argv, scenario))
    {
        return 1;
    }

    // connect_robot 返回连接句柄而非 Result,因此通过句柄是否有效判断连接结果。
    SOCKETFD fd = connect_robot(robot_ip, robot_port);
    if (fd <= 0)
    {
        std::cerr << "控制器连接失败" << std::endl;
        return 1;
    }
    std::cout << "控制器连接成功" << std::endl;

    // 连接成功后的场景代码全部放入 try,确保可捕获异常都进入显式断开路径。
    try
    {
        return run_disconnect_demo(fd, scenario);
    }
    catch (const std::exception& error)
    {
        std::cerr << "捕获业务异常:" << error.what() << std::endl;

        // 异常路径在返回前显式关闭真实控制器连接。
        if (!disconnect_controller(fd, "异常退出路径"))
        {
            return 1;
        }

        // 连接关闭成功不改变业务发生异常的事实,因此保持非零退出码。
        return 1;
    }
    catch (...)
    {
        // 未知异常执行相同清理策略,避免新增异常类型时遗漏断开连接。
        std::cerr << "捕获未知异常" << std::endl;
        if (!disconnect_controller(fd, "异常退出路径"))
        {
            return 1;
        }

        return 1;
    }
}

6、正常退出流程

不传入命令行参数时,程序执行正常主动断开流程:

  1. parse_scenario 将场景设置为 ExitScenario::normal
  2. connect_robot 建立与控制器的真实连接。
  3. run_disconnect_demo 进入正常业务路径。
  4. disconnect_controller 调用 disconnect_robot 主动断开连接。
  5. 断开成功时程序返回 0;断开失败时返回 1。

正常运行命令如下:

text
demo.exe

正常路径预期输出类似:

text
控制器连接成功
开始执行正常断开流程
正常退出路径:控制器连接已关闭

7、异常退出流程

传入 --abnormal 时,程序模拟业务执行过程中发生可捕获异常:

  1. parse_scenario 将场景设置为 ExitScenario::abnormal
  2. 程序与控制器建立真实连接。
  3. run_disconnect_demo 主动抛出 std::runtime_error
  4. main 中的标准异常分支捕获该异常。
  5. 异常处理路径调用 disconnect_controller 断开真实连接。
  6. 即使连接成功关闭,程序仍返回 1,以表示本次业务执行发生异常。

异常测试命令如下:

text
demo.exe --abnormal

异常路径预期输出类似:

text
控制器连接成功
开始模拟业务异常
捕获业务异常:模拟异常退出
异常退出路径:控制器连接已关闭

异常场景的非零退出码用于保留“业务执行失败”的事实,不能因为资源已经成功清理就将本次运行报告为成功。

需要注意,本 Demo 只能处理程序仍有机会执行代码的正常退出和可捕获异常。对于突然断电、进程被操作系统强制结束或不可恢复的进程崩溃,应用程序无法继续运行清理代码,因此不能依赖本示例保证这些情况下仍会调用 SDK 断开接口。