当前位置: 首页 > news >正文

VC++实战:使用WinHTTP库高效获取网页源代码的完整指南

1. 项目概述与核心价值

最近在技术社区里,看到不少朋友对“如何用VC++获取网页源代码”这个话题很感兴趣,尤其是在一些需要数据采集、自动化测试或者网络监控的场景下。这确实是一个经典且实用的网络编程入门课题。乍一看,这个标题似乎很简单,不就是发起一个HTTP请求然后把返回的数据读出来吗?但真正动手做起来,你会发现从TCP连接建立、HTTP协议组装、到数据接收和编码处理,每一步都有不少细节和“坑”等着你。我自己在早期做客户端软件,需要检查更新或者拉取一些在线配置时,就反复折腾过这块。

用VC++(这里通常指使用微软Visual C++编译器及配套库的Windows平台C++开发)来做这件事,核心价值在于其原生的Windows API支持和高效的运行时性能。相比于使用Python的requests库或者C#的HttpClient,用C++直接操作WinINet或WinHTTP API,你能获得更底层的控制力,比如精确管理连接超时、自定义请求头、处理代理服务器、乃至实现多线程异步抓取,这对于构建高性能、高稳定性的桌面应用后台模块至关重要。这个项目不仅能帮你巩固Socket编程和HTTP协议的理解,更是打通从“网络小白”到能处理实际网络I/O问题的C++开发者的关键一步。

2. 技术方案选型与对比

在Windows平台上用C++获取网页内容,主流有几种技术路径,各有优劣,选择哪种取决于你的具体需求。

2.1 WinINet库:快速上手的经典选择

WinINet是微软提供的一个高级网络接口,专门用于HTTP、FTP等协议。它的API设计相对友好,封装了底层的Socket连接、协议解析等复杂细节。

优点:

  • 开发便捷:提供了InternetOpenInternetOpenUrlInternetReadFile等一系列直观的函数,可以快速实现HTTP请求。
  • 集成度高:自动处理系统代理设置、Cookie缓存等,行为与IE浏览器类似。
  • 适合简单场景:对于一次性、同步的网页内容获取任务,代码量少,实现简单。

缺点与坑点:

  • 灵活性受限:高级功能(如精确超时控制、连接复用)配置起来比较麻烦。
  • 异步操作复杂:其异步模式回调机制较为陈旧,不易与现代C++的异步范式(如Promise/Future)结合。
  • 进程内依赖:某些版本在DLL中缓存了上下文信息,在复杂进程模型(如DLL卸载)中可能引发问题。

注意:WinINet设计初衷是给交互式客户端使用,官方文档明确指出,在服务或长期运行的应用程序中,更推荐使用WinHTTP。如果你的程序是服务、后台守护进程,或者需要高并发,请谨慎选择WinINet。

2.2 WinHTTP库:服务端与高性能之选

WinHTTP是一个更轻量、更专注于HTTP协议的客户端库,它不包含浏览器特有的缓存、Cookie自动处理等特性,因此更纯净、更可控。

优点:

  • 更稳定可靠:微软推荐用于服务端和非交互式客户端应用。
  • 更好的控制力:提供更精细的连接和请求超时设置。
  • 支持异步I/O:其异步接口基于完成端口(I/O Completion Port),性能更高,更适合高并发场景。
  • 无UI依赖:不依赖用户会话,可以在服务中稳定运行。

缺点:

  • 需要手动处理更多细节:如Cookie管理、重定向处理等,需要自己实现。
  • API稍显繁琐:相比WinINet,初始化、配置会话的步骤可能多一点。

2.3 原生Socket (Winsock) 编程:终极控制与学习利器

直接使用Winsock API,从TCP连接开始,手动构造HTTP请求报文,再解析响应报文。这是最底层、最灵活的方式。

优点:

  • 完全控制:你可以控制网络通信的每一个字节,实现任何自定义协议。
  • 深入理解:是学习HTTP协议和TCP/IP网络编程的最佳途径。
  • 无额外依赖:只依赖最基本的Winsock库,部署简单。

缺点:

  • 开发复杂度高:需要处理DNS解析、连接建立、数据分包、编码转换、响应头解析等一系列问题。
  • 容易出错:缓冲区管理、超时重试、错误处理等都需要精心设计。
  • 代码冗长:实现一个健壮的HTTP客户端需要大量代码。

