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

来源:互联网 时间:1970-01-01


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

[cpp] view plaincopy
  1. CURLMcode curl_multi_add_handle(CURLM *multi_handle, CURL *easy_handle);  

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

初始化

初始化easy interface

[cpp] view plaincopy
  1. bool CHttpRequestByCurl::Prepare() {  
  2.     bool bSuc = false;  
  3.     do {  
  4.         if (!m_pCurlEasy) {  
  5.             m_pCurlEasy = curl_easy_init();  
  6.         }  
  7.         if (!m_pCurlEasy) {  
  8.             break;  
  9.         }  

初始化multi interface

[cpp] view plaincopy
  1. if (!m_pCurlMulti){  
  2.     m_pCurlMulti = curl_multi_init();  
  3. }  
  4. if (!m_pCurlMulti) {  
  5.     break;  
  6. }  

设置

设置过程回调

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

[cpp] view plaincopy
  1. CURLcode easycode;  
  2. easycode = curl_easy_setopt( m_pCurlEasy, CURLOPT_NOPROGRESS, 0 );  
  3. CHECKCURLEASY_EROORBREAK(easycode);  
  4. easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_PROGRESSFUNCTION, progresscallback);  
  5. CHECKCURLEASY_EROORBREAK(easycode);  
  6. easycode = curl_easy_setopt( m_pCurlEasy, CURLOPT_PROGRESSDATA, this );  
  7. CHECKCURLEASY_EROORBREAK(easycode);  

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

[cpp] view plaincopy
  1. int CHttpRequestByCurl::progresscallback( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow ) {  
  2.     if (clientp) {  
  3.         CHttpRequestByCurl* pThis = (CHttpRequestByCurl*)clientp;  
  4.         return pThis->ProcessCallback(dltotal, dlnow);  
  5.     }  
  6.     else {  
  7.         return -1;  
  8.     }  
  9. }  
  10.   
  11.    int CHttpRequestByCurl::ProcessCallback( double dltotal, double dlnow ) {  
  12.        if ( m_CallBack ) {  
  13.            const DWORD dwMaxEslapeTime = 500;  
  14.            std::ostringstream os;  
  15.            os << (unsigned long)dlnow;  
  16.            std::string strSize = os.str();  
  17.   
  18.            std::ostringstream ostotal;  
  19.            ostotal << (unsigned long)dltotal;  
  20.            std::string strContentSize = ostotal.str();  
  21.            DWORD dwTickCount = GetTickCount();  
  22.            if ( ( 0 != ((unsigned long)dltotal)) && ( strSize == strContentSize || dwTickCount - m_dwLastCallBackTime > dwMaxEslapeTime ) ) {  
  23.                m_dwLastCallBackTime = dwTickCount;  
  24.                m_CallBack( strContentSize, strSize );  
  25.            }  
  26.        }  
  27.        return 0;  
  28.    }  

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

设置写结果回调

[cpp] view plaincopy
  1. easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_WRITEFUNCTION, writefilecallback);  
  2. CHECKCURLEASY_EROORBREAK(easycode);  
  3. easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_WRITEDATA, this);  
  4. CHECKCURLEASY_EROORBREAK(easycode);  
[cpp] view plaincopy
  1. size_t CHttpRequestByCurl::writefilecallback( void *buffer, size_t size, size_t nmemb, void *stream ) {  
  2.     if (stream) {  
  3.          CHttpRequestByCurl* pThis = (CHttpRequestByCurl*)stream;  
  4.          return pThis->WriteFileCallBack(buffer, size, nmemb);  
  5.     }  
  6.     else {  
  7.         return size * nmemb;  
  8.     }  
  9. }  
  10.   
  11.    size_t CHttpRequestByCurl::WriteFileCallBack( void *buffer, size_t size, size_t nmemb ) {  
  12.        if (!m_pCurlEasy) {  
  13.            return 0;  
  14.        }  
  15.   
  16.        int nResponse = 0;  
  17.        CURLcode easycode = curl_easy_getinfo(m_pCurlEasy, CURLINFO_RESPONSE_CODE, &nResponse);  
  18.        if ( CURLE_OK != easycode || nResponse >= 400 ) {  
  19.            return 0;  
  20.        }  
  21.   
  22.        return Write(buffer, size, nmemb);  
  23.    }  
        在WriteFileCallBack函数中,我们使用curl_easy_getinfo判断了easy interface的返回值,这是为了解决接收返回结果时服务器中断的问题。

