之前我们已经详细介绍了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

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

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

    在<使用WinHttp接口实现HTTP协议Get.Post和文件上传功能>一文中,我已经比较详细地讲解了如何使用WinHttp接口实现各种协议.在最近的代码梳理中,我觉得Post和文件上传 ...

  2. Spring MVC环境中的文件上传功能实现

    在实际开发过程中,尤其是web项目开发,文件上传和下载的需求的功能非常场景,比如说用户头像.商品图片.邮件附件等等.其实文件上传下载的本质都是通过流的形式进行读写操作,而在开发中不同的框架都会对文件上 ...

  3. php用ajaxs上传图片_php+ajax实现图片文件上传功能实例

    目前常用的异步文件上传功能有几种,比较多见的如使用iframe框架形式,ajax功能效果,以及flash+php功能,下面介绍ajax与iframe实现异步文件上传的功能的例子. 方法一,利用jque ...

  4. Java文件上传功能代码 —— 普遍适用

    版权声明:本文为博主原创文章,如需转载,请标明出处. https://blog.csdn.net/alan_liuyue/article/details/72782207 一. 前言   通过之前的博 ...

  5. query AjaxUpload实现多文件上传功能代码实例教程

    在PHP网站开发中,文件上传功能时常用到,之前我已介绍过如何利用PHP实现文件上传功能.随着WEB技术的发展,用户体验成为衡量网站成功与否的关键,今天和大家分享如何在PHP中利用Jquery实现Aja ...

  6. 学习ASP.NET Core Razor 编程系列十三——文件上传功能(一)

    原文:学习ASP.NET Core Razor 编程系列十三--文件上传功能(一) 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习A ...

  7. springboot util 测试类怎么写_SpringBoot入门建站全系列(九)文件上传功能与下载方式...

    SpringBoot入门建站全系列(九)文件上传功能与下载方式 Spring对文件上传做了简单的封装,就是用MultipartFile这个对象去接收文件,当然有很多种写法,下面会一一介绍. 文件的下载 ...

  8. 简单的文件上传功能实现(java)

    现在很多做后台系统的项目大部分都会用到文件上传这个功能,网上有很多例子,这边是自己用SSM框架技术做的一个简单的文件上传功能. 在写文件上传功能前首先要导入相应的jar包: commons-fileu ...

  9. 使用php框架文件上传,Laravel框架文件上传功能实现方法示例

    本文实例讲述了Laravel框架文件上传功能实现方法.分享给大家供大家参考,具体如下: 以Laravel 5.2.45 框架为主,进行文件上传功能的实现如下: 实现步骤: (1). 配置文件修改 打开 ...

最新文章

  1. struct和union的大小问题
  2. Mobile OpenCart 自适应主题模板 ABC-0074
  3. 深度神经网络的反向传播算法数学推导
  4. ibm服务器hyper-v搭建的虚拟机操作系统数据如何导出到本机,将虚拟机从Hyper-V转移到KVM|导出完整vhdx磁盘转换qcow2...
  5. Fedora 11 Beta 跳票了
  6. python创建函数如何调用字典对象_我不知道如何用Python创建一个调用我函数的字典...
  7. Linux9.0下构建FTP服务器
  8. 聊一聊分布式对象存储解决方案
  9. iPhone 配置使用工具
  10. JAVA实现List集合去重
  11. 计算机笔记--【Java设计模式】
  12. 计算机动画相关论文,计算机动画论文.doc
  13. 贵如油的春雨都是润物细无声的么?——记2021年首场大范围雷雨强对流天气
  14. 中小网吧网络安全解决方案(转)
  15. I Want To Spend My Lifetime Loving You
  16. Python 之父出生 | 历史上的今天
  17. 老生常谈之防止刷新重复提交表单。
  18. 盘古开源:Web3.0浪潮来袭,离不开分布式存储赋能
  19. Android图形系统之HWComposer
  20. android ListView详解

热门文章

  1. C++ 函数参数传递:传值,传指针,传引用
  2. 奇葩错误:灰度图也想转彩图???(凭空增加数据???)
  3. 【深度学习理论】(3) 激活函数
  4. matlab中调用java代码_Matlab中调用第三方Java代码
  5. jvm 堆外内存_NIO效率高的原理之零拷贝与直接内存映射
  6. configure_file路径疑惑
  7. Udacity机器人软件工程师课程笔记(十四)-运动学-正向运动学和反向运动学(其一)
  8. 【力扣网练习题】整数反转
  9. Ubuntu 14.04 64bit安装IPython
  10. Unity创建2D动作RPG游戏 Create Action 2D RPG Game in Unity