方案选择建议:对于大多数以“获取网页源代码”为目标的桌面应用程序,如果需求不复杂,追求快速实现,WinINet是一个不错的起点。如果你正在开发一个系统服务、后台程序,或者需要高并发、高性能的抓取任务,WinHTTP是更专业的选择。而如果你是为了学习网络编程原理,或者有非常特殊的协议定制需求,那么从Winsock开始是值得的。

本项目将以WinHTTP作为核心实现方案进行详解,因为它平衡了控制力、稳定性和开发效率,且其知识可迁移性强。理解了WinHTTP,再回头看WinINet或深入Winsock都会更容易。

3. 基于WinHTTP的实战代码解析

我们将一步步构建一个使用WinHTTP同步获取网页源代码的类。同步模式逻辑清晰,便于理解,后续可以在此基础上扩展为异步模式。

3.1 环境准备与项目配置

首先,确保你的开发环境是Visual Studio,并创建一个新的控制台应用程序或MFC/Win32项目。

  1. 包含头文件与库:在需要使用WinHTTP的源文件中,包含头文件并链接库。

    #include <windows.h> #include <winhttp.h> #pragma comment(lib, "winhttp.lib")

    使用#pragma comment是一种便捷的链接方式。你也可以在项目属性 -> 链接器 -> 输入 -> 附加依赖项中手动添加winhttp.lib

  2. 初始化与清理:WinHTTP不需要像Winsock那样显式调用WSAStartup进行初始化,但其内部资源需要正确释放。

3.2 核心类设计与实现

我们设计一个简单的CHttpFetcher类来封装获取网页的逻辑。

