You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在httplib REST服务器中调用boost/asio IP命令失效问题求助

问题:httplib REST服务器启动后Boost.Asio命令无法执行

我用httplib搭建了REST服务器,尝试在其中发送Boost.Asio的IP命令,但服务器启动后命令完全无法执行。补充说明:在调用REST服务器的listen方法前,通过看门狗超时触发IP命令发送的方法能正常执行,一切正常,但服务器启动后就无法使用Boost.Asio了。我对Boost.Asio经验不足,对此十分困惑。

已尝试的方案:

  • 为REST服务器单独创建线程,期望实现与Boost.Asio函数的非阻塞
  • 为io_context单独创建线程
  • 在两种场景下均尝试使用mutex
  • 尝试使用Boost.Asio的异步写入方法
    但以上方案均未解决问题。

代码示例

#include <iostream>
#include <boost/asio.hpp>

#include "../include/httplib.h"
#include "../include/json.hpp"

using boost::asio::ip::tcp;

static boost::asio::io_context io_context;
static tcp::socket testSocket(io_context);
boost::system::error_code boost_error;

std::string testIP = "192.168.9.114";
std::string testPort = "20000";

int serverPort__ = 8077;
static httplib::Server server_;


namespace RestServer {
    std::string what(const std::exception_ptr &eptr = std::current_exception()) {

        if (!eptr) { throw std::bad_exception(); }

        try { std::rethrow_exception(eptr); }
        catch (const std::exception &e) { return e.what()   ; }
        catch (const std::string    &e) { return e          ; }
        catch (const char           *e) { return e          ; }
        catch (...)                     { return "who knows"; }
    }

    int main() {
        std::cout << "Starting REST server at port " << serverPort__ << std::endl;

        if (!server_.is_valid()) {
            std::cout << "Rest server has an error..." << std::endl;
            return -1;
        }

        server_.Get("/stop", [&](const httplib::Request &req, httplib::Response &res) {
            std::cout << "Rest server is stopping..." << std::endl;
            (void)req;
            (void)res;
            server_.stop();
        });

        server_.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
            const char *fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
            char buf[BUFSIZ];
            if (res.body.length() > 4) {
                snprintf(buf, sizeof(buf), "<p>Error Status: <span style='color:red;'>%d</span></p><p>Message: %s</p>", res.status, res.body.c_str());
            }
            else {
                snprintf(buf, sizeof(buf), fmt, res.status);
            }
            res.set_content(buf, "text/html");
        });

        server_.set_exception_handler([](const httplib::Request &req, httplib::Response &res, std::exception_ptr ep) {
            std::cout << "REST Exception!  " << what(ep) << std::endl;
        });

        server_.Get("/TEST", [](const httplib::Request &req, httplib::Response &res) {
            nlohmann::json jsonRes;
            
            std::string cmdStr = req.get_param_value("cmd");
            std::string  opStr = req.get_param_value("op");

            uint8_t operation = 0;
            uint8_t command = 0;

            try {
                operation = std::stoi(opStr);
                command = std::stoi(cmdStr);
            } 
            catch (std::exception &_) {
                res.body = "Bad operation/command/addr/numBytes";
                res.status = 400;
                return;
            }

            switch (operation) {
                case 0: {
                    switch (command) {
                        case 1: {
                            // example of boost/asio method done via rest
                            sendTestIPCommand(0x02, 0x01);
                        }
                    }
                }
                case 1: {
                    switch (command) {
                        case 1: {
                            // other read commands
                        }
                    }
                }
            }
            res.set_content(jsonRes.dump(), "application/json");
        });

        server_.listen("0.0.0.0", serverPort__);

        std::cout << "Quitting REST server at port" << serverPort__ << std::endl;

        return 0;
    }
}

void startTestSocket() {
    try {
        testSocket = tcp::socket(io_context);
        tcp::resolver signalLightResolver(io_context);
        tcp::resolver::results_type signalLightEndpoints = signalLightResolver.resolve(tcp::resolver::query(tcp::v4(), testIP, testPort));
        boost::asio::connect(testSocket, signalLightEndpoints);
    }
    catch (std::exception &e) {
        std::cerr << "Socket Exception: " << e.what() << std::endl;
    }
}

