C++编程框架实战——基于LibCurl的网络协议编程

一、LibCurl库简介

libCurl是一个功能强大、跨平台的网络协议库,被广泛用于实现各种网络通信模块。

libCurl支持多种主流的网络协议,包括HTTP/HTTPS、FTP/FTPS、SMTP/POP3、SCP/SFTP、RTSP、LDAP、Gopher、Telnet等协议。

开发者可以基于libCurl实现HTTPS证书授权、HTTP POST/PUT请求、FTP文件上传下载、Proxy代理支持、Cookies管理和用户认证等功能。

基于LibCurl库可以实现以下功能:

1.HTTP/HTTPS通信

支持GET/POST/PUT/DELETE等HTTP方法。

支持基于SSL/TLS的HTTPS通信。

2.文件传输协议

支持FTP/SFTP实现的文件上传和下载功能。

基于CURLOPT_RESUME_FROM接口实现的断点续传功能。

3.数据处理和解析

基于curl_mime接口实现的多部分表单数据上传功能。

基于CURLOPT_WRITEFUNCTION回调函数实现的HTTP响应数据处理功能。

libCurl库的核心特性如下:

1.多协议支持:

libcurl支持8种以上主流网络协议,确保开发者能在不同场景下灵活处理数据交互。

通过以下C语言代码可以配置允许的网络协议,让代码的可读性更强:

curl_easy_setopt(handle,
CURLOPT_PROTOCOLS,
CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP);

2.安全通信机制:

支持SSL/TLS、内置HTTPS证书验证机制,保障了数据传输的安全性。

支持验证服务器证书,包括检查证书链和主机域名是否匹配,验证过程可通过CURLOPT_SSL_VERIFYPEER接口和CURLOPT_SSL_VERIFYHOST接口进行设置。

支持使用客户端证书进行双向认证,可通过CURLOPT_SSLCERT接口设置客户端证书,通过CURLOPT_SSLKEY为TLS/SSL客户端证书指定私钥文件,通过CURLOPT_CAINFO指定证书的路径。

3.支持数据传输

支持断点续传,通过设置CURLOPT_RESUME_FROM_LARGE可以指定从文件的某个位置开始传输,用于下载中断后恢复续传。

传输速度可配置,通过CURLOPT_MAX_SEND_SPEED_LARGE和CURLOPT_MAX_RECV_SPEED_LARGE可以限制上传和下载的最快速度。

支持HTTP表单上传、FTP文件上传和下载。

4.回调机制

libcurl通过回调函数将数据处理的控制权交给用户的应用程序。

写回调:用于处理接收到的数据,通过CURLOPT_WRITEFUNCTION设置。在C++中,该回调函数必须是静态成员函数或全局函数,因为非静态成员函数有隐含的this指针。

读回调:用于上传数据,通过CURLOPT_READFUNCTION设置。

其他回调:例如进度条显示回调CURLOPT_PROGRESSFUNCTION、Header头部回调CURLOPT_HEADERFUNCTION等。

5.多线程支持

通过设置CURLOPT_NOSIGNAL为1,可以避免在多线程中使用信号,因为信号处理是进程级别的,在多线程中不安全。

每个curl_easy句柄通常在一个线程内使用,多个线程可以同时使用各自的curl_easy句柄。

6.跨平台兼容

可在Windows、Linux、macOS等系统上编译运行,无需修改核心代码。

二、LibCurl核心接口分类

三、LibCurl开发流程

1.初始化libcurl库

调用curl_global_init()进行全局初始化。注意,此函数不是线程安全的,应仅在主线程中调用一次。虽然libCurl整体是线程安全的,但curl_global_init()必须在主线程中调用以避免竞态条件。

2.创建curl_easy句柄

使用curl_easy_init()获取一个easy interface指针。

3.设置curl选项

通过curl_easy_setopt()配置传输参数,比如URL、回调函数等。

4.实现读/写回调

定义回调函数处理数据传输,如接收数据或错误处理。

5.执行请求任务

调用curl_easy_perform()启动数据传输或网络请求。

6.清理资源

任务完成后,用curl_easy_cleanup()释放内存,进程退出时调用curl_global_cleanup()清理全局资源。

LibCurl实现FTP请求代码样例:

include