class CHttpFetcher { public: CHttpFetcher(); ~CHttpFetcher(); // 同步获取网页内容 BOOL FetchUrl(const std::wstring& url, std::string& outContent, DWORD dwTimeoutMs = 30000); // 获取最后一次错误信息 std::wstring GetLastError() const { return m_lastError; } private: std::wstring m_lastError; // 可以添加更多状态信息,如响应码、响应头等 };

关键实现:FetchUrl方法

这是最核心的方法,我们将其拆解为几个逻辑步骤。

BOOL CHttpFetcher::FetchUrl(const std::wstring& url, std::string& outContent, DWORD dwTimeoutMs) { HINTERNET hSession = NULL; HINTERNET hConnect = NULL; HINTERNET hRequest = NULL; BOOL bResults = FALSE; DWORD dwStatusCode = 0; DWORD dwSize = sizeof(dwStatusCode); outContent.clear(); m_lastError.clear(); // 1. 创建会话句柄 hSession = WinHttpOpen(L"WinHTTP Example/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, // 使用系统代理 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!hSession) { m_lastError = L"WinHttpOpen failed."; goto cleanup; } // 设置超时(重要!避免程序无响应) WinHttpSetTimeouts(hSession, dwTimeoutMs, // 解析超时 dwTimeoutMs, // 连接超时 dwTimeoutMs, // 发送超时 dwTimeoutMs); // 接收超时 // 2. 解析URL,获取主机名和路径 URL_COMPONENTS urlComp = {0}; wchar_t szHostName[256] = {0}; wchar_t szUrlPath[2048] = {0}; urlComp.dwStructSize = sizeof(urlComp); urlComp.lpszHostName = szHostName; urlComp.dwHostNameLength = sizeof(szHostName)/sizeof(wchar_t); urlComp.lpszUrlPath = szUrlPath; urlComp.dwUrlPathLength = sizeof(szUrlPath)/sizeof(wchar_t); urlComp.dwSchemeLength = -1; // 自动识别协议 if (!WinHttpCrackUrl(url.c_str(), url.length(), 0, &urlComp)) { m_lastError = L"WinHttpCrackUrl failed."; goto cleanup; } // 3. 创建连接句柄 hConnect = WinHttpConnect(hSession, szHostName, urlComp.nPort, 0); if (!hConnect) { m_lastError = L"WinHttpConnect failed."; goto cleanup; } // 4. 创建请求句柄 DWORD dwOpenRequestFlags = (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0; hRequest = WinHttpOpenRequest(hConnect, L"GET", szUrlPath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, dwOpenRequestFlags); if (!hRequest) { m_lastError = L"WinHttpOpenRequest failed."; goto cleanup; } // 5. 发送请求 bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); if (!bResults) { m_lastError = L"WinHttpSendRequest failed."; goto cleanup; } // 6. 接收响应 bResults = WinHttpReceiveResponse(hRequest, NULL); if (!bResults) { m_lastError = L"WinHttpReceiveResponse failed."; goto cleanup; } // 7. 检查HTTP状态码(例如,处理404、500等错误) bResults = WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode, &dwSize, WINHTTP_NO_HEADER_INDEX); if (bResults && dwStatusCode != 200) { // 非200状态码,可以记录或处理,这里选择将错误信息放入返回内容或单独记录 wchar_t szStatus[64]; swprintf_s(szStatus, L"HTTP Error: %d", dwStatusCode); m_lastError = szStatus; // 我们仍然可以继续读取服务器返回的错误页面内容 } // 8. 循环读取响应体数据 DWORD dwDownloaded = 0; const DWORD dwChunkSize = 8192; // 每次读取8KB std::vector<char> buffer(dwChunkSize); do { // 读取一块数据 if (!WinHttpReadData(hRequest, buffer.data(), dwChunkSize, &dwDownloaded)) { m_lastError = L"WinHttpReadData failed."; bResults = FALSE; break; } if (dwDownloaded == 0) { break; // 读取完毕 } // 将读取到的数据追加到输出字符串 outContent.append(buffer.data(), dwDownloaded); } while (dwDownloaded > 0); cleanup: // 9. 按顺序关闭所有句柄(重要!) if (hRequest) WinHttpCloseHandle(hRequest); if (hConnect) WinHttpCloseHandle(hConnect); if (hSession) WinHttpCloseHandle(hSession); return bResults && !outContent.empty(); // 成功标志:操作成功且获取到了内容 }

3.3 关键步骤与参数详解

  1. WinHttpOpen:创建会话根句柄。第一个参数是用户代理字符串,有些网站会根据这个字符串返回不同内容。WINHTTP_ACCESS_TYPE_DEFAULT_PROXY表示使用IE或系统设置的代理,这在企业内网环境中非常有用。如果你想绕过代理,可以使用WINHTTP_ACCESS_TYPE_NO_PROXY

  2. WinHttpSetTimeouts这是避免程序“卡死”的关键!务必为每个环节设置合理的超时。网络环境复杂,DNS解析失败、服务器无响应等情况都可能发生。示例中为所有阶段设置了相同的超时,实际可以根据需要分别调整。

  3. WinHttpCrackUrl:一个非常实用的函数,帮你安全地解析URL,分离出协议、主机、端口、路径等部分,避免了手动字符串解析可能带来的安全漏洞(如缓冲区溢出)。

  4. WinHttpOpenRequest:创建请求。这里指定了HTTP方法为“GET”。第三个参数是路径(包含查询字符串)。如果要发送POST数据,需要在这里指定内容类型,并在WinHttpSendRequest中传入数据。WINHTTP_FLAG_SECURE标志用于HTTPS连接,它会自动处理SSL/TLS握手。

  5. 数据读取循环WinHttpReadData可能一次无法读取完所有数据,必须循环调用直到返回的dwDownloaded为0。使用std::stringstd::vector<char>来动态累积数据是高效且安全的方式。

  6. 资源清理:使用goto cleanup进行集中错误处理和资源释放是C语言风格下清晰且安全的做法。务必按照请求->连接->会话的逆序关闭句柄。

4. 编码处理与内容解析

获取到的原始数据(outContent)是一个字节流。网页的字符编码信息通常包含在HTTP响应头或HTML的<meta>标签中。正确处理编码是显示正确中文或其它多字节文字的关键。

4.1 从HTTP头获取编码

在读取数据之前或之后,可以查询响应头。

std::wstring GetResponseHeader(HINTERNET hRequest, const std::wstring& headerName) { DWORD dwSize = 0; std::wstring headerValue; // 第一次调用,获取所需缓冲区大小 WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CUSTOM, headerName.c_str(), WINHTTP_NO_OUTPUT_BUFFER, &dwSize, WINHTTP_NO_HEADER_INDEX); if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { headerValue.resize(dwSize / sizeof(wchar_t)); if (WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CUSTOM, headerName.c_str(), headerValue.data(), &dwSize, WINHTTP_NO_HEADER_INDEX)) { // 成功获取 } } return headerValue; } // 调用示例,获取Content-Type std::wstring contentType = GetResponseHeader(hRequest, L"Content-Type"); // contentType可能类似:L"text/html; charset=utf-8"

4.2 从HTML Meta标签解析编码

如果HTTP头没有指定,则需要从HTML内容的前几KB中解析<meta charset="..."><meta http-equiv="Content-Type" content="...">标签。这需要一个简单的字符串查找和解析逻辑。

4.3 执行编码转换

得到编码信息(如“utf-8”、“gb2312”)后,需要使用Windows的MultiByteToWideCharWideCharToMultiByte函数进行转换,或者使用C++11的<codecvt>库(已弃用但可用)或第三方库如iconv。

一个将UTF-8字符串(常见编码)转换为Windows宽字符串(UTF-16)的示例:

std::wstring UTF8ToWide(const std::string& str) { if (str.empty()) return std::wstring(); int size_needed = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), NULL, 0); std::wstring wstrTo(size_needed, 0); MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), &wstrTo[0], size_needed); return wstrTo; }

