osx下libcurl的使用

时间:2022-11-26 14:35:55

osx下已经预安装了curl,关于curl的使用方法可自行百度。本文主要说的是安装及使用libcurl库进行网页捕获程序的编写。


一、安装

1. 到官网(http://curl.haxx.se/download.html)下载最新版本的源码

2. 解压,在终端进入文件夹

3. 配置选项,编译安装

# ./configure --prefix=/usr/local/curl

# make

# sudo make install

执行完上面的指令后,在目录/usr/local/curl下会bin include lib share这几个目录。包含了编译生产的库、头文件等。

二、使用libcurl库

使用流程:

· 调用curl_global_init()初始化libcurl

· 调用 curl_easy_init()函数得到 easy interface型指针

· 调用curl_easy_setopt设置传输选项

· 根据curl_easy_setopt设置的传输选项,实现回调函数以完成用户特定任务

· 调用curl_easy_perform()函数完成传输任务

· 调用curl_easy_cleanup()释放内存

在整过过程中设置curl_easy_setopt()参数是最关键的,几乎所有的libcurl程序都要使用它。


1. 初始化  curl_global_init(long flag);

使用libcurl之前应该对其全局进行初始化。该函数接受一个参数,以确定如何初始化。

常用参数是:CURL_GLOBAL_ALL。会使libcurl初始化所有的子模块和一些默认的选项

可选参数是:CURL_GLOBAL_WIN32。只应用于Windows平台。它告诉libcurl初始化winsock库。如果winsock库没有正确地初始化,应用程序就不能使用socket。

                      CURL_GLOBAL_SSL。如果libcurl在编译时被设定支持SSL,那么该参数用于初始化相应的SSL库。

 CURL_GLOBAL_NOTHING         //没有额外的初始化。

libcurl有默认的保护机制,如果在调用curl_easy_perform时它检测到还没有通过curl_global_init进行初始 化,libcurl会根据当前的运行时环境,自动调用全局初始化函数。但必须清楚的是,让系统自已初始化不是一个好的选择。

当应用程序不再使用libcurl的时候,应该调用curl_global_cleanup来释放相关的资源。

***在程序中,应当避免多次调用curl_global_init和curl_global_cleanup。它们只能被调用一次。


2. 初始化一个curl的指针:curl_easy_init()

在条用结束时要用curl_easy_cleanup函数清理。

一般curl_easy_init()意味着一个会话的开始,当调用该函数时会有一个返回值,如果调用fopen()时一样。该返回值一般都用在easy系列的函数中。


3. 设置初始化后的curl句柄属性和操作:curl_easy_setopt(easy_handle, 属性类型,属性值)

在easy handle中设置了相应的属性和操作后,它们将一直作用该easy handle。也就是说,重复使用easy hanle向远程主机发出请求,先前设置的属性仍然生效。

①. curl_easy_setopt(easy_handle, CURLOPT_URL, “http://www.baidu.com“);

为句柄easy_handle设置URL

②. curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, write_data);

为句柄easy_handle设置回调函数,即当有数据来的时候,执行回调函数。

回调函数的原型是:

size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp);

@param [in] buffer 当有数据到达的时候把数据缓存在buffer里

@param [in] size 每一个数据块的大小

@param [in] nmemb 数据块的数量

@param [in/out] userp 用户的指针,该指针可以在CURLOPT_WRITEDATA中指定

③. curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, &internal_struct);

指定用户的打开文件,使之可以输出到文件中

④. curl_easy_perform(easy_handle);

执行简单句柄

下面是一个简单的示例程序:

#include <stdio.h>
#include <curl/curl.h>
#include <cstring>
#include <iostream>
using namespace std;

/**
* @brief libcurl接收到数据时的回调函数
* @param [in] buffer 接收到的数据所在的缓冲区
* @param [in] size 数据长度
* @param [in] nmemb 数据片数量
* @param [in/out] 用户自定义指针
* @return 获取的数据长度
*/

