http和websocket的一些思考
In CivetWeb, the terms CivetHandler
and CivetWebSocketHandler
are related to different types of request handling.
- CivetHandler:
CivetHandler
is a generic class in CivetWeb that is used for handling HTTP requests.- When you create a class that inherits from
CivetHandler
and override its virtual functions, you can define how the server should handle various HTTP methods (GET, POST, etc.) and request types. - This class is used for handling traditional HTTP requests, not WebSocket connections.
Example:
class MyHandler : public CivetHandler { public: bool handleGet(CivetServer *server, struct mg_connection *conn) { // Handle GET requests here return true; // Request handled successfully } };
例如,Apollo代码库中proto_handler的定义,其作用是为了在dv启动阶段获取所需要的proto,从而用于图表的自定义
有兴趣的可以去Apollo开源代码库自行学习:https://github.com/ApolloAuto/apollo
proto_handler.h
#pragma once #include <string> #include <functional> #include <mutex> #include <unordered_map> #include "CivetServer.h" /** * @namespace apollo::dreamview * @brief apollo::dreamview */ namespace apollo { namespace dreamview { /** * @class ProtoHandler * * @brief The ProtoHandler, built on top of CivetHandler, transfer the proto * file by http for frontend to parse message. */ class ProtoHandler : public CivetHandler { public: ProtoHandler() {} bool handleGet(CivetServer *server, struct mg_connection *conn); std::string GenerateETag(const std::string &content); private: bool FindProtoPath(const std::string file_relative_path, std::string* file_abs_path); std::unordered_map<std::string, std::string> content_cache_; std::mutex cache_mutex_; }; } // namespace dreamview } // namespace apollo
proto_handler.cc
其重写了handleGet
#include <fstream> #include "cyber/common/file.h" #include "cyber/common/log.h" #include "modules/dreamview/backend/common/handlers/proto_handler.h" namespace apollo { namespace dreamview { bool ProtoHandler::handleGet(CivetServer *server, struct mg_connection *conn) { const struct mg_request_info *req_info = mg_get_request_info(conn); // parse request rui std::string request_uri = req_info->local_uri; // replace /proto to actual file root path prefix,remove /proto // todo(@lijin):adapt to package version,change this to a variable std::string file_relative_path = request_uri.substr(6); std::string content; { std::lock_guard<std::mutex> lock(cache_mutex_); if (content_cache_.find(file_relative_path) != content_cache_.end()) { content = content_cache_[file_relative_path]; } } if (content.empty()) { std::string file_abs_path; if (FindProtoPath(file_relative_path, &file_abs_path) && apollo::cyber::common::GetContent(file_abs_path, &content)) { std::lock_guard<std::mutex> lock(cache_mutex_); content_cache_[file_relative_path] = content; } else { mg_printf( conn, "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\n\r\nFile " "not found"); return true; } } mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n"); mg_printf(conn, "Cache-Control: max-age=86400\r\n\r\n"); // 缓存 24 小时 // mg_printf(conn, "ETag: \"%s\"\r\n", // GenerateETag(content).c_str()); // 生成并发送ETag mg_printf(conn, "%s", content.c_str()); return true; } bool ProtoHandler::FindProtoPath(const std::string file_relative_path, std::string* file_abs_path) { std::string tmp_file_path; // source code tmp_file_path = "/apollo" + file_relative_path; if (apollo::cyber::common::PathExists(tmp_file_path)) { *file_abs_path = tmp_file_path; return true; } // package -source code tmp_file_path = "/apollo_workspace" + file_relative_path; if (apollo::cyber::common::PathExists(tmp_file_path)) { *file_abs_path = tmp_file_path; return true; } // package -source code tmp_file_path = "/opt/apollo/neo/src" + file_relative_path; if (apollo::cyber::common::PathExists(tmp_file_path)) { *file_abs_path = tmp_file_path; return true; } return false; } std::string ProtoHandler::GenerateETag(const std::string &content) { // 使用 std::hash 生成基于内容的哈希值 std::hash<std::string> hasher; size_t hash = hasher(content); // 将哈希值转换为十六进制字符串 std::stringstream ss; ss << std::hex << hash; return ss.str(); } } // namespace dreamview } // namespace apollo
- CivetWebSocketHandler:
CivetWebSocketHandler
is a specific type of handler in CivetWeb designed for handling WebSocket connections.- WebSocket is a protocol that provides full-duplex communication channels over a single, long-lived TCP connection. It's often used for real-time applications.
- When you create a class that inherits from
CivetWebSocketHandler
and override its virtual functions, you define how the server should handle WebSocket connections.
Example:
class MyWebSocketHandler : public CivetWebSocketHandler { public: bool handleConnection(CivetServer *server, const struct mg_connection *conn) { // Callback method for when the client intends to establish a websocket connection, before websocket handshake. return true; // Connection accepted } void handleReadyState(CivetServer *server, struct mg_connection *conn) { // Callback method for when websocket handshake is successfully completed, and connection is ready for data exchange. } bool WebSocketHandler::handleData(CivetServer *server, Connection *conn, int bits, char *data, size_t data_len) { // Callback method for when a data frame has been received from the client. } };
例如,Apollo的websocket中重写了这三个函数,同时响应拓展了支持处理json数据和binary数据,对于json数据又通过map映射了多个名为key的函数,通过前端指定不同接口名,即可通过一个websocket进行响应;此外发送也支持单播和广播,广播是在连接建立时记录了该连接,使用时遍历一次发送。
websocket_handler.h
#pragma once #include <memory> #include <mutex> #include <string> #include <thread> #include <unordered_map> #include <vector> #include "CivetServer.h" #include "nlohmann/json.hpp" /** * @namespace apollo::dreamview * @brief apollo::dreamview */ namespace apollo { namespace dreamview { /** * @class WebSocketHandler * * @brief The WebSocketHandler, built on top of CivetWebSocketHandler, is a * websocket handler that handles different types of websocket related events. */ class WebSocketHandler : public CivetWebSocketHandler { // In case of receiving fragmented message, // websocket opcode and accumulated data are stored. thread_local static unsigned char current_opcode_; thread_local static std::stringstream data_; public: using Json = nlohmann::json; using Connection = struct mg_connection; using MessageHandler = std::function<void(const Json &, Connection *)>; using ConnectionReadyHandler = std::function<void(Connection *)>; explicit WebSocketHandler(const std::string &name) : name_(name) {} /** * @brief Callback method for when the client intends to establish a websocket * connection, before websocket handshake. * * @param server the calling server * @param conn the connection information * @returns true to keep socket open, false to close it */ bool handleConnection(CivetServer *server, const Connection *conn) override { return true; } /** * @brief Callback method for when websocket handshake is successfully * completed, and connection is ready for data exchange. * * @param server the calling server * @param conn the connection information */ void handleReadyState(CivetServer *server, Connection *conn) override; /** * @brief Callback method for when a data frame has been received from the * client. * * @details In the websocket protocol, data is transmitted using a sequence of * frames, and each frame received invokes this callback method. Since the * type of opcode (text, binary, etc) is given in the first frame, this method * stores the opcode in a thread_local variable named current_opcode_. And * data from each frame is accumulated to data_ until the final fragment is * detected. See websocket RFC at http://tools.ietf.org/html/rfc6455, section * 5.4 for more protocol and fragmentation details. * * @param server the calling server * @param conn the connection information * @param bits first byte of the websocket frame, see websocket RFC at * http://tools.ietf.org/html/rfc6455, section 5.2 * @param data payload, with mask (if any) already applied. * @param data_len length of data * @returns true to keep socket open, false to close it */ bool handleData(CivetServer *server, Connection *conn, int bits, char *data, size_t data_len) override; bool handleJsonData(Connection *conn, const std::string &data); bool handleBinaryData(Connection *conn, const std::string &data); /** * @brief Callback method for when the connection is closed. * * @param server the calling server * @param conn the connection information */ void handleClose(CivetServer *server, const Connection *conn) override; /** * @brief Sends the provided data to all the connected clients. * @param data The message string to be sent. */ bool BroadcastData(const std::string &data, bool skippable = false); /** * @brief Sends the provided binary data to all the connected clients. * @param data The message string to be sent. */ bool BroadcastBinaryData(const std::string &data, bool skippable = false); /** * @brief Sends the provided data to a specific connected client. * * @param conn The connection to send to. * @param data The message string to be sent. * @param skippable whether the data is allowed to be skipped if some other is * being sent to this connection. */ bool SendData(Connection *conn, const std::string &data, bool skippable = false, int op_code = MG_WEBSOCKET_OPCODE_TEXT); bool SendBinaryData(Connection *conn, const std::string &data, bool skippable = false); /** * @brief Add a new message handler for a message type. * @param type The name/key to identify the message type. * @param handler The function to handle the received message. */ void RegisterMessageHandler(std::string type, MessageHandler handler) { message_handlers_[type] = handler; } /** * @brief Add a new handler for new connections. * @param handler The function to handle the new connection in ReadyState. */ void RegisterConnectionReadyHandler(ConnectionReadyHandler handler) { connection_ready_handlers_.emplace_back(handler); } private: const std::string name_; // Message handlers keyed by message type. std::unordered_map<std::string, MessageHandler> message_handlers_; // New connection ready handlers. std::vector<ConnectionReadyHandler> connection_ready_handlers_; // The mutex guarding the connection set. We are not using read // write lock, as the server is not expected to get many clients // (connections). // CAVEAT: Execution section while holding this global lock should be as // brief as possible. mutable std::mutex mutex_; // The pool of all maintained connections. Each connection has a lock to // guard against simultaneous write. std::unordered_map<Connection *, std::shared_ptr<std::mutex>> connections_; }; } // namespace dreamview } // namespace apollo
websocket_handler.cc
#include "modules/dreamview/backend/common/handlers/websocket_handler.h" #include "cyber/common/log.h" #include "modules/common/util/map_util.h" namespace apollo { namespace dreamview { using apollo::common::util::ContainsKey; void WebSocketHandler::handleReadyState(CivetServer *server, Connection *conn) { { std::unique_lock<std::mutex> lock(mutex_); connections_.emplace(conn, std::make_shared<std::mutex>()); } AINFO << name_ << ": Accepted connection. Total connections: " << connections_.size(); // Trigger registered new connection handlers. for (const auto handler : connection_ready_handlers_) { handler(conn); } } void WebSocketHandler::handleClose(CivetServer *server, const Connection *conn) { // Remove from the store of currently open connections. Copy the mutex out // so that it won't be reclaimed during map.erase(). Connection *connection = const_cast<Connection *>(conn); std::shared_ptr<std::mutex> connection_lock; { std::unique_lock<std::mutex> lock(mutex_); connection_lock = connections_[connection]; } { // Make sure there's no data being sent via the connection std::unique_lock<std::mutex> lock_connection(*connection_lock); std::unique_lock<std::mutex> lock(mutex_); connections_.erase(connection); } // send close frame int ret = mg_websocket_write(connection, 0x8, "", 0); // Determine error message based on return value. AWARN << name_ << ": Failed to send clase frame. Reason"; if (ret == 0) { AWARN << "Connection closed"; } else if (ret < 0) { AWARN << "Send error: " << std::strerror(errno); } else { AWARN << "Bytes to send: expected 2, actual: " << ret; } AINFO << name_ << ": Connection closed. Total connections: " << connections_.size(); } bool WebSocketHandler::BroadcastData(const std::string &data, bool skippable) { std::vector<Connection *> connections_to_send; { std::unique_lock<std::mutex> lock(mutex_); if (connections_.empty()) { return true; } for (auto &kv : connections_) { Connection *conn = kv.first; connections_to_send.push_back(conn); } } bool all_success = true; for (Connection *conn : connections_to_send) { if (!SendData(conn, data, skippable)) { all_success = false; } } return all_success; } bool WebSocketHandler::BroadcastBinaryData(const std::string &data, bool skippable) { std::vector<Connection *> connections_to_send; { std::unique_lock<std::mutex> lock(mutex_); if (connections_.empty()) { return true; } for (auto &kv : connections_) { Connection *conn = kv.first; connections_to_send.push_back(conn); } } bool all_success = true; for (Connection *conn : connections_to_send) { if (!SendData(conn, data, skippable, MG_WEBSOCKET_OPCODE_BINARY)) { all_success = false; } } return all_success; } bool WebSocketHandler::SendBinaryData(Connection *conn, const std::string &data, bool skippable) { return SendData(conn, data, skippable, MG_WEBSOCKET_OPCODE_BINARY); } bool WebSocketHandler::SendData(Connection *conn, const std::string &data, bool skippable, int op_code) { std::shared_ptr<std::mutex> connection_lock; { std::unique_lock<std::mutex> lock(mutex_); if (!ContainsKey(connections_, conn)) { AERROR << name_ << ": Trying to send to an uncached connection, skipping."; return false; } // Copy the lock so that it still exists if the connection is closed after // this block. connection_lock = connections_[conn]; } // Lock the connection while sending. if (!connection_lock->try_lock()) { // Skip sending data if: // 1. Data is skippable according to sender and there's higher priority data // being sent. // 2. The connection has been closed. if (skippable) { AWARN << "Skip sending a droppable message!"; return false; } // Block to acquire the lock. connection_lock->lock(); std::unique_lock<std::mutex> lock(mutex_); if (!ContainsKey(connections_, conn)) { return false; } } // Note that while we are holding the connection lock, the connection won't be // closed and removed. int ret = mg_websocket_write(conn, op_code, data.c_str(), data.size()); connection_lock->unlock(); if (ret != static_cast<int>(data.size())) { // When data is empty, the header length (2) is returned. if (data.empty() && ret == 2) { return true; } // Determine error message based on return value. AWARN << name_ << ": Failed to send data via websocket connection. Reason"; if (ret == 0) { AWARN << "Connection closed"; } else if (ret < 0) { AWARN << "Send error: " << std::strerror(errno); } else { AWARN << "Bytes to send: expected " << data.size() << ", actual: " << ret; } return false; } return true; } thread_local unsigned char WebSocketHandler::current_opcode_ = 0x00; thread_local std::stringstream WebSocketHandler::data_; bool WebSocketHandler::handleData(CivetServer *server, Connection *conn, int bits, char *data, size_t data_len) { // Ignore connection close request. if ((bits & 0x0F) == MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE) { return false; } data_.write(data, data_len); if (current_opcode_ == 0x00) { current_opcode_ = bits & 0x7f; } bool result = true; // The FIN bit (the left most significant bit) is used to indicates // the final fragment in a message. Note, the first fragment MAY // also be the final fragment. bool is_final_fragment = bits & 0x80; if (is_final_fragment) { switch (current_opcode_) { case MG_WEBSOCKET_OPCODE_TEXT: result = handleJsonData(conn, data_.str()); break; case MG_WEBSOCKET_OPCODE_BINARY: result = handleBinaryData(conn, data_.str()); break; default: AERROR << name_ << ": Unknown WebSocket bits flag: " << bits; break; } // reset opcode and data current_opcode_ = 0x00; data_.clear(); data_.str(std::string()); } return result; } bool WebSocketHandler::handleJsonData(Connection *conn, const std::string &data) { Json json; try { json = Json::parse(data.begin(), data.end()); } catch (const std::exception &e) { AERROR << "Failed to parse JSON data: " << e.what(); return false; } if (!ContainsKey(json, "type")) { AERROR << "Received JSON data without type field: " << json; return true; } auto type = json["type"]; if (!ContainsKey(message_handlers_, type)) { AERROR << "No message handler found for message type " << type << ". The message will be discarded!"; return true; } message_handlers_[type](json, conn); return true; } bool WebSocketHandler::handleBinaryData(Connection *conn, const std::string &data) { auto type = "Binary"; message_handlers_[type](data, conn); return true; } } // namespace dreamview } // namespace apollo
自己选择的路,跪着也要走完。朋友们,虽然这个世界日益浮躁起来,只要能够为了当时纯粹的梦想和感动坚持努力下去,不管其它人怎么样,我们也能够保持自己的本色走下去。