设置读回调

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

[cpp] view plaincopy
  1. easycode = curl_easy_setopt( m_pCurlEasy,  CURLOPT_READFUNCTION,  read_callback);  
  2. CHECKCURLEASY_EROORBREAK(easycode);  

        我们看下回调就明白了

[cpp] view plaincopy
  1. size_t CHttpRequestByCurl::read_callback( void *ptr, size_t size, size_t nmemb, void *stream ) {  
  2.    return ((ToolsInterface::LPIMemFileOperation)(stream))->MFRead(ptr, size, nmemb);  
  3. }  
        这次用户自定义指针指向了一个IMemFileOperation对象指针,它是在之后的其他步奏里传递过来的。这儿有个非常有意思的地方——即MFRead的返回值和libcurl要求的read_callback返回值是一致的——并不是说类型一致——而是返回值的定义一致。这就是统一成标准接口的好处。

设置URL

[cpp] view plaincopy
  1. easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_URL, m_strUrl.c_str());  
  2. CHECKCURLEASY_EROORBREAK(easycode);  

设置超时时间

[cpp] view plaincopy
  1. easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_TIMEOUT_MS, m_nTimeout);  
  2. CHECKCURLEASY_EROORBREAK(easycode);  

设置Http头

[cpp] view plaincopy
  1. for ( ToolsInterface::ListStrCIter it = m_listHeaders.begin(); it != m_listHeaders.end(); it++ ) {  
  2.     m_pHeaderlist = curl_slist_append(m_pHeaderlist, it->c_str());  
  3. }  
  4. if (m_pHeaderlist) {  
  5.     curl_easy_setopt(m_pCurlEasy, CURLOPT_HTTPHEADER, m_pHeaderlist);  
  6. }  

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

[cpp] view plaincopy
  1. if (m_pHeaderlist) {  
  2.     curl_slist_free_all (m_pHeaderlist);  
  3.     m_pHeaderlist = NULL;  
  4. }  

设置Agent

[cpp] view plaincopy
  1. if (!m_strAgent.empty()) {  
  2.     easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_USERAGENT, m_strAgent.c_str());  
  3.     CHECKCURLEASY_EROORBREAK(easycode);  
  4. }  

设置Post参数

[cpp] view plaincopy
  1. if ( ePost == GetType() ) {  
  2.     easycode = ModifyEasyCurl(m_pCurlEasy, m_Params);  
  3.     CHECKCURLEASY_EROORBREAK(easycode);  
  4. }  

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

将easy interface加入到multi interface

[cpp] view plaincopy
  1.       CURLMcode multicode = curl_multi_add_handle( m_pCurlMulti, m_pCurlEasy );  
  2.       CHECKCURLMULTI_EROORBREAK(multicode);  
  3.   
  4.       bSuc = true;  
  5. } while (0);  
  6. return bSuc;  

运行