size_t process_data(void *buffer, size_t size, size_t nmemb, void *user_p)
{
FILE *fp = (FILE *)user_p;
size_t return_size = fwrite(buffer, size, nmemb, fp);
cout << (char *)buffer << endl;
return return_size;
}

int main()
{
CURL *easy_handle = curl_easy_init();
char url[] = "http://www.baidu.com";
if (NULL == easy_handle)
{
cerr << "get a easy handle failed." << endl;
return -1;
}
FILE *fp = fopen("data.html", "ab+");
curl_easy_setopt(easy_handle, CURLOPT_URL, url);
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, &process_data);
curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, fp);

curl_easy_perform(easy_handle);
fclose(fp);
curl_easy_cleanup(easy_handle);
return 0;
}

编译的时候记得带上 -lcurl

还有一些其他属性可以通过curl_easy_setopt设置

CURLOPT_VERBOSE属性设置为1,libcurl会输出通信过程中的一些细节。如果使用的是http协议,请求头/响应头也会被输出。

CURLOPT_HEADER设为1,这些头信息将出现在消息的内容中。


3. 关闭创建的easy_handle:curl_easy_cleanup(easy_handle)


4. 上传数据

libcurl提供协议无关的方式进行数据传输。所以上传一个文件到FTP服务器,跟向HTTP服务器提交一个PUT请求的操作方式是类似的:

①. 创建easy handle或者重用先前创建的easy handle。

②. 设置CURLOPT_URL属性。

③. 编写回调函数。

在执行上传的时候,libcurl通过回调函数读取要上传的数据。(如果要从远程服务器下载数据,可以通过回调来保存接收到的数据。)回调函数的原型如下:

size_t function(char *bufptr, size_t size, size_t nitems, void *userp);

bufptr指针表示缓冲区,用于保存要上传的数据,size表示每一个数据块的大小,nitems表示数据块的数量,因此size * nitems是缓冲区数据的长度,userp是一个用户自定义指针,libcurl不对该指针作任何操作,它只是简单的传递该指针。可以使用该指针在应用程序与libcurl之间传递信息。

④. 注册回调函数,设置自定义指针。语法如下:

curl_easy_setopt(easy_handle, CURLOPT_READFUNCTION, read_function);
curl_easy_setopt(easy_handle, CURLOPT_READDATA, &filedata);

⑤. 告诉libcurl,执行的是上传操作。

curl_easy_setopt(easy_handle, CURLOPT_UPLOAD, 1L);

有些协议在没有预先知道上传文件大小的情况下,可能无法正确判断上传是否结束,所以最好预先使CURLOPT_INFILESIZE_LARGE属性:告诉它要上传文件的大小:

curl_easy_setopt(easy_handle, CURLOPT_INFILESIZE_LARGE, file_size);
/* in this example, file_size must be an curl_off_t variable */ 

⑥. 调用curl_easy_perform。

接下来,libcurl将会完成剩下的所有工作。在上传文件过程中,libcurl会不断调用先前设置的回调函数,用于将要上传的数据读入到缓冲区,并执行上传。


5. 使用libcurl以post方式向HTTP服务器提交数据

在网站上找到form标签中的name属性

然后把值上传上去

#include <stdio.h>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <curl/curl.h>
using namespace std;

int main()
{
char url[] = "http://paste.ubuntu.com";
CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
CURL *easy_handle = curl_easy_init();
curl_easy_setopt(easy_handle, CURLOPT_URL, url);
curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, "poster=123&syntax=text&content=123");
//curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, "username=zz&password=zz");
code = curl_easy_perform(easy_handle);
curl_easy_cleanup(easy_handle);
curl_global_cleanup();
return 0;
}

*在C++中使用libcurl时,回调函数不能是类的非静态成员函数。例如:

class AClass { static size_t write_data(void *ptr, size_t size, size_t nmemb, void *ourpointer) { /* do what you want with the data */ } }


6. 服务器与客户端之间的cookie认证

