http://blog.csdn.net/earbao/article/details/19615147
http://blog.sina.com.cn/s/blog_6e2d53050101k230.html
之前我们已经详细介绍了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;
-
-
- timeout.tv_sec = 30;
- 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;
- }
- }
-
- 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
以下是一个例子:
- #include <stdio.h>
- #include <curl/curl.h>
- #include <string.h>
- #include <ctype.h>
- #include <iconv.h>
- #define TMP_FILE "tmp.html"
- #define HTML_BUFFER_SIZE 1024*800
-
- void split(char **arr, char *str, const char *del) {
- char *s = strtok(str, del);
- while (s != NULL) {
- *arr++ = s;
- s = strtok(NULL, del);
- }
- }
-
-
- void replaceFirst(char *str1, char *str2, char *str3) {
- char str4[strlen(str1) + 1];
- char *p;
- strcpy(str4, str1);
- if ((p = strstr(str1, str2)) != NULL) {
- while (str1 != p && str1 != NULL) {
- str1++;
- }
- str1[0] = '/0';
- strcat(str1, str3);
- strcat(str1, strstr(str4, str2) + strlen(str2));
- }
- }
-
-
- void replace(char *str1, char *str2, char *str3) {
- while (strstr(str1, str2) != NULL) {
- replaceFirst(str1, str2, str3);
- }
- }
-
-
- void substring(char *dest, char *src, int start, int end) {
- char *p = src;
- int i = start;
- if (start > strlen(src))return;
- if (end > strlen(src))
- end = strlen(src);
- while (i < end) {
- dest[i - start] = src[i];
- i++;
- }
- dest[i - start] = '/0';
- return;
- }
-
-
- char charAt(char *src, int index) {
- char *p = src;
- int i = 0;
- if (index < 0 || index > strlen(src))
- return 0;
- while (i < index)i++;
- return p[i];
- }
-
-
- int indexOf(char *str1, char *str2) {
- char *p = str1;
- int i = 0;
- p = strstr(str1, str2);
- if (p == NULL)
- return -1;
- else {
- while (str1 != p) {
- str1++;
- i++;
- }
- }
- return i;
- }
-
-
- int lastIndexOf(char *str1, char *str2) {
- char *p = str1;
- int i = 0, len = strlen(str2);
- p = strstr(str1, str2);
- if (p == NULL)return -1;
- while (p != NULL) {
- for (; str1 != p; str1++)i++;
- p = p + len;
- p = strstr(p, str2);
- }
- return i;
- }
-
-
- void ltrim(char *str) {
- int i = 0, j, len = strlen(str);
- while (str[i] != '/0') {
- if (str[i] != 32 && str[i] != 9)break;
- i++;
- }
- if (i != 0)
- for (j = 0; j <= len - i; j++) {
- str[j] = str[j + i];
- }
- }
-
-
- void rtrim(char *str) {
- char *p = str;
- int i = strlen(str) - 1;
- while (i >= 0) {
- if (p[i] != 32 && p[i] != 9)break;
- i--;
- }
- str[++i] = '/0';
- }
-
-
- void trim(char *str) {
- ltrim(str);
- rtrim(str);
- }
-
-
-
-
- static size_t write_callback(void *ptr, size_t size, size_t nmemb, void *stream) {
- int len = size * nmemb;
- int written = len;
- FILE *fp = NULL;
- if (access((char*) stream, 0) == -1) {
- fp = fopen((char*) stream, "wb");
- } else {
- fp = fopen((char*) stream, "ab");
- }
- if (fp) {
- fwrite(ptr, size, nmemb, fp);
- }
-
- fclose(fp);
- return written;
- }
-
-
- void test_post(char* url,char* data) {
- CURL *curl;
- CURLcode res;
- curl = curl_easy_init();
- if (curl) {
-
- curl_easy_setopt(curl, CURLOPT_URL, url);
- curl_easy_setopt(curl, CURLOPT_POST, 1L);
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
- res = curl_easy_perform(curl);
- curl_easy_cleanup(curl);
- }
- }
-
- int file_exists(char *filename) {
- return (access(filename, 0) == 0);
- }
- int GetCharset(char *src_html,char *charCode) {
- char tmp_html[HTML_BUFFER_SIZE]={0};
- int pos = indexOf(src_html, "text/html; charset=");
- if (pos > 0) {
- strncpy(tmp_html, src_html + pos + strlen("text/html; charset="), strlen(src_html) - pos);
- pos = indexOf(tmp_html, "\"");
- if (pos > 0) {
- strncpy(charCode, tmp_html, pos);
- }
- }
- return 0;
-
- }
-
- void test_get(char* url) {
- CURL *curl;
- CURLcode res;
- curl = curl_easy_init();
- if (curl) {
- if (file_exists(TMP_FILE))
- remove(TMP_FILE);
- curl_easy_setopt(curl, CURLOPT_URL, url);
-
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
-
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, TMP_FILE);
- res = curl_easy_perform(curl);
- char tocode[64] = "UTF-8";
- if (CURLE_OK == res) {
- char *ct;
- res = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
- if ((CURLE_OK == res) && ct)
- printf("We received Content-Type: %s\n", ct);
-
-
- int index = indexOf(ct, "=");
- char* arr[3];
- if (index > 0) {
- split(arr, ct, "=");
-
- strcpy(tocode, arr[1]);
- }
- FILE *fp = NULL;
- fp = fopen(TMP_FILE, "r");
- char src_html[HTML_BUFFER_SIZE]={0};
- char output_html[HTML_BUFFER_SIZE]={0};
- char tmp_html[HTML_BUFFER_SIZE]={0};
- if (fp) {
- fread(src_html, HTML_BUFFER_SIZE, 1, fp);
- strcpy(tmp_html,src_html);
- if(index <0) {
- GetCharset(tmp_html,tocode);
- printf("%s\n",tocode);
- }
- int iRet;
-
- iconv_t hIconv = iconv_open(tocode,"iso-8859-1");
- if (-1 == (int) hIconv) {
- return -1;
- }
- printf("%s\n",src_html);
-
- iRet = iconv(hIconv, (char**) (&src_html), strlen(src_html), (char**) (&output_html), strlen(src_html));
- printf("%s\n", output_html);
- printf("ok");
- if(strcmp(output_html,"")==0)
- {
- printf("%s\n",src_html);
- }
-
- iconv_close(hIconv);
-
- }
-
- }
- curl_easy_cleanup(curl);
- }
- }
-
- int main(int argc, char* argv) {
-
-
- test_post("http://192.168.1.6:8080/TestServer/index.html","wd=hava&hehe=123456");
-
- printf("\nok");
- return 0;
- }