实操心得:很多中文网站使用GBK或GB2312编码。如果转换后仍是乱码,可以尝试将CP_UTF8替换为936(GBK的代码页)。更健壮的做法是,优先使用HTTP头中的编码信息,如果没有,再尝试从HTML中解析,并准备一个常见编码的列表(如UTF-8, GBK, BIG5)进行逐一尝试,直到转换出的文字可读。

5. 高级功能与健壮性增强

基础的GET请求跑通后,可以考虑以下增强,让你的HTTP客户端更实用、更健壮。

5.1 添加自定义请求头

有些API或网站需要特定的Header,如User-AgentAuthorizationCookie等。

// 在WinHttpSendRequest之前调用 BOOL AddRequestHeader(HINTERNET hRequest, const std::wstring& header, const std::wstring& value) { std::wstring fullHeader = header + L": " + value; return WinHttpAddRequestHeaders(hRequest, fullHeader.c_str(), (ULONG)-1L, WINHTTP_ADDREQ_FLAG_ADD); } // 示例:设置一个自定义的User-Agent AddRequestHeader(hRequest, L"User-Agent", L"MyCrawler/1.0 (Windows NT 10.0)");

5.2 处理HTTPS与SSL证书

WinHTTP默认会验证服务器SSL证书。在开发测试阶段,连接到自签名证书的服务器可能会失败。生产环境强烈不建议禁用证书验证。仅在测试时,可以如下设置:

DWORD dwSecurityFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_CERT_CN_INVALID; WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwSecurityFlags, sizeof(dwSecurityFlags));

5.3 实现异步操作

同步请求会阻塞线程。对于GUI程序或需要同时抓取多个页面的场景,异步模式是必须的。WinHTTP的异步操作基于回调函数。

  1. WinHttpOpen时,设置WINHTTP_FLAG_ASYNC标志。
  2. 使用WinHttpSetStatusCallback设置一个回调函数。
  3. 在回调函数中处理各种状态通知,如WINHTTP_CALLBACK_STATUS_REQUEST_SENTWINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLEWINHTTP_CALLBACK_STATUS_DATA_AVAILABLEWINHTTP_CALLBACK_STATUS_READ_COMPLETE
  4. 所有WinHTTP函数调用将立即返回,操作结果通过回调函数和OVERLAPPED结构返回。

异步编程模型更复杂,需要管理请求上下文、线程安全等问题,但能极大提升程序的响应能力和吞吐量。

5.4 连接复用与性能优化

频繁创建和断开TCP连接(HTTP短连接)开销很大。HTTP/1.1默认支持持久连接(Keep-Alive),WinHTTP在同一个会话句柄(hSession)下对同一主机端口的多个请求,会尝试复用连接。

优化建议:对于需要批量抓取同一网站页面的任务,应该复用hSessionhConnect句柄,只创建和销毁hRequest句柄。可以将CHttpFetcher类改造成维护一个到特定主机的持久连接。

6. 常见问题排查与调试技巧

即使代码逻辑正确,网络编程中仍会遇到各种问题。以下是一些常见坑点及排查方法。

