C++轻量级HTTP服务器封装:从Socket到多线程的实战指南 1. 项目概述为什么我们需要一个轻量级的C HTTP服务器封装类在当前的软件开发环境中无论是构建一个需要提供简单API的后端服务还是为桌面应用嵌入一个本地的Web管理界面HTTP服务器都是一个绕不开的核心组件。你可能会想到直接用Nginx、Apache或者用Python的Flask、Go的Golang标准库来快速搭建。但对于一个追求极致性能、零外部依赖或者需要在特定嵌入式、高性能计算环境中运行的C项目来说引入这些重量级或跨语言的方案往往意味着额外的部署复杂度、性能开销甚至是许可协议的困扰。这就是我动手封装这个C轻量级HTTP服务器的初衷。它不是一个功能大而全的Web框架而是一个聚焦于“够用、好用、性能可控”的封装类。它的目标很明确让你能在几分钟内用纯C代码启动一个能处理GET/POST请求、解析URL参数和表单数据、返回JSON或HTML的HTTP服务并且能轻松地集成到你的现有C项目中无需额外安装任何运行时库。从网络热词中频繁出现的“C八股文”、“C面试题”可以看出很多开发者对C网络编程的理解停留在理论层面。而“项目实战”相关的搜索又表明市场迫切需要能将知识转化为实际生产力的案例。这个项目正是连接这两者的桥梁。它避开了像Boost.Beast那样庞大而复杂的学习曲线也不同于那些仅用于演示的“玩具”服务器。我们关注的是工业级代码的健壮性、资源管理的严谨性以及在实际部署中可能遇到的各种“坑”比如连接泄露、线程安全、缓冲区溢出等。简单来说如果你正在开发一个需要内嵌Web服务的C应用比如一款游戏的服务器、一个物联网设备的控制端、一个高频交易系统的监控界面或者你单纯想深入理解HTTP协议和Socket编程在C中的实战应用那么这个项目将为你提供一个绝佳的起点和可复用的核心组件。2. 核心设计思路与架构拆解2.1 设计哲学在轻量与功能间寻找平衡点设计一个库尤其是基础网络库首要任务是在功能完备性和代码轻量性之间做出权衡。我们的目标是“轻量级”但这不意味着功能残缺。这里的“轻量”主要体现在以下几个方面零外部依赖核心实现仅依赖于C11/14标准库和操作系统提供的Socket API如Linux的sys/socket.h或Windows的winsock2.h。这意味着你的项目在编译和部署时不需要额外安装或链接像libcurl、openssl这样的第三方库除非你需要HTTPS。这极大地简化了项目的构建流程和运行环境。专注核心协议完整实现HTTP/1.1协议是一个浩大的工程。我们聚焦于最常用的部分请求行、状态行、头部字段的解析以及GET、POST方法的支持。对于PUT、DELETE、PATCH等方法我们提供易于扩展的接口。对于分块传输编码Chunked Transfer Encoding、持久连接Keep-Alive的优化我们将其作为可选的进阶特性而不是必须的初始负担。清晰的接口隔离将网络I/O、协议解析、业务逻辑进行分层。封装类只负责处理底层的Socket监听、连接接收、数据读写和HTTP报文的基本解析与组装。具体的请求路由Routing、业务处理Handler以回调函数或虚函数的形式暴露给使用者。这样使用者可以专注于业务开发而不必关心recv和send的细节。2.2 技术选型为什么是原生Socket与多线程面对“多线程HTTP服务器”这个热词很多人会疑惑为什么不用更“现代”的异步IO模型如epoll、kqueue、IOCP或者协程库这是一个非常好的问题。选择“一个连接一个线程”的经典多线程模型主要基于以下考量实现复杂度异步IO模型Reactor/Proactor性能极高能轻松应对C10K甚至更高并发但其编程模型复杂状态机难以调试对开发者要求高。而多线程模型逻辑直观一个线程处理一个连接代码流程是线性的易于理解和维护。对于内部管理界面、中低并发API服务例如并发连接数在几百到几千多线程模型完全够用且开发效率更高。教学与示范价值多线程模型清晰地展示了连接的生命周期管理、线程的创建与销毁、资源竞争等经典问题是学习网络编程和并发编程的绝佳范例。理解了它再学习异步模型会更有基础。可控的并发度我们可以通过线程池来限制最大并发线程数防止连接数暴增导致线程耗尽系统资源。这为模型提供了基本的保护。当然我们并非排斥高性能。在架构设计上我们将accept连接的部分与handle请求的部分解耦。未来可以很容易地将线程池替换为基于epoll的事件驱动模型而对外暴露的业务处理接口几乎不需要改动。这为性能升级留出了空间。2.3 核心类图与工作流程整个封装类的核心可以简化为以下几个部分HttpServer类服务器的主类。负责启动和停止服务绑定端口并运行一个主循环run方法来接受客户端连接。Connection类或结构体代表一个客户端连接。它封装了客户端的Socket文件描述符或句柄以及用于读写的缓冲区。每个Connection对象在一个独立的线程中被处理。HttpRequest类负责解析原始的HTTP请求数据。它将TCP字节流解析为方法Method、路径URI、查询参数Query Parameters、头部Headers和消息体Body。对于application/x-www-form-urlencoded和multipart/form-data格式的POST数据它提供便捷的解析方法。HttpResponse类负责构建HTTP响应。使用者可以方便地设置状态码如200 OK、404 Not Found、添加响应头如Content-Type、设置响应体字符串或二进制数据。该类最终负责将响应格式化为符合HTTP协议的字节流。路由与处理器Callback这是使用者主要交互的部分。我们提供一个类似std::unordered_mapstd::string, std::functionvoid(const HttpRequest, HttpResponse)的路由表。使用者将URL路径模式如/api/user与一个处理函数绑定。当请求到来时服务器根据路径查找并调用对应的处理函数。一次请求的完整工作流程如下HttpServer在指定端口监听。接受一个新连接创建一个Connection对象并将其交给一个空闲的工作线程或新建一个线程。工作线程从Connection的Socket中读取数据直到遇到一个完整的HTTP请求根据Content-Length或空行判断结束。将读取到的原始数据传递给HttpRequest::parse方法进行解析。根据解析出的请求路径在路由表中查找对应的处理函数。调用处理函数该函数接收HttpRequest和HttpResponse对象作为参数执行业务逻辑并填充HttpResponse。将HttpResponse对象序列化为字节流通过Connection的Socket写回客户端。根据HTTP头部Connection: close或超时设置决定是关闭连接还是等待同一连接上的下一个请求Keep-Alive。清理Connection资源线程任务结束。3. 关键实现细节与避坑指南3.1 Socket编程的跨平台封装网络编程的第一道坎就是操作系统API的差异。在Linux/macOS上我们使用Berkeley sockets在Windows上则是Winsock。为了让代码跨平台我们必须进行封装。// 示例一个简单的Socket类封装 class Socket { public: Socket(); ~Socket(); bool create(); // 创建socket bool bind(const std::string host, int port); bool listen(int backlog 10); Socket accept(); ssize_t read(char* buffer, size_t length); ssize_t write(const char* buffer, size_t length); void close(); // ... 其他方法如setNonBlocking等 private: #ifdef _WIN32 SOCKET sockfd_; #else int sockfd_; #endif // 封装WSAStartup/WSACleanup的初始化与清理 };避坑指南1资源泄漏是魔鬼Socket是操作系统资源必须确保在任何路径下包括异常抛出时都能被正确关闭。我们使用RAIIResource Acquisition Is Initialization技术在Socket类的析构函数中调用close()。同时拷贝构造函数和拷贝赋值运算符应该被禁用delete或实现为深拷贝/移动语义避免多个Socket对象持有同一个文件描述符导致重复关闭或未关闭。避坑指南2优雅地处理EINTR系统调用如accept,read,write可能被信号中断此时会返回错误并设置errno为EINTR在Windows上对应WSAEINTR。正确的做法不是将其视为致命错误而是在循环中重试被中断的系统调用。很多网络程序的稳定性问题都源于忽略了对EINTR的处理。3.2 HTTP协议解析器的稳健实现解析器是HTTP服务器的核心也是最容易出安全漏洞的地方如缓冲区溢出。我们的解析器采用状态机State Machine的方式逐字节处理而不是依赖sscanf或简单的字符串查找。请求行解析例如GET /api/user?id123 HTTP/1.1\r\n。我们需要分离出方法、路径、查询字符串和版本。这里的关键是正确处理URL编码Percent-Encoding将%20转换为空格等。头部解析头部字段以:分隔键值每行以\r\n结尾以一个空的\r\n标识头部结束。解析时需要处理折行folded header现已不推荐但需兼容和重复头部如多个Cookie头。我们使用std::unordered_mapstd::string, std::string来存储头部键名转换为小写以便大小写不敏感查找。消息体解析这是POST请求的关键。需要根据Content-Length头部或Transfer-Encoding: chunked来判断消息体的长度和格式。Content-Length相对简单读取指定长度的字节即可。Transfer-Encoding: chunked实现稍复杂需要解析分块格式。每个块以十六进制数字块大小开头后跟\r\n然后是数据块再跟\r\n。最后以一个大小为0的块结束。虽然我们的轻量级服务器可以暂不支持chunked但设计上要留出接口。避坑指南3严格限制缓冲区与防御性编程绝不能假设客户端发送的请求是规范的。必须为每一部分请求行、头部字段名、值、路径设置合理的最大长度限制防止恶意客户端发送超长数据耗尽服务器内存。在解析时每一个状态转换都要检查边界条件。// 示例解析请求行状态机片段 enum class ParseState { REQUEST_LINE, HEADERS, BODY, COMPLETE }; ParseState state ParseState::REQUEST_LINE; std::string buffer; // ... 读取数据到buffer size_t pos 0; while (pos buffer.size() state ! ParseState::COMPLETE) { switch (state) { case ParseState::REQUEST_LINE: { size_t line_end buffer.find(\r\n, pos); if (line_end std::string::npos) break; // 数据不足等待下次读取 std::string line buffer.substr(pos, line_end - pos); if (line.length() MAX_REQUEST_LINE_LENGTH) { // 返回 414 URI Too Long 或 413 Payload Too Large state ParseState::COMPLETE; return false; } // 解析 method, uri, version... pos line_end 2; state ParseState::HEADERS; break; } // ... 其他状态 } }3.3 多线程管理与线程安全我们采用“主线程Accept 工作线程处理”的模型。主线程负责循环调用accept每当有新连接就将其包装成任务投递到一个线程池中。线程池的实现一个简单的线程池包含一个任务队列std::queuestd::functionvoid()和一组工作线程。使用std::mutex保护任务队列使用std::condition_variable进行线程间同步。当主线程投递新任务时通知一个等待中的工作线程来取任务执行。避坑指南4警惕线程间的数据竞争即使每个连接独立处理仍然存在共享数据比如日志输出多个线程同时向std::cout或一个日志文件写入会导致输出混乱。需要将日志操作同步或使用线程安全的日志库。全局配置或统计信息如果需要统计服务器处理的请求总数这个计数器就是共享资源对其的操作需要加锁如std::atomic。路由表虽然在服务器启动后通常是只读的但如果在运行时支持动态添加路由热更新则修改路由表的操作也需要同步。避坑指南5妥善处理线程异常工作线程中执行的用户业务逻辑可能会抛出异常。如果这个异常未被捕获会终止整个线程导致资源如Socket连接无法释放。因此必须在每个工作线程的任务执行最外层进行try-catch确保即使业务代码崩溃也能安全地关闭连接并记录错误而不影响服务器其他部分。void workerThreadFunc() { while (running_) { std::functionvoid() task; { std::unique_lockstd::mutex lock(queueMutex_); condition_.wait(lock, [this] { return !taskQueue_.empty() || !running_; }); if (!running_ taskQueue_.empty()) break; task std::move(taskQueue_.front()); taskQueue_.pop(); } try { task(); // 执行处理连接的任务 } catch (const std::exception e) { std::cerr Worker thread caught exception: e.what() std::endl; // 这里可以尝试获取当前任务的连接信息并关闭它 } catch (...) { std::cerr Worker thread caught unknown exception. std::endl; } } }4. 从零开始构建并运行你的第一个服务器4.1 环境准备与项目配置无论你使用Visual Studio、VSCode还是CLion一个清晰的CMake项目结构是良好开端。假设你的项目目录如下cpp_http_server/ ├── CMakeLists.txt ├── include/ │ ├── http_server.h │ ├── http_request.h │ ├── http_response.h │ └── socket_util.h ├── src/ │ ├── http_server.cpp │ ├── http_request.cpp │ ├── http_response.cpp │ └── socket_util.cpp └── examples/ └── simple_demo.cppCMakeLists.txt 关键配置cmake_minimum_required(VERSION 3.10) project(CppHttpServer LANGUAGES CXX) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 根据平台链接网络库 if (WIN32) set(PLATFORM_LIBS ws2_32) else() set(PLATFORM_LIBS) endif() add_library(cpp_http_server STATIC src/http_server.cpp src/http_request.cpp src/http_response.cpp src/socket_util.cpp ) target_include_directories(cpp_http_server PUBLIC include) target_link_libraries(cpp_http_server PUBLIC ${PLATFORM_LIBS}) # 示例程序 add_executable(simple_demo examples/simple_demo.cpp) target_link_libraries(simple_demo cpp_http_server)在Windows上使用VSCode或Visual Studio时确保已安装“Microsoft Visual C Redistributable”运行时库。对于开发环境你需要的是“Microsoft Visual C Build Tools”或Visual Studio Installer中的C桌面开发组件以获取必要的头文件和库。4.2 编写一个简单的“Hello World”服务器现在让我们在examples/simple_demo.cpp中创建一个最简单的服务器#include http_server.h #include iostream #include string int main() { using namespace my_http; // 假设我们的类在my_http命名空间下 try { // 1. 创建服务器实例监听本地8080端口 HttpServer server(8080); // 2. 注册路由处理函数 // 处理根路径 GET 请求 server.Get(/, [](const HttpRequest req, HttpResponse res) { res.SetStatusCode(200); res.SetHeader(Content-Type, text/html; charsetutf-8); res.SetBody(h1Hello, C HTTP Server!/h1); }); // 3. 处理带路径参数的请求 server.Get(/user/{id}, [](const HttpRequest req, HttpResponse res) { // 假设我们从路径中提取了 {id}这里简化为从查询参数获取 auto params req.GetQueryParams(); std::string userId params.count(id) ? params.at(id) : unknown; res.SetStatusCode(200); res.SetHeader(Content-Type, application/json); res.SetBody({\user_id\: \ userId \, \status\: \ok\}); }); // 4. 处理 POST 请求解析表单数据 server.Post(/login, [](const HttpRequest req, HttpResponse res) { if (req.GetHeader(content-type).find(application/x-www-form-urlencoded) ! std::string::npos) { auto formData req.ParseFormUrlEncoded(); // 假设有这个方法 std::string username formData[username]; std::string password formData[password]; // 警告实际中永远不要明文传输密码 // ... 验证逻辑 ... res.SetStatusCode(200); res.SetBody(Login successful for user: username); } else { res.SetStatusCode(415); // Unsupported Media Type res.SetBody(Only application/x-www-form-urlencoded is supported.); } }); // 5. 启动服务器阻塞调用直到收到停止信号 std::cout Server starting on port 8080... std::endl; server.Run(); } catch (const std::exception e) { std::cerr Server error: e.what() std::endl; return 1; } return 0; }编译与运行# 在项目根目录 mkdir build cd build cmake .. cmake --build . --config Release # 在Windows上可能需要指定 --config # 运行示例 ./simple_demo # Linux/macOS # 或 simple_demo.exe # Windows打开浏览器访问http://localhost:8080/你应该能看到“Hello, C HTTP Server!”的标题。访问http://localhost:8080/user?id123会收到一个JSON响应。4.3 核心API详解与扩展让我们深入看看HttpResponse类的一些关键方法理解如何构建丰富的响应class HttpResponse { public: // 设置状态码和原因短语可选会自动根据标准码填充 void SetStatusCode(int code, const std::string reason ); // 添加或设置一个响应头 void SetHeader(const std::string key, const std::string value); void AddHeader(const std::string key, const std::string value); // 用于添加多个相同key的头如Set-Cookie // 设置响应体。可以接受字符串或二进制数据std::vectorchar void SetBody(const std::string body); void SetBody(const std::vectorchar data); // 便捷方法设置JSON响应会自动添加Content-Type头 void SetJsonBody(const std::string jsonString); // 未来可以整合nlohmann/json等库直接传入json对象 // void SetJsonBody(const nlohmann::json j); // 便捷方法发送文件自动处理Content-Type, Content-Length 分块读取大文件 bool SendFile(const std::string filePath); // 将整个响应序列化为符合HTTP协议的字符串 std::string ToString() const; private: int statusCode_; std::string reasonPhrase_; std::unordered_mapstd::string, std::string headers_; std::vectorchar body_; };扩展思考静态文件服务一个实用的服务器通常需要提供静态文件HTML、CSS、JS、图片。我们可以实现一个通用的静态文件处理器。关键在于根据文件扩展名正确设置Content-TypeMIME类型。高效读取文件内容对于大文件不应一次性读入内存而应分块读取并发送。处理Range请求断点续传/视频播放和If-Modified-Since请求缓存协商。5. 生产环境考量与性能调优5.1 连接管理与超时设置一个健壮的服务器必须能处理异常或恶意的客户端连接。读/写超时使用setsockopt设置SO_RCVTIMEO和SO_SNDTIMEO防止慢客户端或网络问题导致线程无限期阻塞。超时后应主动关闭连接。Keep-Alive超时如果支持HTTP/1.1持久连接需要设置一个空闲超时。可以在Connection对象中记录最后一次活动时间并由一个独立的清理线程定期检查并关闭超时的连接。最大连接数限制在accept之后可以检查当前活跃连接数。如果超过阈值如thread_pool_size * 2可以立即返回503 Service Unavailable并关闭新连接防止资源耗尽。5.2 日志与监控日志是调试和运维的生命线。至少需要记录访问日志客户端IP、请求时间、方法、路径、状态码、响应大小、处理时长。格式可以模仿Nginx的Combined Log Format。错误日志解析错误、系统调用错误如accept失败、业务逻辑异常等。可以将日志输出到文件并实现简单的日志轮转如按天或按大小切割。对于高性能场景可以考虑使用异步日志库如spdlog来避免日志I/O阻塞工作线程。5.3 从多线程到事件驱动性能升级路径当并发连接数上升到数千时线程切换的开销和内存占用每个线程的栈会成为瓶颈。此时需要考虑将核心模型升级为事件驱动。升级方案保留接口HttpServer、HttpRequest、HttpResponse的对外接口基本不变。重写核心循环将HttpServer::Run中的accept循环改为非阻塞模式并使用epollLinux、kqueuemacOS/BSD或IOCPWindows来监听所有连接套接字上的读写事件。改造连接处理不再为每个连接创建线程。当epoll通知某个连接可读时在一个固定的线程池IO线程池中读取数据。解析出一个完整的HTTP请求后将HttpRequest对象投递到另一个业务线程池进行处理。处理完成后再将HttpResponse对象和对应的连接fd投递回IO线程池进行写操作。引入缓冲区管理需要为每个连接维护读缓冲区和写缓冲区因为一次读事件可能只读到半个HTTP请求一次写事件可能只写出一部分响应数据。这种改造相当于将现在的“一个连接一个线程”模型解耦为“事件驱动IO 线程池业务处理”的Reactor模式。虽然改造工作量较大但由于前期架构清晰隔离做得好核心的业务处理代码几乎无需改动。5.4 安全性增强建议一个面向公网的简易服务器必须考虑基本的安全请求限制限制请求行、头部、body的最大长度防止DoS攻击。路径遍历防护当提供静态文件服务时必须检查请求的路径防止../../../etc/passwd这样的路径遍历攻击。设置安全的响应头例如对于没有动态内容的静态资源可以设置X-Content-Type-Options: nosniffX-Frame-Options: DENY等。考虑HTTPS虽然我们的封装类本身不包含TLS/SSL但可以通过将Socket连接包装在像OpenSSL的SSL*对象之后来实现。这需要另一个层级的封装。6. 实战中遇到的典型问题与解决方案在实际使用和测试这个封装类的过程中我踩过不少坑。这里记录下最典型的几个问题及其解决方法希望能帮你绕过去。问题一服务器在高并发下出现“Address already in use”错误即使重启后也要等一会儿才能再次绑定端口。原因这是TCP的TIME_WAIT状态导致的。当服务器主动关闭一个连接后该连接的套接字会进入TIME_WAIT状态通常持续2MSL即1-4分钟在此期间端口无法立即重用。解决方案在创建服务器Socket后绑定bind之前设置SO_REUSEADDR套接字选项。int reuse 1; if (setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, (const char*)reuse, sizeof(reuse)) 0) { // 处理错误 }注意在Windows上这个选项叫SO_EXCLUSIVEADDRUSE其语义略有不同通常设置SO_REUSEADDR即可但需要仔细阅读文档。问题二客户端突然断开连接服务器线程在read或write时阻塞或崩溃。原因网络是不稳定的。对端可能崩溃、拔网线或正常关闭连接。如果服务器线程在阻塞的read调用上等待而连接已断开在Linux上会返回0读到EOF在错误情况下返回-1在阻塞的write调用上如果连接已断开通常会触发SIGPIPE信号默认终止进程或返回-1并设置errno为EPIPE。解决方案忽略SIGPIPE信号在程序启动时调用signal(SIGPIPE, SIG_IGN)POSIX系统防止进程被写断开的管道信号杀死。检查系统调用返回值每次read和write后都必须检查返回值。read返回0表示对端关闭连接应清理资源。write返回-1且errno为EPIPE也表示连接已失效。使用超时如前所述设置套接字超时选项避免无限期阻塞。异常处理在所有网络IO操作周围使用try-catch并确保在异常发生时能安全关闭连接。问题三POST请求的body解析不正确特别是multipart/form-data文件上传。原因multipart/form-data的格式比较复杂由边界boundary分隔多个部分每个部分有自己的头部和内容。简单的字符串查找很难正确处理。解决方案实现一个专门的状态机来解析multipart格式。或者在轻量级场景下可以暂时不支持文件上传只处理application/x-www-form-urlencoded和application/json。如果必须支持可以参考RFC 7578并严格测试。一个常见的坑是body数据可能不是一次性到达的解析器需要能够处理“数据不完整”的情况等待下一次读取。问题四内存使用量随着运行时间缓慢增长内存泄漏。原因最可能的原因是连接资源Connection对象没有在断开后被正确释放或者线程局部缓存、全局容器没有及时清理。排查方法使用ValgrindLinux或Visual Studio的诊断工具Windows运行服务器进行压力测试查看内存泄漏报告。确保每个Connection对象都被std::shared_ptr或std::unique_ptr管理并在处理函数结束时或捕获到任何异常时确保其引用计数归零触发析构。检查线程池的任务队列是否有没有被取出的任务对象持有资源。检查所有容器如路由表std::unordered_map中存储的回调函数或对象是否无意中持有了不必要的上下文引用。问题五QPS每秒查询率上不去达到几百后就瓶颈明显。可能瓶颈与排查方向锁竞争使用性能分析工具如perf,vtune查看热点。如果线程池的任务队列锁queueMutex_竞争激烈说明生产任务accept的速度远快于消费处理的速度。可以尝试使用无锁队列如moodycamel::ConcurrentQueue或改为多个任务队列每个工作线程一个。日志同步如果日志输出到控制台或单个文件且没有缓冲这会是巨大的性能瓶颈。改为异步日志或仅在调试时开启详细日志。业务逻辑本身最可能的瓶颈其实是用户自己注册的业务处理函数。用工具定位处理函数中耗时的操作如数据库查询、复杂的计算。系统限制检查操作系统对单个进程打开文件描述符数的限制ulimit -n以及线程栈大小。对于短连接场景TIME_WAIT状态下的连接过多也会消耗资源可以适当调整系统TCP参数如net.ipv4.tcp_tw_reuse。这个轻量级HTTP服务器封装类项目就像一把自己锻造的瑞士军刀。它可能没有专业工具那么功能繁多但每一处设计都贴合你的手型每一个细节你都了如指掌。从理解Socket API的细微之处到设计一个稳健的HTTP解析状态机再到处理多线程下的各种竞态条件整个过程是对C系统编程能力的一次全面锻炼。当你看到自己编写的服务器稳定地响应着浏览器的请求时那种成就感是调用现成库无法比拟的。更重要的是通过这个项目积累的经验和代码你可以自信地将其作为基石嵌入到任何需要网络通信的C应用中真正做到知其然更知其所以然。