void sendTestIPCommand(uint8_t color, uint8_t mode) {
    unsigned char c_pIdataW[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    c_pIdataW[0] = 'W';
    c_pIdataW[color] = mode;

    auto start_time = std::chrono::steady_clock::now();

    try {
        while (true) {
            boost::asio::write(testSocket, boost::asio::buffer(c_pIdataW), boost_error);
            auto now = std::chrono::steady_clock::now();
            if (std::chrono::duration_cast<std::chrono::seconds>(now - start_time).count() > 3) {
                std::cout << "socket start break" << std::endl;
                break;
            }
        }
    }
    catch (std::exception &e) {
        std::cerr << "Socket Exception: " << e.what() << std::endl;
    }

    if (!boost_error) {
        std::cout << "starting signal light.." << std::endl;
    }
    else {
        std::cerr << "a socket error has occurred" << std::endl;
    }
}

int main() {
    startTestSocket();
    return RestServer::main();
}
原因分析
  1. IO上下文未被驱动:httplib的listen是阻塞式方法,会占用主线程。而Boost.Asio的io_context必须有线程调用run()才能处理IO事件——服务器启动前,同步的connect和write能执行是因为它们直接阻塞等待完成,但服务器启动后主线程被占用,io_context没有线程驱动,后续Boost.Asio操作无法推进。
  2. 同步操作阻塞REST请求:sendTestIPCommand中包含3秒的同步循环写入,在REST回调中执行会直接阻塞httplib的请求处理线程,导致请求超时甚至服务器无响应。
  3. 全局资源线程不安全:testSocket和io_context是全局静态变量,多线程场景下无同步保护,会引发竞态条件,导致未定义行为。
解决方案

1. 正确驱动Boost.Asio的io_context

为io_context创建独立线程,确保其始终运行:

// 在main函数中添加
std::thread io_thread([&]() {
    io_context.run(); // 线程会持续运行,直到io_context被停止
});
io_thread.detach(); // 若需优雅退出,可调用io_context.stop()后join线程

2. 将同步IO改为异步,避免阻塞REST线程

重构sendTestIPCommand为异步写入,利用Boost.Asio的回调机制:

void asyncSendTestIPCommand(uint8_t color, uint8_t mode) {
    auto buffer = std::make_shared<boost::asio::const_buffer>(
        [color, mode]() {
            unsigned char c_pIdataW[10] = {0};
            c_pIdataW[0] = 'W';
            c_pIdataW[color] = mode;
            return boost::asio::buffer(c_pIdataW);
        }()
    );
    auto start_time = std::chrono::steady_clock::now();

    auto writeHandler = [buffer, start_time](const boost::system::error_code& ec, std::size_t) {
        if (ec) {
            std::cerr << "Socket write error: " << ec.message() << std::endl;
            return;
        }
        auto now = std::chrono::steady_clock::now();
        if (std::chrono::duration_cast<std::chrono::seconds>(now - start_time).count() <= 3) {
            async_write(testSocket, *buffer, writeHandler);
        } else {
            std::cout << "Async write completed after 3s" << std::endl;
        }
    };

    async_write(testSocket, *buffer, writeHandler);
}

在REST回调中调用此异步版本,避免阻塞请求线程。

3. 封装全局资源,保证线程安全

将io_context、testSocket封装到类中,用互斥锁保护操作:

class SocketManager {
public:
    SocketManager(const std::string& ip, const std::string& port) 
        : ip_(ip), port_(port), socket_(io_context_) {
        io_thread_ = std::thread([&]() { io_context_.run(); });
    }

    ~SocketManager() {
        io_context_.stop();
        if (io_thread_.joinable()) io_thread_.join();
        if (socket_.is_open()) socket_.close();
    }

    void connect() {
        std::lock_guard<std::mutex> lock(mutex_);
        try {
            tcp::resolver resolver(io_context_);
            auto endpoints = resolver.resolve(ip_, port_);
            boost::asio::connect(socket_, endpoints);
        } catch (const std::exception& e) {
            std::cerr << "Connect error: " << e.what() << std::endl;
        }
    }

    void sendCommand(uint8_t color, uint8_t mode) {
        std::lock_guard<std::mutex> lock(mutex_);
        if (!socket_.is_open()) {
            std::cerr << "Socket not open, cannot send command" << std::endl;
            return;
        }
        asyncSendTestIPCommand(color, mode);
    }

private:
    boost::asio::io_context io_context_;
    std::thread io_thread_;
    tcp::socket socket_;
    std::string ip_;
    std::string port_;
    std::mutex mutex_;

    void asyncSendTestIPCommand(uint8_t color, uint8_t mode) {
        auto buffer = std::make_shared<boost::asio::const_buffer>(
            [color, mode]() {
                unsigned char c_pIdataW[10] = {0};
                c_pIdataW[0] = 'W';
                c_pIdataW[color] = mode;
                return boost::asio::buffer(c_pIdataW);
            }()
        );
        auto start_time = std::chrono::steady_clock::now();

        auto writeHandler = [this, buffer, start_time](const boost::system::error_code& ec, std::size_t) {
            std::lock_guard<std::mutex> lock(mutex_);
            if (ec) {
                std::cerr << "Write error: " << ec.message() << std::endl;
                return;
            }
            auto now = std::chrono::steady_clock::now();
            if (std::chrono::duration_cast<std::chrono::seconds>(now - start_time).count() <= 3) {
                async_write(socket_, *buffer, writeHandler);
            } else {
                std::cout << "Signal light command sent successfully" << std::endl;
            }
        };

        async_write(socket_, *buffer, writeHandler);
    }
};

在main中使用该类:

int main() {
    SocketManager socketManager("192.168.9.114", "20000");
    socketManager.connect();
    
    // 在REST回调中调用socketManager.sendCommand(0x02, 0x01);
    return RestServer::main();
}

4. 调整httplib运行方式(可选)

若需让httplib不阻塞主线程,可使用bind_to_port配合run_in_background:

// 在RestServer::main中替换listen调用
server_.bind_to_port("0.0.0.0", serverPort__);
server_.run_in_background();
// 主线程可处理其他逻辑,比如等待退出信号

内容的提问来源于stack exchange,提问作者Grayson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 23:53:09