6.1 访问返回空或失败

  • 检查网络连接与代理:确保机器能正常访问目标网站。如果公司有代理服务器,WinHttpOpen使用WINHTTP_ACCESS_TYPE_DEFAULT_PROXY通常能自动检测。如果不行,可以尝试显式设置代理:WINHTTP_ACCESS_TYPE_NAMED_PROXY,并指定代理地址和端口。
  • 检查URL格式:确保URL完整,特别是HTTPS的URL。WinHttpCrackUrl失败是一个明显的信号。
  • 查看错误代码:每次WinHTTP API调用失败后,立即调用GetLastError()获取错误码。使用FormatMessage函数将错误码转换为可读信息,这是定位问题的第一手资料。
    DWORD dwError = GetLastError(); wchar_t errMsg[256]; FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, 0, errMsg, 256, NULL); m_lastError = errMsg; // 记录到类的错误信息中
  • 使用抓包工具:如Wireshark或Fiddler。配置Fiddler作为代理,让你的程序通过Fiddler发送请求,可以清晰地看到发出的HTTP请求和收到的响应,包括所有头信息和原始数据,这对于调试协议问题、查看服务器返回的错误信息至关重要。

6.2 程序在WinHttpReceiveResponseWinHttpReadData处卡住

这几乎都是超时设置问题。务必调用WinHttpSetTimeouts为会话设置合理的超时。网络环境不稳定、服务器响应慢、或者目标页面太大,都可能导致读取时间过长。根据应用场景调整超时值,例如,对于内部API可以设短点(5-10秒),对于公网页面可以设长点(30-60秒)。

6.3 获取到的中文是乱码

这是编码问题。请严格按照第4章的方法处理。

  1. 首先确认你获取到的原始数据是否正确。可以在调试器中以十六进制查看outContent的前几个字节。对于UTF-8编码的中文“你好”,字节序列是E4 BD A0 E5 A5 BD
  2. 检查HTTP响应头中的Content-Type
  3. 如果HTTP头没有,检查HTML文件开头的<meta>标签。
  4. 使用正确的代码页进行转换。UTF-8用CP_UTF8,GBK用936

6.4 内存泄漏与句柄泄漏

WinHTTP的句柄是系统资源,必须关闭。

  • 确保所有路径都关闭句柄:使用goto cleanup模式或C++ RAII(资源获取即初始化)技术来管理句柄生命周期。可以编写一个简单的WinHttpHandleGuard类,在析构函数中调用WinHttpCloseHandle
  • 检查关闭顺序:虽然理论上关闭顺序不影响,但按照请求->连接->会话的顺序关闭是良好的习惯。

6.5 多线程下的使用

WinHTTP会话句柄(hSession)可以在多个线程中用于创建不同的连接和请求,但同一个请求句柄(hRequest)及其相关的连接句柄(hConnect)不应在多个线程间同时操作。安全的做法是为每个线程创建独立的会话,或者使用连接池并进行加锁保护。

7. 从同步到异步的改造思路

将上述同步示例改造为异步模式,能显著提升程序效率。核心思路如下:

  1. 创建异步会话WinHttpOpen时传入WINHTTP_FLAG_ASYNC
  2. 设置回调函数:使用WinHttpSetStatusCallback
  3. 修改调用流程:不再使用阻塞式的WinHttpReceiveResponse和循环WinHttpReadData。改为:
    • WinHttpSendRequest发起请求后立即返回。
    • 在回调函数中,收到WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE状态时,调用WinHttpQueryHeadersWinHttpQueryDataAvailable
    • 收到WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE时,根据提示的数据大小分配缓冲区,然后调用WinHttpReadData
    • 收到WINHTTP_CALLBACK_STATUS_READ_COMPLETE时,处理读取到的数据块,并再次调用WinHttpQueryDataAvailable,直到数据大小为0。
  4. 上下文管理:每个异步操作都需要一个上下文结构(通常包含this指针、缓冲区、状态等),通过WinHttpSetOptionWINHTTP_OPTION_CONTEXT_VALUE关联到请求句柄,以便在回调函数中识别是哪个请求的数据。

异步模式代码结构更复杂,但它是构建高性能网络应用的基石。建议先彻底理解同步模式,再逐步挑战异步实现。

8. 进阶方向与项目扩展