int main(void)
{
CURL *curl;
CURLcode res;

curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
/*
* Make the URL end with a trailing slash
*/
curl_easy_setopt(curl, CURLOPT_URL, “ftp://ftp.example.com/”);

res = curl_easy_perform(curl);

/* always cleanup */
curl_easy_cleanup(curl);

if(CURLE_OK != res) {
  /* we failed */
  fprintf(stderr, "curl told us %d\n", res);
}

}

curl_global_cleanup();
return0;
}

四、LibCurl开发环境搭建

从LibCurl官网下载指定版本的源码进行编译和安装。

step.01:下载源码

wget https://curl.se/download/curl-7.74.0.tar.gz

step.02:解压源码

tar -zxvf curl-7.74.0.tar.gz

step.03:构建 & 编译

./configure –prefix=/usr/local
make && make install

安装完成以后,可以执行以下命令查看libcurl版本支持的特性:

五、LibCurl库常用接口

1.Easy Interface(简单接口)

Easy Interface是libcurl中最常用的同步传输接口。每个Easy Handle代表一个独立的传输会话。

CURL *curl_easy_init(void);

初始化一个简单的句柄(easy handle),用于后续的传输设置和执行。

CURLcode curl_easy_setopt(CURL *handle, CURLoption option, …);

设置传输选项(options)。这是配置easy handle行为的主要函数,有超过200个选项可用。

CURLcode curl_easy_perform(CURL *handle);

执行传输。这是一个阻塞调用,直到传输完成或失败。

void curl_easy_cleanup(CURL *handle);

清理并释放一个easy handle。

CURL *curl_easy_duphandle(CURL *handle);

复制一个已有的easy handle(包括所有设置)。

void curl_easy_reset(CURL *handle);

重置一个easy handle的所有选项,使其回到初始状态(但保持连接池)。

CURLcode curl_easy_pause(CURL *handle, int bitmask);

暂停或恢复一个正在进行的传输(通常用于回调函数中)。

CURLcode curl_easy_send(CURL *handle, const void *buffer, size_t buflen, size_t *n);

用于WebSocket或RTSP等,发送原始数据(非阻塞)。

CURLcode curl_easy_recv(CURL *handle, void *buffer, size_t buflen, size_t *n);

用于WebSocket或RTSP等,接收原始数据(非阻塞)。

C++代码样例:

/* 协议控制 */

// 目标URL
curl_easy_setopt(handle, CURLOPT_URL, “https://example.com”);
// 协议限制
curl_easy_setopt(handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
// 端口设置
curl_easy_setopt(handle, CURLOPT_PORT, 443L);

/* 回调函数 */

// 接收回调
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback);
// 头回调
curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, header_callback);

/* 超时控制 */

// 总超时
curl_easy_setopt(handle, CURLOPT_TIMEOUT, 30L);
// 连接超时
curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, 10L);

2.Multi Interface(多接口)

Multi Interface允许同时进行多个传输(非阻塞方式),通常在事件循环中使用。

CURLM *curl_multi_init(void);

初始化一个multi handle。

CURLMcode curl_multi_add_handle(CURLM *multi_handle, CURL *easy_handle);

将一个easy handle添加到multi handle中,使其成为多传输的一部分。

CURLMcode curl_multi_remove_handle(CURLM *multi_handle, CURL *easy_handle);

从multi handle中移除一个easy handle。

CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles);

执行传输(非阻塞),返回当前仍在运行的传输数量。

CURLMcode curl_multi_poll(CURLM *multi_handle, struct curl_waitfd *extra_fds, unsigned int extra_nfds, int timeout_ms, int *numfds);

等待文件描述符上的活动(类似于poll函数)。

CURLMcode curl_multi_wait(CURLM *multi_handle, struct curl_waitfd *extra_fds, unsigned int extra_nfds, int timeout_ms, int *numfds);

等待multi handle上的活动(类似于select函数,但更简单)。

CURLMcode curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queue);

读取已完成传输的信息(例如,哪些传输已完成)。

CURLMcode curl_multi_cleanup(CURLM *multi_handle);

清理multi handle。

CURLMcode curl_multi_setopt(CURLM *multi_handle, CURLMoption option, …);

设置multi handle的选项。

const char *curl_multi_strerror(CURLMcode errornum);

将multi接口的错误代码转换为字符串。

CURLMcode curl_multi_socket_action(CURLM *multi_handle, curl_socket_t sockfd, int ev_bitmask, int *running_handles);

