当前位置: 首页 > 编程日记 > 正文

实现HTTP协议Get、Post和文件上传功能——使用libcurl接口实现

之前我们已经详细介绍了WinHttp接口如何实现Http的相关功能。本文我将主要讲解如何使用libcurl库去实现相关功能。(转载请指明出于breaksoftware的csdn博客)

libcurl在http://curl.haxx.se/libcurl/有详细的介绍,有兴趣的朋友可以去读下。本文我只是从实际使用的角度讲解其中的一些功能。

libcurl中主要有两个接口类型:CURL和CURLM。CURL又称easy interface,它接口简单、使用方便,但是它是一个同步接口,我们不能使用它去实现异步的功能——比如下载中断——其实也是有办法的(比如对写回调做点手脚)。相应的,CURLM又称multi interface,它是异步的。可以想下,我们使用easy interface实现一个HTTP请求过程,如果某天我们需要将其改成multi interface接口的,似乎需要对所有接口都要做调整。其实不然,libcurl使用一种优雅的方式去解决这个问题——multi interface只是若干个easy interface的集合。我们只要把easy interface指针加入到multi interface中即可。

CURLMcode curl_multi_add_handle(CURLM *multi_handle, CURL *easy_handle);

本文将使用multi interface作为最外层的管理者,具体下载功能交给easy interface。在使用easy interface之前,我们需要对其初始化

初始化

初始化easy interface

bool CHttpRequestByCurl::Prepare() {bool bSuc = false;do {if (!m_pCurlEasy) {m_pCurlEasy = curl_easy_init();}if (!m_pCurlEasy) {break;}

初始化multi interface

            if (!m_pCurlMulti){m_pCurlMulti = curl_multi_init();}if (!m_pCurlMulti) {break;}

设置

设置过程回调

过程回调用于体现数据下载了多少或者上传了多少

		CURLcode easycode;easycode = curl_easy_setopt( m_pCurlEasy, CURLOPT_NOPROGRESS, 0 );CHECKCURLEASY_EROORBREAK(easycode);easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_PROGRESSFUNCTION, progresscallback);CHECKCURLEASY_EROORBREAK(easycode);easycode = curl_easy_setopt( m_pCurlEasy, CURLOPT_PROGRESSDATA, this );CHECKCURLEASY_EROORBREAK(easycode);

设置CURLOPT_NOPROGRESS代表我们需要使用过程回调这个功能。设置CURLOPT_PROGRESSFUNCTION为progresscallback是设置回调函数的指针,我们将通过静态函数progresscallback反馈过程状态。注意一下这儿,因为libcurl是一个C语言API库,所以它没有类的概念,这个将影响之后我们对各种静态回调函数的设置。此处要求progresscallback是一个静态函数——它也没有this指针,但是libcurl设计的非常好,它留了一个用户自定义参数供我们使用,这样我们便可以将对象的this指针通过CURLOPT_PROGRESSDATA传过去。

	int CHttpRequestByCurl::progresscallback( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow ) {if (clientp) {CHttpRequestByCurl* pThis = (CHttpRequestByCurl*)clientp;return pThis->ProcessCallback(dltotal, dlnow);}else {return -1;}}int CHttpRequestByCurl::ProcessCallback( double dltotal, double dlnow ) {if ( m_CallBack ) {const DWORD dwMaxEslapeTime = 500;std::ostringstream os;os << (unsigned long)dlnow;std::string strSize = os.str();std::ostringstream ostotal;ostotal << (unsigned long)dltotal;std::string strContentSize = ostotal.str();DWORD dwTickCount = GetTickCount();if ( ( 0 != ((unsigned long)dltotal)) && ( strSize == strContentSize || dwTickCount - m_dwLastCallBackTime > dwMaxEslapeTime ) ) {m_dwLastCallBackTime = dwTickCount;m_CallBack( strContentSize, strSize );}}return 0;}

此处progresscallback只是一个代理功能——它是静态的,它去调用clientp传过来的this指针所指向对象的ProcessCallback成员函数。之后我们的其他回调函数也是类似的,比如写结果的回调设置

设置写结果回调

		easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_WRITEFUNCTION, writefilecallback);CHECKCURLEASY_EROORBREAK(easycode);easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_WRITEDATA, this);CHECKCURLEASY_EROORBREAK(easycode);
	size_t CHttpRequestByCurl::writefilecallback( void *buffer, size_t size, size_t nmemb, void *stream ) {if (stream) {CHttpRequestByCurl* pThis = (CHttpRequestByCurl*)stream;return pThis->WriteFileCallBack(buffer, size, nmemb);}else {return size * nmemb;}}size_t CHttpRequestByCurl::WriteFileCallBack( void *buffer, size_t size, size_t nmemb ) {if (!m_pCurlEasy) {return 0;}int nResponse = 0;CURLcode easycode = curl_easy_getinfo(m_pCurlEasy, CURLINFO_RESPONSE_CODE, &nResponse);if ( CURLE_OK != easycode || nResponse >= 400 ) {return 0;}return Write(buffer, size, nmemb);}

