前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实现HTTP协议Get、Post和文件上传功能——使用libcurl接口实现

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

作者头像
方亮
发布2019-01-16 15:09:15
2.7K0
发布2019-01-16 15:09:15
举报
文章被收录于专栏:方亮方亮

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

代码语言:javascript
复制
CURLMcode curl_multi_add_handle(CURLM *multi_handle, CURL *easy_handle);

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

初始化

初始化easy interface

代码语言:javascript
复制
bool CHttpRequestByCurl::Prepare() {
	bool bSuc = false;
	do {
		if (!m_pCurlEasy) {
			m_pCurlEasy = curl_easy_init();
		}
		if (!m_pCurlEasy) {
			break;
		}

初始化multi interface

代码语言:javascript
复制
            if (!m_pCurlMulti){
                m_pCurlMulti = curl_multi_init();
            }
            if (!m_pCurlMulti) {
                break;
            }

设置

设置过程回调

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

代码语言:javascript
复制
		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传过去。

代码语言:javascript
复制
	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成员函数。之后我们的其他回调函数也是类似的,比如写结果的回调设置

设置写结果回调

代码语言:javascript
复制
		easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_WRITEFUNCTION, writefilecallback);
		CHECKCURLEASY_EROORBREAK(easycode);
		easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_WRITEDATA, this);
		CHECKCURLEASY_EROORBREAK(easycode);
代码语言:javascript
复制
	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指针过去。

代码语言:javascript
复制
            easycode = curl_easy_setopt( m_pCurlEasy,  CURLOPT_READFUNCTION,  read_callback);
            CHECKCURLEASY_EROORBREAK(easycode);

        我们看下回调就明白了

代码语言:javascript
复制
    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

代码语言:javascript
复制
            easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_URL, m_strUrl.c_str());
            CHECKCURLEASY_EROORBREAK(easycode);

设置超时时间

代码语言:javascript
复制
            easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_TIMEOUT_MS, m_nTimeout);
            CHECKCURLEASY_EROORBREAK(easycode);

设置Http头

代码语言:javascript
复制
            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在整个请求完毕后需要释放

代码语言:javascript
复制
		if (m_pHeaderlist) {
			curl_slist_free_all (m_pHeaderlist);
			m_pHeaderlist = NULL;
		}

设置Agent

代码语言:javascript
复制
            if (!m_strAgent.empty()) {
                easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_USERAGENT, m_strAgent.c_str());
                CHECKCURLEASY_EROORBREAK(easycode);
            }

设置Post参数

代码语言:javascript
复制
            if ( ePost == GetType() ) {
                easycode = ModifyEasyCurl(m_pCurlEasy, m_Params);
                CHECKCURLEASY_EROORBREAK(easycode);
            }

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

将easy interface加入到multi interface

代码语言:javascript
复制
            CURLMcode multicode = curl_multi_add_handle( m_pCurlMulti, m_pCurlEasy );
            CHECKCURLMULTI_EROORBREAK(multicode);

            bSuc = true;
      } while (0);
      return bSuc;
}

运行

代码语言:javascript
复制
    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结构体保存参数

组装上传文件

代码语言:javascript
复制
    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为服务器上保存的文件名。

组装上传数据

代码语言:javascript
复制
    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拼接起来。待之后一并发送。

设置数据待上传        

代码语言:javascript
复制
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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2015年06月07日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 初始化
    • 初始化easy interface
      • 初始化multi interface
      • 设置
        • 设置过程回调
          • 设置写结果回调
            • 设置读回调
              • 设置URL
                • 设置超时时间
                  • 设置Http头
                    • 设置Agent
                      • 设置Post参数
                        • 将easy interface加入到multi interface
                        • 运行
                        • 实现Post、文件上传功能
                          • 组装上传文件
                            • 组装上传数据
                              • 设置数据待上传        
                              相关产品与服务
                              大数据处理套件 TBDS
                              腾讯大数据处理套件(Tencent Big Data Suite,TBDS)依托腾讯多年海量数据处理经验,基于云原生技术和泛 Hadoop 生态开源技术对外提供的可靠、安全、易用的大数据处理平台。 TBDS可在公有云、私有云、非云化环境,根据不同数据处理需求组合合适的存算分析组件,包括 Hive、Spark、HBase、Flink、presto、Iceberg、Alluxio 等,以快速构建企业级数据湖、数据仓库。
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档