在指定的socket上通知libcurl发生的事件(用于更精细的事件驱动)。

CURLMcode curl_multi_timeout(CURLM *multi_handle, long *timeout);

获取multi handle的超时时间(用于设置事件循环的等待时间)。

C++代码样例:

CURLM *multi = curl_multi_init();
// 添加多个easy handle…

int running;
do {
CURLMcode res = curl_multi_perform(multi, &running);
if (running) {
// 使用epoll集成
curl_multi_poll(multi, NULL, 0, 1000, NULL);
}
} while (running);

// 处理完成的任务
CURLMsg *msg;
while ((msg = curl_multi_info_read(multi, &remaining))) {
if (msg->msg == CURLMSG_DONE) {
// 处理完成传输
}
}

3.MIME Interface(MIME表单接口)

MIME接口用于构建和发送multipart表单数据(如文件上传)。

curl_mime *curl_mime_init(CURL *easy);

初始化一个mime句柄(与特定的easy handle关联)。

curl_mimepart *curl_mime_addpart(curl_mime *mime);

向mime表单中添加一个部分(part)。

CURLcode curl_mime_name(curl_mimepart *part, const char *name);

设置mime部分的名称(表单字段名)。

CURLcode curl_mime_filename(curl_mimepart *part, const char *filename);

设置mime部分的文件名(用于文件上传)。

CURLcode curl_mime_type(curl_mimepart *part, const char *mimetype);

设置mime部分的MIME类型(如”text/plain”)。

CURLcode curl_mime_encoder(curl_mimepart *part, const char *encoding);

设置mime部分的内容编码(如”base64″)。

CURLcode curl_mime_data(curl_mimepart *part, const char *data, size_t datasize);

设置mime部分的数据(内存数据)。

CURLcode curl_mime_filedata(curl_mimepart *part, const char *filename);

设置mime部分的数据来自文件(文件上传)。

CURLcode curl_mime_data_cb(curl_mimepart *part, curl_off_t datasize, curl_read_callback readfunc, curl_seek_callback seekfunc, curl_free_callback freefunc, void *arg);

通过回调函数设置mime部分的数据(允许自定义数据源)。

CURLcode curl_mime_subparts(curl_mimepart *part, curl_mime *subparts);

设置mime部分的子部分(用于嵌套的multipart)。

void curl_mime_free(curl_mime *mime);

释放一个mime句柄及其所有部分。

C++代码样例:

curl_mime *form = curl_mime_init(curl);
curl_mimepart *field = curl_mime_addpart(form);

// 文本字段
curl_mime_name(field, “username”);
curl_mime_data(field, “testuser”, CURL_ZERO_TERMINATED);

// 文件字段
field = curl_mime_addpart(form);
curl_mime_name(field, “avatar”);
curl_mime_filedata(field, “/path/to/avatar.jpg”);
curl_mime_filename(field, “profile.jpg”);

// 设置到easy handle
curl_easy_setopt(curl, CURLOPT_MIMEPOST, form);

4.数据处理回调接口

size_t write_callback(char* ptr, size_t size, size_t nmemb, void* userdata)

数据接收回调。

size_t read_callback(char* buffer, size_t size, size_t nitems, void* instream)

数据上传回调。

5.实用工具接口

const char* curl_easy_strerror(CURLcode errornum)

将错误码转为可读信息(调试必备)。

CURLINFO curl_easy_getinfo(CURL* handle, CURLINFO info, …)

获取传输元数据,比如传输相关的信息(如响应代码、传输速度等)。

struct curl_slist* curl_slist_append(struct curl_slist* list, const char* string)

构建HTTP头部。

6.其他接口

libcurl还提供了一些其他接口,例如:

Share Interface:用于在多个easy handle之间共享数据,如cookies、DNS缓存。

URL Interface:用于解析和操作URL。

Version Information:获取库的版本信息。

代码样例:

// 共享DNS缓存
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);

// 共享SSL会话缓存
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);

// 自定义锁
curl_share_setopt(share, CURLSHOPT_LOCKFUNC, lock_callback);

六、LibCurl库C++编程实例

Demo1:简单网页请求

include

include