相关新闻

最新新闻

里斯本大学的研究者教你用普通电脑“猜出“量子电路的答案

里斯本大学的研究者教你用普通电脑“猜出“量子电路的答案

这项由葡萄牙里斯本高等理工学院(Instituto Superior Tcnico, Universidade de Lisboa)完成的研究,以预印本形式发表于2026年7月,论文编号为arXiv:2607.07816,有兴趣深入了解的读者可以通过该编号在arXiv平台查询完整论…

2026/7/22 0:31:49
draw.io桌面版终极指南:完全免费的跨平台图表工具

draw.io桌面版终极指南:完全免费的跨平台图表工具

draw.io桌面版终极指南:完全免费的跨平台图表工具 【免费下载链接】drawio-desktop Official electron build of draw.io 项目地址: https://gitcode.com/GitHub_Trending/dr/drawio-desktop 还在为昂贵的图表软件发愁吗?想要一款真正免费、功能强…

2026/7/22 0:31:49
HarmonyOS应用开发实战:小事记 - @Link 与 @Prop 双向同步:父子组件状态协调的深层原理

HarmonyOS应用开发实战:小事记 - @Link 与 @Prop 双向同步:父子组件状态协调的深层原理

前言 在 ArkUI 中,Link 和 Prop 都用于父子组件间的数据传递,但它们的同步方向和使用场景不同。Prop 是单向的(父 → 子),而 Link 是双向同步的。本文以小事记(xiaoshiji_ohos_app) 的组件扩展…