[cpp] view plaincopy
  1. EDownloadRet CHttpRequestByCurl::Curl_Multi_Select(CURLM* pMultiCurl)  
  2. {  
  3.     EDownloadRet ERet = EContinue;  
  4.   
  5.     do {  
  6.         struct timeval timeout;  
  7.         fd_set fdread;  
  8.         fd_set fdwrite;  
  9.         fd_set fdexcep;  
  10.   
  11.         CURLMcode multicode;  
  12.         long curl_timeo = -1;  
  13.   
  14.         /* set a suitable timeout to fail on */   
  15.         timeout.tv_sec = 30; /* 30 seconds */   
  16.         timeout.tv_usec = 0;  
  17.         multicode = curl_multi_timeout(pMultiCurl, &curl_timeo);  
  18.         if ( CURLM_OK == multicode && curl_timeo >= 0 ) {  
  19.             timeout.tv_sec = curl_timeo / 1000;  
  20.             if (timeout.tv_sec > 1) {  
  21.                 timeout.tv_sec = 0;  
  22.             }   
  23.             else {  
  24.                 timeout.tv_usec = (curl_timeo % 1000) * 1000;  
  25.             }  
  26.         }  
  27.   
  28.         int nMaxFd = -1;  
  29.   
  30.         while ( -1 == nMaxFd ) {  
  31.   
  32.             FD_ZERO(&fdread);  
  33.             FD_ZERO(&fdwrite);  
  34.             FD_ZERO(&fdexcep);  
  35.   
  36.             multicode = curl_multi_fdset( m_pCurlMulti, &fdread, &fdwrite, &fdexcep, &nMaxFd );  
  37.             CHECKCURLMULTI_EROORBREAK(multicode);  
  38.             if ( -1 != nMaxFd ) {  
  39.                 break;  
  40.             }  
  41.             else {  
  42.                 if (WAIT_TIMEOUT != WaitForSingleObject(m_hStop, 100)) {  
  43.                     ERet = EInterrupt;  
  44.                     break;  
  45.                 }  
  46.                 int nRunning = 0;  
  47.                 CURLMcode multicode = curl_multi_perform( m_pCurlMulti, &nRunning );  
  48.                 CHECKCURLMULTI_EROORBREAK(multicode);  
  49.             }  
  50.         }  
  51.   
  52.         if ( EContinue == ERet ) {  
  53.             int nSelectRet = select( nMaxFd + 1, &fdread, &fdwrite, &fdexcep, &timeout );  
  54.   
  55.             if ( -1 == nSelectRet ){  
  56.                 ERet = EFailed;  
  57.             }  
  58.         }  
  59.         if ( EInterrupt == ERet ) {  
  60.             break;  
  61.         }  
  62.     } while (0);  
  63.   
  64.     return ERet;  
  65. }  
  66.   
  67. DWORD CHttpRequestByCurl::StartRequest() {  
  68.     Init();  
  69.     EDownloadRet eDownloadRet = ESuc;  
  70.     do {  
  71.         if (!Prepare()) {  
  72.             break;  
  73.         }  
  74.   
  75.         int nRunning = -1;  
  76.         while( CURLM_CALL_MULTI_PERFORM == curl_multi_perform(m_pCurlMulti, &nRunning) ) {  
  77.             if (WAIT_TIMEOUT != WaitForSingleObject(m_hStop, 10)) {  
  78.                 eDownloadRet = EInterrupt;  
  79.                 break;  
  80.             }  
  81.         }  
  82.   
  83.         if ( EInterrupt == eDownloadRet ) {  
  84.             break;  
  85.         }  
  86.   
  87.         while(0 != nRunning) {  
  88.             EDownloadRet nSelectRet = Curl_Multi_Select(m_pCurlMulti);  
  89.             if ( EFailed == nSelectRet || EInterrupt == nSelectRet || ENetError == nSelectRet ) {  
  90.                 eDownloadRet = nSelectRet;  
  91.                 break;  
  92.             }  
  93.             else {  
  94.                 CURLMcode multicode = curl_multi_perform(m_pCurlMulti, &nRunning);  
  95.                 if (CURLM_CALL_MULTI_PERFORM == multicode) {  
  96.                     if (WAIT_TIMEOUT != WaitForSingleObject(m_hStop, 10)) {  
  97.                         eDownloadRet = EInterrupt;  
  98.                         break;  
  99.                     }  
  100.                 }  
  101.                 else if ( CURLM_OK == multicode ) {  
  102.                 }  
  103.                 else {  
  104.                     if (WAIT_TIMEOUT != WaitForSingleObject(m_hStop, 100)) {  
  105.                         eDownloadRet = EInterrupt;  
  106.                     }  
  107.                     break;  
  108.                 }  
  109.             }  
  110.   
  111.             if ( EInterrupt == eDownloadRet ) {  
  112.                 break;  
  113.             }  
  114.         } // while  
  115.   
  116.         if ( EInterrupt == eDownloadRet ) {  
  117.             break;  
  118.         }  
  119.   
  120.         int msgs_left;    
  121.         CURLMsg*  msg;    
  122.         while((msg = curl_multi_info_read(m_pCurlMulti, &msgs_left))) {    
  123.             if (CURLMSG_DONE == msg->msg) {   
  124.                 if ( CURLE_OK != msg->data.result ) {  
  125.                     eDownloadRet = EFailed;  
  126.                 }  
  127.             }  
  128.             else {  
  129.                 eDownloadRet = EFailed;  
  130.             }  
  131.         }  
  132.   
  133.     } while (0);  
  134.   
  135.     Unint();  
  136.   
  137.     m_bSuc = ( ESuc == eDownloadRet ) ? true : false;  
  138.     return eDownloadRet;  
  139. }  

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

