在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(); }
原因分析
- IO上下文未被驱动:httplib的
listen是阻塞式方法,会占用主线程。而Boost.Asio的io_context必须有线程调用run()才能处理IO事件——服务器启动前,同步的connect和write能执行是因为它们直接阻塞等待完成,但服务器启动后主线程被占用,io_context没有线程驱动,后续Boost.Asio操作无法推进。 - 同步操作阻塞REST请求:
sendTestIPCommand中包含3秒的同步循环写入,在REST回调中执行会直接阻塞httplib的请求处理线程,导致请求超时甚至服务器无响应。 - 全局资源线程不安全:
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
相关产品推荐
相关产品推荐