int main(void)
{
CURL *curl;

CURLcode result = curl_global_init(CURL_GLOBAL_ALL);
if(result != CURLE_OK)
return (int)result;

curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, “https://example.com”);
/* example.com is redirected, so we tell libcurl to follow redirection */
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

/* Perform the request, result gets the return code */
result = curl_easy_perform(curl);
/* Check for errors */
if(result != CURLE_OK)
  fprintf(stderr, "curl_easy_perform() failed: %s\n",
          curl_easy_strerror(result));

/* always cleanup */
curl_easy_cleanup(curl);

}
curl_global_cleanup();
return0;
}
运行结果:



Example Domain

Example Domain

This domain is for use in documentation examples without needing permission. Avoid use in operations.

Learn more

Demo2:SSL安全认证

应用场景:银行系统对接、医疗数据加密传输

// HTTPS双向证书认证
void SecureRequest() {
CURL* curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, “https://secure-api.com”);
// 客户端证书设置
curl_easy_setopt(curl, CURLOPT_SSLCERT, “/certs/client.pem”);
curl_easy_setopt(curl, CURLOPT_SSLKEY, “/certs/key.pem”);
curl_easy_setopt(curl, CURLOPT_KEYPASSWD, “mypassword”);
// CA证书验证
curl_easy_setopt(curl, CURLOPT_CAINFO, “/certs/cacert.pem”);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);

    CURLcode res = curl_easy_perform(curl);
    // 错误处理...
}

}

Demo3:HTTP/HTTPS客户端通信

应用场景:实现Restful API调用或Web数据抓取

include

include

// 回调函数处理响应数据
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* data) {
data->append((char*)contents, size * nmemb);
return size * nmemb;
}

int main() {
CURL* curl = curl_easy_init();
std::string response;

if(curl) {
    // 设置API端点
    curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/users");
    // 设置HTTP头
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    // 设置JSON请求体
    constchar* json = R"({"name":"John","age":30})";
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
    // 设置响应处理回调
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

    CURLcode res = curl_easy_perform(curl);
    if(res != CURLE_OK) {
        std::cerr << "Request failed: " << curl_easy_strerror(res);
    } else {
        std::cout << "API Response: " << response;
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
}
return0;

}

Demo4:文件传输协议

应用场景:跨服务器文件同步、OTA固件升级、FTP/SFTP服务器文件上传/下载

include

int main() {
CURL* curl;
FILE* fp;
constchar* url = “ftp://example.com/file.txt”;
constchar* local_path = “file.txt”;

// 打开本地文件用于写入
fp = fopen(local_path, "wb");
if (!fp) return1;

curl = curl_easy_init();
if (curl) {
    // 设置FTP地址
    curl_easy_setopt(curl, CURLOPT_URL, url);
    // 设置用户名密码
    curl_easy_setopt(curl, CURLOPT_USERPWD, "username:password");
    // 设置文件写入回调
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);

    // 执行下载
    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK)
        fprintf(stderr, "Download failed: %s\n", curl_easy_strerror(res));

    // 清理
    curl_easy_cleanup(curl);
}
fclose(fp);
return0;

}

Demo5:Form表单数据上传

include

int main() {
CURL* curl;
curl_mime* mime;
curl_mimepart* part;

curl = curl_easy_init();
if (curl) {
    // 创建多部件表单
    mime = curl_mime_init(curl);

    // 添加文本字段
    part = curl_mime_addpart(mime);
    curl_mime_name(part, "text_field");
    curl_mime_data(part, "Hello World", CURL_ZERO_TERMINATED);

    // 添加文件字段
    part = curl_mime_addpart(mime);
    curl_mime_name(part, "file_field");
    curl_mime_filedata(part, "/path/to/file.jpg");

    // 设置表单和URL
    curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
    curl_easy_setopt(curl, CURLOPT_URL, "http://example.com/upload");

    // 执行请求
    curl_easy_perform(curl);

    // 清理
    curl_mime_free(mime);
    curl_easy_cleanup(curl);
}
return0;

}

七、LibCurl库C++代码实战

Demo1:Web网页爬虫

基于LibCurl实现Web网页爬虫,设置Write回调函数来接收Web网页数据。

在多线程场景中,需要额外设置CURLOPT_NOSIGNAL以避免信号问题,但本例为单线程,可省略。

特殊代码:

1.用户代理设置(避免被屏蔽)

curl_easy_setopt(curl, CURLOPT_USERAGENT, “Mozilla/5.0…”);

2.自动跟随重定向
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

3.获取HTTP状态码
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

完整C++代码实现:

include

include

include

include