在WriteFileCallBack函数中,我们使用curl_easy_getinfo判断了easy interface的返回值,这是为了解决接收返回结果时服务器中断的问题。

设置读回调

读回调我们并没有传递this指针过去。

            easycode = curl_easy_setopt( m_pCurlEasy,  CURLOPT_READFUNCTION,  read_callback);CHECKCURLEASY_EROORBREAK(easycode);

我们看下回调就明白了

    size_t CHttpRequestByCurl::read_callback( void *ptr, size_t size, size_t nmemb, void *stream ) {return ((ToolsInterface::LPIMemFileOperation)(stream))->MFRead(ptr, size, nmemb);}

这次用户自定义指针指向了一个IMemFileOperation对象指针,它是在之后的其他步奏里传递过来的。这儿有个非常有意思的地方——即MFRead的返回值和libcurl要求的read_callback返回值是一致的——并不是说类型一致——而是返回值的定义一致。这就是统一成标准接口的好处。

设置URL

            easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_URL, m_strUrl.c_str());CHECKCURLEASY_EROORBREAK(easycode);

设置超时时间

            easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_TIMEOUT_MS, m_nTimeout);CHECKCURLEASY_EROORBREAK(easycode);

设置Http头

            for ( ToolsInterface::ListStrCIter it = m_listHeaders.begin(); it != m_listHeaders.end(); it++ ) {m_pHeaderlist = curl_slist_append(m_pHeaderlist, it->c_str());}if (m_pHeaderlist) {curl_easy_setopt(m_pCurlEasy, CURLOPT_HTTPHEADER, m_pHeaderlist);}

这儿需要注意的是m_pHeaderlist在整个请求完毕后需要释放

		if (m_pHeaderlist) {curl_slist_free_all (m_pHeaderlist);m_pHeaderlist = NULL;}

设置Agent

            if (!m_strAgent.empty()) {easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_USERAGENT, m_strAgent.c_str());CHECKCURLEASY_EROORBREAK(easycode);}

设置Post参数

            if ( ePost == GetType() ) {easycode = ModifyEasyCurl(m_pCurlEasy, m_Params);CHECKCURLEASY_EROORBREAK(easycode);}

之后我们将讲解ModifyEasyCurl的实现。我们先把整个调用过程将完。

将easy interface加入到multi interface

            CURLMcode multicode = curl_multi_add_handle( m_pCurlMulti, m_pCurlEasy );CHECKCURLMULTI_EROORBREAK(multicode);bSuc = true;} while (0);return bSuc;
}

