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

来源:互联网 发布:金融网络诈骗类型 编辑:程序博客网 时间:2024/05/16 05:28

        之前我们已经详细介绍了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;                }            } // while            if ( 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

2 0
原创粉丝点击