// 网页响应数据写入回调函数
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* output) {
size_t total_size = size * nmemb;
output->append(static_cast(contents), total_size);
return total_size;
}

// 提取网页标题的正则表达式
std::string ExtractTitle(const std::string& html) {
std::regex title_regex(“(.*?)”, std::regex_constants::icase);
std::smatch match;

if (std::regex_search(html, match, title_regex) && match.size() > 1) {
    return match[1].str();
}
return"Title not found";

}

// 提取所有链接
void ExtractLinks(const std::string& html) {
std::regex link_regex(R”(<a\s+href=“‘[“‘])”, std::regex_constants::icase);
auto links_begin = std::sregex_iterator(html.begin(), html.end(), link_regex);
auto links_end = std::sregex_iterator();

std::cout << "\nFound links:\n";
for (std::sregex_iterator i = links_begin; i != links_end; ++i) {
    std::smatch match = *i;
    if (match.size() > 1) {
        std::cout << "  - " << match[1].str() << "\n";
    }
}

}

// 爬虫函数
void WebCrawler(const std::string& url) {
CURL* curl = curl_easy_init();
std::string response;

if (!curl) {
    std::cerr << "Curl initialization failed\n";
    return;
}

// 设置cURL选项
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; MyCrawler/1.0)");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);  // 跟随重定向
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);        // 10秒超时

// 执行请求
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
    std::cerr << "Request failed: " << curl_easy_strerror(res) << "\n";
} else {
    // 获取HTTP状态码
    long http_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    if (http_code == 200) {
        std::cout << "\n=== Crawling successful ===\n";
        std::cout << "URL: " << url << "\n";
        std::cout << "Page Title: " << ExtractTitle(response) << "\n";
        std::cout << "Content Size: " << response.size() << " bytes\n";

        // 提取链接
        ExtractLinks(response);
    } else {
        std::cout << "HTTP Error: " << http_code << "\n";
    }
}
curl_easy_cleanup(curl);

}

int main() {
curl_global_init(CURL_GLOBAL_DEFAULT);

// 爬取示例网站
WebCrawler("https://example.com");

// 可添加更多URL
// WebCrawler("https://www.wikipedia.org");

curl_global_cleanup();
return0;

}

编译运行:
g++ demo.cpp -lcurl

运行结果:

Demo2:多表单请求

include

include

include

include

int main(void)
{
CURL *curl;
CURLM *multi_handle;
int still_running = 0;

curl_mime *form = NULL;
curl_mimepart *field = NULL;
struct curl_slist *headerlist = NULL;
staticconstchar buf[] = “Expect:”;

curl = curl_easy_init();
multi_handle = curl_multi_init();
if(curl && multi_handle) {
/* Create the form */
form = curl_mime_init(curl);

/* Fill in the file upload field */
field = curl_mime_addpart(form);
curl_mime_name(field, "sendfile");
curl_mime_filedata(field, "multi-post.c");

/* Fill in the filename field */
field = curl_mime_addpart(form);
curl_mime_name(field, "filename");
curl_mime_data(field, "multi-post.c", CURL_ZERO_TERMINATED);

/* Fill in the submit field too, even if this is rarely needed */
field = curl_mime_addpart(form);
curl_mime_name(field, "submit");
curl_mime_data(field, "send", CURL_ZERO_TERMINATED);

/* initialize custom header list (stating that Expect: 100-continue is
   not wanted */
headerlist = curl_slist_append(headerlist, buf);

/* what URL that receives this POST */
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/upload.cgi");
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
curl_easy_setopt(curl, CURLOPT_MIMEPOST, form);

curl_multi_add_handle(multi_handle, curl);
do {
  CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
  if(still_running)
    /* wait for activity, timeout or "nothing" */
    mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);

  if(mc)
    break;
} while(still_running);

curl_multi_cleanup(multi_handle);

/* always cleanup */
curl_easy_cleanup(curl);

/* then cleanup the form */
curl_mime_free(form);

/* free slist */
curl_slist_free_all(headerlist);

}
return0;
}

运行结果:

参考阅读:

https://curl.se/libcurl/c/simple.html

https://curl.se/libcurl/c/libcurl-tutorial.html

声明:来自程序员与背包客,仅代表创作者观点。链接:https://eyangzhen.com/6462.html

程序员与背包客的头像程序员与背包客

相关推荐

添加微信
添加微信
Ai学习群
返回顶部