2026/7/22 0:31:49
kafka broker不设置分区key,会将同一topic的消息存放到不同的分区,但读取数据不能将不同分区的数据一次性查询出来怎么解决

kafka broker不设置分区key,会将同一topic的消息存放到不同的分区,但读取数据不能将不同分区的数据一次性查询出来怎么解决

在使用Apache Kafka时,如果不设置分区键(partition key),Kafka 会根据消息的键(key)或消息本身的内容来决定将消息发送到哪个分区。如果没有指定消息的key,Kafka通常会采用默认的分区策略&#…

2026/7/22 0:31:49
flink rocksdb 配置memtable大小

flink rocksdb 配置memtable大小

在使用Apache Flink的RocksDBStateBackend时,配置RocksDB的memtable大小是一个常见的需求,特别是在处理大规模状态数据时。RocksDB的memtable是用来存储键值对数据,直到它们被写入到磁盘上的SSTable文件中的。调整memtable的大小可以影响状态…

2026/7/22 0:31:49
044、Dialect Conversion Infrastructure:TypeConverter与Pattern

044、Dialect Conversion Infrastructure:TypeConverter与Pattern

044、Dialect Conversion Infrastructure:TypeConverter与Pattern 昨晚调一个MLIR的lowering pass到凌晨三点,问题出在类型转换上。一个tensor<*xf32>死活转不过去,TypeConverter报了个“unexpected type”就罢工了。翻遍LLVM的邮件列表,发现两年前就有人踩过这个坑…

2026/7/22 0:26:48

月新闻