实现Post、文件上传功能

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

组装上传文件

[cpp] view plaincopy
  1. CURLcode CPostByCurl::ModifyEasyCurl_File( CURL* pEasyCurl, const FMParam& Param ) {  
  2.   
  3.     Param.value->MFSeek(0L, SEEK_END);  
  4.     long valuesize = Param.value->MFTell();  
  5.     Param.value->MFSeek(0L, SEEK_SET);  
  6.   
  7.     curl_formadd((curl_httppost**)&m_pFormpost,  
  8.         (curl_httppost**)&m_pLastptr,  
  9.         CURLFORM_COPYNAME, Param.strkey.c_str(),  
  10.         CURLFORM_STREAM, Param.value,   
  11.         CURLFORM_CONTENTSLENGTH, valuesize,  
  12.         CURLFORM_FILENAME, Param.fileinfo.szfilename,  
  13.         CURLFORM_CONTENTTYPE, "application/octet-stream",  
  14.         CURLFORM_END);  
  15.   
  16.     return CURLE_OK;  
  17. }  

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

组装上传数据

[cpp] view plaincopy
  1. CURLcode CPostByCurl::ModifyEasyCurl_Mem( CURL* pEasyCurl, const FMParam& Param ) {  
  2.     if (Param.meminfo.bMulti) {  
  3.         Param.value->MFSeek(0L, SEEK_END);  
  4.         long valuesize = Param.value->MFTell();  
  5.         Param.value->MFSeek(0L, SEEK_SET);  
  6.         curl_formadd(&m_pFormpost, &m_pLastptr,   
  7.             CURLFORM_COPYNAME, Param.strkey.c_str(),   
  8.             CURLFORM_STREAM, Param.value,   
  9.             CURLFORM_CONTENTSLENGTH, valuesize,  
  10.             CURLFORM_CONTENTTYPE, "application/octet-stream",  
  11.             CURLFORM_END );  
  12.     }  
  13.     else {  
  14.         if (!m_strCommonPostData.empty()) {  
  15.             m_strCommonPostData += "&";  
  16.         }  
  17.         std::string strpostvalue;  
  18.         while(!Param.value->MFEof()) {  
  19.             char buffer[1024] = {0};  
  20.             size_t size = Param.value->MFRead(buffer, 1, 1024);  
  21.             strpostvalue.append(buffer, size);  
  22.         }  
  23.         m_strCommonPostData += Param.strkey;  
  24.         m_strCommonPostData += "=";  
  25.         m_strCommonPostData += strpostvalue;  
  26.     }  
  27.     return CURLE_OK;  
  28. }  
        对于需要MultiPart格式发送的数据,我们发送的方法和文件发送相似——只是少了CURLFORM_FILENAME设置——因为没有文件名。

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

设置数据待上传        

[cpp] view plaincopy
  1. CURLcode CPostByCurl::ModifyEasyCurl( CURL* pEasyCurl, const FMParams& Params ) {  
  2.         for (FMParamsCIter it = m_PostParam.begin(); it != m_PostParam.end(); it++ ) {  
  3.             if (it->postasfile) {  
  4.                 ModifyEasyCurl_File(pEasyCurl, *it);  
  5.             }  
  6.             else {  
  7.                 ModifyEasyCurl_Mem(pEasyCurl, *it);  
  8.             }  
  9.         }  
  10.   
  11.         if (m_pFormpost){  
  12.             curl_easy_setopt(pEasyCurl, CURLOPT_HTTPPOST, m_pFormpost);  
  13.         }  
  14.   
  15.         if (!m_strCommonPostData.empty()) {  
  16.             curl_easy_setopt(pEasyCurl, CURLOPT_COPYPOSTFIELDS, m_strCommonPostData.c_str());  
  17.         }  
  18.   
  19.     return CURLE_OK;  
  20. }  
        通过设置CURLOPT_HTTPPOST,我们将MultiPart型数据——包括文件上传数据设置好。通过设置CURLOPT_COPYPOSTFIELDS,我们将普通Post型数据设置好。

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

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


上文来自:http://blog.csdn.net/breaksoftware/article/details/45874197



相关阅读:
Top