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通过回调函数读取要上传的数据。(如果要从远程服务器下载数据,可以通过回调来保存接收到的数据。)回调函数的原型如下:
bufptr指针表示缓冲区,用于保存要上传的数据,size表示每一个数据块的大小,nitems表示数据块的数量,因此size * nitems是缓冲区数据的长度,userp是一个用户自定义指针,libcurl不对该指针作任何操作,它只是简单的传递该指针。可以使用该指针在应用程序与libcurl之间传递信息。
④. 注册回调函数,设置自定义指针。语法如下:
⑤. 告诉libcurl,执行的是上传操作。
有些协议在没有预先知道上传文件大小的情况下,可能无法正确判断上传是否结束,所以最好预先使CURLOPT_INFILESIZE_LARGE属性:告诉它要上传文件的大小:
⑥. 调用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时,回调函数不能是类的非静态成员函数。例如:
6. 服务器与客户端之间的cookie认证
在应用场景中,你可能需要保存服务器发送给你的cookie,并在接下来的请求中,把这些cookie一并发往服务器。所以,可以把上次从服务器收到的所有响应头信息保存到文本文件中,当下次需要向服务器发送请求时,通过CURLOPT_COOKIEFILE属性告诉libcurl从该文件中读取 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"); 的路径必须是已经存在的,否则编译时会出现段错误。