运行

    EDownloadRet CHttpRequestByCurl::Curl_Multi_Select(CURLM* pMultiCurl){EDownloadRet ERet = EContinue;do {struct timeval timeout;fd_set fdread;fd_set fdwrite;fd_set fdexcep;CURLMcode multicode;long curl_timeo = -1;/* set a suitable timeout to fail on */ timeout.tv_sec = 30; /* 30 seconds */ timeout.tv_usec = 0;multicode = curl_multi_timeout(pMultiCurl, &curl_timeo);if ( CURLM_OK == multicode && curl_timeo >= 0 ) {timeout.tv_sec = curl_timeo / 1000;if (timeout.tv_sec > 1) {timeout.tv_sec = 0;} else {timeout.tv_usec = (curl_timeo % 1000) * 1000;}}int nMaxFd = -1;while ( -1 == nMaxFd ) {FD_ZERO(&fdread);FD_ZERO(&fdwrite);FD_ZERO(&fdexcep);multicode = curl_multi_fdset( m_pCurlMulti, &fdread, &fdwrite, &fdexcep, &nMaxFd );CHECKCURLMULTI_EROORBREAK(multicode);if ( -1 != nMaxFd ) {break;}else {if (WAIT_TIMEOUT != WaitForSingleObject(m_hStop, 100)) {ERet = EInterrupt;break;}int nRunning = 0;CURLMcode multicode = curl_multi_perform( m_pCurlMulti, &nRunning );CHECKCURLMULTI_EROORBREAK(multicode);}}if ( EContinue == ERet ) {int nSelectRet = select( nMaxFd + 1, &fdread, &fdwrite, &fdexcep, &timeout );if ( -1 == nSelectRet ){ERet = EFailed;}}if ( EInterrupt == ERet ) {break;}} while (0);return ERet;}DWORD CHttpRequestByCurl::StartRequest() {Init();EDownloadRet eDownloadRet = ESuc;do {if (!Prepare()) {break;}int nRunning = -1;while( CURLM_CALL_MULTI_PERFORM == curl_multi_perform(m_pCurlMulti, &nRunning) ) {if (WAIT_TIMEOUT != WaitForSingleObject(m_hStop, 10)) {eDownloadRet = EInterrupt;break;}}if ( EInterrupt == eDownloadRet ) {break;}while(0 != nRunning) {EDownloadRet nSelectRet = Curl_Multi_Select(m_pCurlMulti);if ( EFailed == nSelectRet || EInterrupt == nSelectRet || ENetError == nSelectRet ) {eDownloadRet = nSelectRet;break;}else {CURLMcode multicode = curl_multi_perform(m_pCurlMulti, &nRunning);if (CURLM_CALL_MULTI_PERFORM == multicode) {if (WAIT_TIMEOUT != WaitForSingleObject(m_hStop, 10)) {eDownloadRet = EInterrupt;break;}}else if ( CURLM_OK == multicode ) {}else {if (WAIT_TIMEOUT != WaitForSingleObject(m_hStop, 100)) {eDownloadRet = EInterrupt;}break;}}if ( EInterrupt == eDownloadRet ) {break;}} // whileif ( EInterrupt == eDownloadRet ) {break;}int msgs_left;  CURLMsg*  msg;  while((msg = curl_multi_info_read(m_pCurlMulti, &msgs_left))) {  if (CURLMSG_DONE == msg->msg) { if ( CURLE_OK != msg->data.result ) {eDownloadRet = EFailed;}}else {eDownloadRet = EFailed;}}} while (0);Unint();m_bSuc = ( ESuc == eDownloadRet ) ? true : false;return eDownloadRet;}

可以见得运行的主要过程就是不停的调用curl_multi_perform。

实现Post、文件上传功能

对于MultiPart格式数据,我们要使用curl_httppost结构体保存参数

组装上传文件

    CURLcode CPostByCurl::ModifyEasyCurl_File( CURL* pEasyCurl, const FMParam& Param ) {Param.value->MFSeek(0L, SEEK_END);long valuesize = Param.value->MFTell();Param.value->MFSeek(0L, SEEK_SET);curl_formadd((curl_httppost**)&m_pFormpost,(curl_httppost**)&m_pLastptr,CURLFORM_COPYNAME, Param.strkey.c_str(),CURLFORM_STREAM, Param.value, CURLFORM_CONTENTSLENGTH, valuesize,CURLFORM_FILENAME, Param.fileinfo.szfilename,CURLFORM_CONTENTTYPE, "application/octet-stream",CURLFORM_END);return CURLE_OK;}

我们使用CURLFORM_STREAM标记数据的载体,此处我们传递的是一个IMemFileOperation指针,之前我们定义的readcallback回调将会将该参数作为第一个参数被调用。CURLFORM_CONTENTSLENGTH也是个非常重要的参数。如果我们不设置CURLFORM_CONTENTSLENGTH,则传递的数据长度是数据起始至\0结尾。所以我们在调用curl_formadd之前先计算了数据的长度——文件的大小。然后指定CURLFORM_FILENAME为服务器上保存的文件名。

组装上传数据

    CURLcode CPostByCurl::ModifyEasyCurl_Mem( CURL* pEasyCurl, const FMParam& Param ) {if (Param.meminfo.bMulti) {Param.value->MFSeek(0L, SEEK_END);long valuesize = Param.value->MFTell();Param.value->MFSeek(0L, SEEK_SET);curl_formadd(&m_pFormpost, &m_pLastptr, CURLFORM_COPYNAME, Param.strkey.c_str(), CURLFORM_STREAM, Param.value, CURLFORM_CONTENTSLENGTH, valuesize,CURLFORM_CONTENTTYPE, "application/octet-stream",CURLFORM_END );}else {if (!m_strCommonPostData.empty()) {m_strCommonPostData += "&";}std::string strpostvalue;while(!Param.value->MFEof()) {char buffer[1024] = {0};size_t size = Param.value->MFRead(buffer, 1, 1024);strpostvalue.append(buffer, size);}m_strCommonPostData += Param.strkey;m_strCommonPostData += "=";m_strCommonPostData += strpostvalue;}return CURLE_OK;}

对于需要MultiPart格式发送的数据,我们发送的方法和文件发送相似——只是少了CURLFORM_FILENAME设置——因为没有文件名。

对于普通Post数据,我们使用m_strCommonPostData拼接起来。待之后一并发送。

设置数据待上传

CURLcode CPostByCurl::ModifyEasyCurl( CURL* pEasyCurl, const FMParams& Params ) {for (FMParamsCIter it = m_PostParam.begin(); it != m_PostParam.end(); it++ ) {if (it->postasfile) {ModifyEasyCurl_File(pEasyCurl, *it);}else {ModifyEasyCurl_Mem(pEasyCurl, *it);}}if (m_pFormpost){curl_easy_setopt(pEasyCurl, CURLOPT_HTTPPOST, m_pFormpost);}if (!m_strCommonPostData.empty()) {curl_easy_setopt(pEasyCurl, CURLOPT_COPYPOSTFIELDS, m_strCommonPostData.c_str());}return CURLE_OK;
}

通过设置CURLOPT_HTTPPOST,我们将MultiPart型数据——包括文件上传数据设置好。通过设置CURLOPT_COPYPOSTFIELDS,我们将普通Post型数据设置好。

Get型请求没什么好说的。详细见之后给的工程源码。

工程源码链接:http://pan.baidu.com/s/1i3eUnMt 密码:hfro

相关文章:

32岁程序员,补偿N+2:“谢谢裁我,让我翻倍!” 网友:榜样!

2019年的冬天&#xff0c;“冷”的有些频繁。12月19日&#xff0c;《马蜂窝被曝裁员40% UGC模式变现难&#xff1f;》爆火&#xff0c;据悉马蜂窝将裁员40%&#xff0c;交易中心成了“重灾区”&#xff0c;赔偿N2&#xff0c;留下的除搜索推荐、内容中心等核心部门外&#xff0…

山有木兮木有枝,心悦君兮君不知

《越人歌》今夕何夕兮&#xff0c;搴舟中流。 今日何日兮&#xff0c;得与王子同舟 蒙羞被好兮&#xff0c;不訾诟耻 心几烦而不绝兮&#xff0c;得知王子 山有木兮木有枝&#xff0c;心悦君兮君不知。本是《夜宴》中的&#xff0c;"山有木兮木有枝&#xff0c;心悦君兮君…

浅析电商、社区、游戏常用的 MySQL 架构

一般、或者必须是这样、MySQL 架构一定要结合业务来分析、设计、优化 所以不管是那种架构、根据业务要求组合成符合需求的即是最好的、不能泛泛而谈 同时、也必须注意数据的安全&#xff08;如ipsec,ssh&#xff0c;vpn传输&#xff09; 常见的架构都是进行业务切…

基于Co-Attention和Co-Excitation的少样本目标检测 | NeurIPS 2019

「免费学习 60 节公开课&#xff1a;投票页面&#xff0c;点击讲师头像」作者 | VincentLee来源 | 晓飞的算法工程笔记&#xff08;ID: gh_084c810bc839&#xff09;导读&#xff1a;论文提出CoAE少样本目标检测算法&#xff0c;该算法使用non-local block来提取目标图片与查询…

服务器架设笔记——搭建用户注册和验证功能

之前介绍的Apache Httpd相关内容&#xff0c;都是些零散的知识点。而实际运用中&#xff0c;我们要根据不同的业务&#xff0c;将这些知识点连接起来以形成各种组合&#xff0c;来满足我们的需求。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 本文我将以用…

项目管理过程中应注意的问题

软件项目从角色分工方面可以划分为研发、开发和实施三类&#xff0c;每个类型的项目有各自的管理过程。下面笔者就公司实施类项目的经历&#xff0c;从项目经理的角度谈一谈实施类项目管理过程中应该注意的一些问题&#xff0c;希望大家共勉。确定项目概况俗话说&#xff1a;“…

原创jQuery移动设备弹出框插件——msgalert.js

最近开发经常会用到顶部弹出框&#xff0c;虽然有现成的&#xff08;bootstrap等&#xff09;&#xff0c;但是都很臃肿&#xff0c;对于有些时候移动端活动页面有点大材小用。所以今晚花了20分钟写了一个通用的插件,我将其命名为msgalert.js。因为定位是jQuery插件&#xff0c…

AbutionGraph:构建以知识图谱为核心的下一代数据中台

「免费学习 60 节公开课&#xff1a;投票页面&#xff0c;点击讲师头像」作者 | 图特摩斯科技创始人闭雨哲出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;前言图特摩斯科技&#xff08;Thutmose&#xff09;基于自研的图形数据库AbutionGraph&#xff08;实时多维数…

服务器架设笔记——多模块和全局数据

随着项目工程的发展&#xff0c;多模块设计和性能优化是在所难免的。本文我将基于一些现实中可能遇到的需求&#xff0c;讲解如何在Apache的Httpd插件体系中实现这些功能。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 之前我碰到两个需求&#xff1a; 需要…

JSP学习笔记(七):使用JavaBean

bean.java publicclassB1 { publicString getString() { return"content"; } }page.jsp <%B1 b1 newB1(); out.print(b1.getString());%>

折返(Reentrancy)VS线程安全(Thread safety)

在Wiki上&#xff0c;折返例如&#xff0c;下面的定义&#xff08;接&#xff09; In computing, a computer program or subroutine is called reentrant if it can be interrupted in the middle of its execution and then safely called again ("re-entered") be…

服务器架设笔记——httpd插件支持mysql字符集选择

mysql数据库默认的字符集是latin1。默认情况下&#xff0c;我们编译的httpd插件是可以正常读取该类型的数据库&#xff0c;并且不会出现乱码。但是&#xff0c;如果我们的数据库变成其他格式&#xff0c;比如UTF8&#xff0c;那么默认读取出来的数据就是乱码&#xff0c;且无论…

只需3行代码自动生成高性能模型,支持4项任务,亚马逊发布开源库AutoGluon

作者 | KYLE WIGGERS编译 | AI科技大本营&#xff08;ID:rgznai100&#xff09;构建涉及图像、文本和表格数据集的机器学习应用并不容易。它需要特征工程或使用数据领域知识来创建使AI算法起作用的特征&#xff0c;还需要进行大量数据预处理&#xff0c;以确保训练模型时不会出…

在客户端(IE中)无法登录Citrix MetaFrame server的原因

当在IE中登录服务器时如果出现这面的错误提示&#xff1a; ERROR: The Citrix MetaFrame servers cannot process your request at this time. The Citrix XML Service object was not found. [404 Not Found] 我的原因是IE使用了8080端口&#xff0c;而我的Citrix XML servic…

虚拟化--015 配置VMware View Event database失败:

015 配置VMware View Event database失败&#xff1a;参考链接http://url.cn/VTq4zN 转载于:https://blog.51cto.com/williamliuwen/1686536

以金山界面库(openkui)为例思考和分析界面库的设计和实现——代码结构(完)

三年前&#xff0c;准备将金山界面库做一个全面的剖析。后来由于种种原因&#xff0c;这个系列被中断而一直没有更新。时过境迁&#xff0c;现在在windows上从事开发的人员越来越少&#xff0c;关注这块的技术的朋友也很少了。本以为这系列也随着技术的没落而不再被人所关注&am…

一包烟钱买到电动剃须刀,小米有品告诉你什么叫性价比

男人身上长得最快的是什么&#xff1f;答案是胡须。一名健康男性的胡须每天都要生长超过 0.4mm&#xff0c;比咱们头发的生长速度还快&#xff0c;这也是男人隔三差五就要剃须的原因之一。男人的一生是与胡子战斗一生&#xff0c;也是被剃须刀拖累的一生。出差办事儿&#xff0…

Label控件属性AssociatedControlID

可以使用Label控件来标注一个HTML表单字段。Label控件拥有属性AssociatedControlID,可以设置此属性来指向表示表单字段的ASP.NET控件。 例如,代码清单2-3中的页面含有一个简单的表单,表单包含两个字段用于输入名和姓。Label控件用于标注这两个TextBox控件。 Code<% Page Lan…

2015_8_21作业——有自翻译有复制他人的英语太差

date作用&#xff1a;打印或设置系统日期和时间格式:date [OPTION]...[FORMAT]date [u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]|是或 多选一的选项 ...代表同类内容可多次选项&#xff1a;注意短选项后不可加号-d,--dateSTRING显示时间字符串但不是立即&#xff1a;如date…

WMI技术介绍和应用——接收事件

时隔两三年&#xff0c;再次更新WMI系列博文。好在功能在三年前就已经实现了&#xff0c;现在只要补充些实例即可。 之前介绍的基本都是查询静态数据&#xff0c;而本文将要介绍非常有意思的事件接收功能。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 监控…

WML标签速查手册

WML标签速查手册 结构相关标签语法及属性<wml><wml xml:lang"lang" >    content</wml> WML元素的共有属性主要有3个&#xff0c;即id、class和xml:lang属性。WML的所有元素都有两个核心属性&#xff0c;即标识(id)和类(class)属性。它们主要用…

Python 三十大实践、建议和技巧

所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项&#xff0c;还有更多福利赠送作者 | Erik-Jan van Baaren译者 | 凯隐编辑 | Jane出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;【导读】2020年&#xff0c;你又立了什么新的 Flag&#xff1f;新一年…

点击通知栏后打开Activity,并传参

为什么80%的码农都做不了架构师&#xff1f;>>> Reciver中intent new Intent(context, MessageDetailsaActivity_.class);intent.putExtra("freshMessageId", String.valueOf(push.getObid()));intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Inten…

WMI技术介绍和应用——执行方法

在之前的博文中&#xff0c;我们主要介绍了如何使用WMI查询信息和接收事件。本文将介绍WMI的另一种用法——执行方法。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 这块的内容在msdn中有详细的介绍&#xff0c;如果想看原版的可以参阅《Example: Calling a…

动态指定超链接参数的几种方法(Passing a JavaScript variable into href of )

情景&#xff1a;有些环境下我们需要根据页面中JavaScript变量的值来确定某个&#xff08;某些&#xff09;超链接的参数&#xff0c;如“http://www.bla.com/test.aspx?var1”中&#xff0c;究竟var1等于多少&#xff0c;要根据JavaScript变量来判定 方法一&#xff1a;很简单…

知乎「致知计划之科学季」颁奖,创作者分享80万元奖金

1月11日&#xff0c;「致知计划之科学季」颁奖典礼在北京798艺术中心举行&#xff0c;近500名创作者到场参加。 为了激励创作者&#xff0c;推动优质内容持续产出&#xff0c;知乎的「致知计划」从四个方面对创作者进行了扶持。一是流量扶持&#xff0c;让专业、优质的内容获得…

Inno Setup制作应用程序安装包

我最近写了一个MFC应用程序&#xff0c;想发给其他的小伙伴玩一玩&#xff0c;直接发了个exe文件过去&#xff0c;结果发现小伙伴那边打不开。原来这个exe文件虽然是MFC静态编译的&#xff0c;但是还依赖了其他几个.dll文件&#xff0c;需要把这几个dll文件和exe文件放在同一个…

WMI技术介绍和应用——事件通知

在《WMI技术介绍和应用——WMI概述》中&#xff0c;我们使用了下图介绍WMI构架&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 我们之前介绍的使用WMI查询系统、硬件等信息的功能&#xff0c;是通过查询WMI静态数据的空间实现的。这个功能的核心是在上图中2&a…

OpenWebSpider 安装使用

OpenWebSpider 是一个很好用的网络爬虫,也可以叫做“网络蜘蛛”. 安装&#xff1a; 1.使用Vs.net 2003 编译&#xff0c; 记得要copy libmysql.dll到工程里 2.配置文件openwebspider.conf 将已经启动了的 mysql数据库用户名&#xff0c;密码填写好 3.数据库建立 执行 sql_struc…

杂谈 | 当前知识蒸馏与迁移学习有哪些可用的开源工具?

所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项&#xff0c;还有更多福利赠送作者&编辑 | 言有三来源 | 有三AI&#xff08;ID:yanyousan_ai&#xff09;【导读】知识蒸馏与迁移学习不仅仅属于模型优化的重要技术之一&#xff0c;也是提升模型跨领域泛化能力的重要技…