- FC 控制指令域修正:payload 14→18 字节,纵倾/横倾姿态拆分为数据1/数据2 - WebServer 广播改为待发队列模式,修复 mongoose 跨线程调用问题 - build-board.sh 增加板卡组网说明(/23 掩码方案)、pCCU 服务部署与 mission 同步 - 同步 0824 协议文档更新
144 lines
4.4 KiB
C++
144 lines
4.4 KiB
C++
#include "WebServer.h"
|
|
#include "pages/index.h"
|
|
#include <iostream>
|
|
|
|
namespace ccu {
|
|
|
|
//---------------------------------------------------------
|
|
// 事件处理:HTTP 路由 + WebSocket 收发
|
|
|
|
void WebServer::handleEvent(struct mg_connection* c, int ev, void* ev_data) {
|
|
if (ev == MG_EV_HTTP_MSG) {
|
|
struct mg_http_message* hm = (struct mg_http_message*)ev_data;
|
|
|
|
// WebSocket 升级
|
|
if (mg_match(hm->uri, mg_str("/ws"), NULL)) {
|
|
mg_ws_upgrade(c, hm, NULL);
|
|
return;
|
|
}
|
|
|
|
// /api/* 接口:交给宿主程序处理,返回 JSON
|
|
std::string path(hm->uri.buf, hm->uri.len);
|
|
if (path.rfind("/api/", 0) == 0) {
|
|
std::string body;
|
|
if (m_apiHandler) {
|
|
std::string query(hm->query.buf, hm->query.len);
|
|
body = m_apiHandler(path, query);
|
|
}
|
|
if (!body.empty()) {
|
|
mg_http_reply(c, 200, "Content-Type: application/json\r\n",
|
|
"%.*s", (int)body.size(), body.c_str());
|
|
} else {
|
|
mg_http_reply(c, 404, "Content-Type: text/plain\r\n", "Not Found\n");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 监控页面
|
|
if (path == "/" || path == "/index.html") {
|
|
mg_http_reply(c, 200, "Content-Type: text/html; charset=utf-8\r\n",
|
|
"%.*s", (int)INDEX_HTML.size(), INDEX_HTML.c_str());
|
|
return;
|
|
}
|
|
|
|
mg_http_reply(c, 404, "Content-Type: text/plain\r\n", "Not Found\n");
|
|
} else if (ev == MG_EV_WS_OPEN) {
|
|
// Web 线程内:先构建欢迎快照(可能涉及 DB 查询,勿持锁),再登记连接
|
|
std::string welcome;
|
|
if (m_onOpen) welcome = m_onOpen();
|
|
m_wsConnections.push_back(c);
|
|
if (!welcome.empty()) {
|
|
mg_ws_send(c, welcome.c_str(), welcome.size(), WEBSOCKET_OP_TEXT);
|
|
}
|
|
} else if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) {
|
|
for (auto it = m_wsConnections.begin(); it != m_wsConnections.end(); ++it) {
|
|
if (*it == c) {
|
|
m_wsConnections.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
} else if (ev == MG_EV_WS_MSG) {
|
|
// 纯监控,忽略客户端消息
|
|
}
|
|
}
|
|
|
|
WebServer::WebServer() {
|
|
mg_mgr_init(&m_mgr);
|
|
}
|
|
|
|
WebServer::~WebServer() {
|
|
stop();
|
|
mg_mgr_free(&m_mgr);
|
|
}
|
|
|
|
void WebServer::setOnOpen(std::function<std::string()> handler) {
|
|
m_onOpen = std::move(handler);
|
|
}
|
|
|
|
void WebServer::setApiHandler(std::function<std::string(const std::string&, const std::string&)> handler) {
|
|
m_apiHandler = std::move(handler);
|
|
}
|
|
|
|
bool WebServer::start(int port) {
|
|
if (m_running) return true;
|
|
m_port = port;
|
|
|
|
std::string addr = "http://0.0.0.0:" + std::to_string(port);
|
|
mg_http_listen(&m_mgr, addr.c_str(), [](mg_connection* c, int ev, void* ev_data) {
|
|
WebServer* server = static_cast<WebServer*>(c->fn_data);
|
|
if (server) server->handleEvent(c, ev, ev_data);
|
|
}, this);
|
|
|
|
m_running = true;
|
|
m_thread = std::thread([this]() { serverThreadFunc(); });
|
|
|
|
std::cout << "pCCU web UI listening on " << addr << std::endl;
|
|
return true;
|
|
}
|
|
|
|
void WebServer::stop() {
|
|
if (!m_running) return;
|
|
m_running = false;
|
|
if (m_thread.joinable()) m_thread.join();
|
|
|
|
// Web 线程已退出,此处触碰连接列表安全
|
|
for (auto c : m_wsConnections) {
|
|
mg_ws_send(c, "", 0, WEBSOCKET_OP_CLOSE);
|
|
}
|
|
m_wsConnections.clear();
|
|
}
|
|
|
|
bool WebServer::serverThreadCB(void* pParam) {
|
|
WebServer* pThis = static_cast<WebServer*>(pParam);
|
|
return pThis->serverThreadFunc();
|
|
}
|
|
|
|
bool WebServer::serverThreadFunc() {
|
|
while (m_running) {
|
|
mg_mgr_poll(&m_mgr, 200);
|
|
// 仅在本线程(poll 间隙)发送,避免跨线程操作 mongoose 连接
|
|
flushPending();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WebServer::flushPending() {
|
|
std::string text;
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_pendingMutex);
|
|
text = std::move(m_pending);
|
|
m_pending.clear();
|
|
}
|
|
if (text.empty()) return;
|
|
for (auto c : m_wsConnections) {
|
|
mg_ws_send(c, text.c_str(), text.size(), WEBSOCKET_OP_TEXT);
|
|
}
|
|
}
|
|
|
|
void WebServer::broadcast(const std::string& text) {
|
|
// 只投递到队列,实际发送在 Web 线程 flushPending() 中完成
|
|
std::lock_guard<std::mutex> lock(m_pendingMutex);
|
|
m_pending = text;
|
|
}
|
|
|
|
} // namespace ccu
|