在应用场景中,你可能需要保存服务器发送给你的cookie,并在接下来的请求中,把这些cookie一并发往服务器。所以,可以把上次从服务器收到的所有响应头信息保存到文本文件中,当下次需要向服务器发送请求时,通过CURLOPT_COOKIEFILE属性告诉libcurl从该文件中读取 cookie信息。

设置CURLOPT_COOKIEFILE属性意味着激活libcurl的cookie parser。在cookie parser被激活之前,libcurl忽略所有之前接收到的cookie信息。cookie parser被激活之后,cookie信息将被保存内存中,在接下来的请求中,libcurl会自动将这些cookie信息添加到消息头里,你的应用程序不需要做任何事件。大多数情况下,这已经足够了。需要注意的是,通过CURLOPT_COOKIEFILE属性来激活cookie parser,给CURLOPT_COOKIEFILE属性设置的一个保存cookie信息的文本文件路径,可能并不需要在磁盘上物理存在

如果你需要使用NetScape或者FireFox浏览器的cookie文件,你只要用这些浏览器的cookie文件的路径来初始化 CURLOPT_COOKIEFILE属性,libcurl会自动分析cookie文件,并在接下来的请求过程中使用这些cookie信息。

libcurl甚至能够把接收到的cookie信息保存成能被Netscape/Mozilla的浏览器所识别的cookie文件。通过把这些称为 cookie-jar。通过设置CURLOPT_COOKIEJAR选项,在调用curl_easy_cleanup释放easy handle的时候,所有的这些cookie信息都会保存到cookie-jar文件中。这就使得cookie信息能在不同的easy handle甚至在浏览器之间实现共享。

*cookie设置的文本文件路径地址,在登陆完之后无法查看到cookie文件,由此可知cookie是存放在内存中了。

#include <stdio.h>
#include <cstring>
#include <algorithm>
#include <curl/curl.h>
#include <iostream>
using namespace std;

CURL *easy_handle;
CURLcode code;
FILE *curl_file;

void prepareCurl()
{
code = curl_global_init(CURL_GLOBAL_ALL);
if (code != CURLE_OK)
{
cerr << "init libcurl failed." << endl;
}

easy_handle = curl_easy_init();
if (NULL == easy_handle)
{
cerr << "get a easy handle failed." << endl;
curl_global_cleanup();
}
// curl_easy_setopt(easy_handle, CURLOPT_TIMEOUT, 90);
// curl_easy_setopt(easy_handle, CURLOPT_CONNECTTIMEOUT, 90);
curl_easy_setopt(easy_handle, CURLOPT_COOKIEFILE, "cookies/");
curl_easy_setopt(easy_handle, CURLOPT_COOKIEJAR, "cookies/");

curl_file = fopen("tmpfiles/data.html", "w");
curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, curl_file);
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, NULL);
}

void performCurl()
{
fclose(curl_file);
curl_easy_cleanup(easy_handle);
curl_global_cleanup();
}


int main()
{
string lgin_url = "http://acm.hrbust.edu.cn/index.php?m=User&a=login";
string sub_url = "http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=postCode";
prepareCurl();
curl_easy_setopt(easy_handle, CURLOPT_URL, lgin_url.c_str());
string post = (string)"m=User&a=login&user_name=username&password=password&ajax=1";
curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, post.c_str());
code = curl_easy_perform(easy_handle);
if (code != CURLE_OK) cout << "login failed." << endl;
string submit = (string)"jumpUrl=&language=2&problem_id=1000&source_code=#include <iostream> using name space std; int main() { int a, b; while (cin >> a >> b) {cout << a + b << en dl;}return 0;";
curl_easy_setopt(easy_handle, CURLOPT_URL, sub_url.c_str());
curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, submit.c_str());

code = curl_easy_perform(easy_handle);
if (code != CURLE_OK) cout << "submit failed." << endl;
performCurl();
return 0;
}

该样例是在学校oj中做的实验,完成了登录,提交代码这两项动作。

**注意:fopen("tmpfiles/data.html", "w");   的路径必须是已经存在的,否则编译时会出现段错误。