掌握了基础的单线程同步获取后,你可以尝试以下方向,将这个简单的功能模块发展成一个强大的网络工具库。

  • 实现POST/PUT等操作:修改WinHttpOpenRequest的方法参数,并在WinHttpSendRequest中传入请求体数据。注意设置Content-TypeContent-Length头。
  • 处理Cookie和会话:手动从响应头Set-Cookie中提取Cookie,并在后续请求的Cookie头中发送。WinHTTP也提供了WinHttpSetOption配合WINHTTP_OPTION_COOKIES进行自动管理,但可控性不如手动处理。
  • 实现重定向跟随:检查HTTP状态码301/302/307等,从Location响应头中获取新的URL,重新发起请求。注意防止重定向循环。
  • 构建多线程爬虫框架:设计一个任务队列,一个线程池。每个工作线程从队列中取出URL,使用独立的HTTP客户端(或共享连接池)进行抓取,将结果保存或回调。需要仔细处理线程同步和资源竞争。
  • 集成HTML解析库:获取到源代码后,下一步通常是解析。可以集成像libxml2(HTML解析模块)或Gumbo-parser这样的纯C解析库,来提取链接、文本内容或特定数据。
  • 添加代理池和用户代理轮换:应对反爬虫策略。维护一个代理IP列表和User-Agent字符串列表,每次请求随机选取。

网络编程的魅力在于,从一个简单的“获取网页源代码”需求出发,你可以深入到协议、性能、架构、安全等各个层面。每一次问题的排查和解决,都是对计算机系统理解的一次加深。希望这份详细的实战指南,能为你用VC++进行网络编程打开一扇门,剩下的精彩,就靠你在具体的项目中去探索和实现了。

http://www.cnnetsun.cn/news/3963322.html

相关文章:

  • 2026年Claude Code Desktop 桌面版接入第三方中转网关API网站 Claude Opus 5模型详细教程
  • AI代码生成实战:Codex如何攻克五大编程痛点与高效集成指南
  • 5个步骤轻松搭建原神私服:KCN-GenshinServer完整指南
  • ArkClaw框架开发《钳子大作战》小游戏实战解析
  • aaaaaaaaaa3333333
  • Claude Code自动模式详解:AI编程助手的权限控制与安全实践
  • Python实战LSM模型:量化可转债定价与套利策略
  • 免费调用Kimi K3与GLM-5.2 API:低成本构建AI应用实战指南
  • 即时零售缺货时怎么处理?替代商品、补差价和退款要按订单阶段定
  • 从“量程够用”到系统误差:新能源电流传感器怎么选?
  • Java 微服务架构:从单体到分布式的演进之路
  • 盘锦新房瓷砖怎么选,入住后才知道这些坑?
  • 单片机毕业设计-基于 STM32 单片机的环境光人体检测智能灯具设计 基于 STM32 的自动手动双模式 10 档可调智能台灯系统(018302)
  • 【计算机毕业设计单片机案例】基于 STM32 单片机的室内自适应感应台灯控制器开发 基于 STM32 的人机交互双模式智能调光灯具研发(018302)
  • AI 发展这么快,等研究生 3 年毕业,会不会岗位都被淘汰呢?
  • **具有转储功能的电池供电的低功耗电导率传感器-使用说明书**
  • 全栈后端开发核心技术体系与实战指南
  • 单片机毕业设计-基于单片机的医护双向无线呼叫报警系统设计 基于 STM32/51 单片机与 LCD1602 的病房呼叫显示终端开发(020102)
  • 【单片机课设毕设项目】多床位并行呼叫优先级处理无线控制系统实现 基于 NRF24L01 的主从式病房双向呼叫报警装置开发(020102)
  • 视觉SLAM相机成像几何模型:从针孔模型到OpenCV实战
  • Spring Boot交通违章管理系统开发实践
  • G-Helper:重新定义华硕笔记本的轻量级性能控制体验
  • RTL8852BE Wi-Fi 6驱动深度解析:从架构设计到性能调优的实战指南
  • 5个简单步骤:使用LeaguePrank免费个性化你的英雄联盟客户端
  • Frida内存Dump技术:从Android SO文件提取到ELF修复实战
  • AS3.0项目GPU加速实战:Starling框架迁移与性能优化指南
  • 5分钟掌握VideoDownloadHelper:你的智能浏览器视频下载助手
  • 基于python大数据分析项目-文本情感分析3(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 超星学习通强制使用、过度索取与免责闭环调查
  • 好消息--------目前网站已经有少数人开始访问了