
目录一.libcurl二.使用libcurl进行邮件验证码发送2.1.启动邮箱授权码2.1.1.QQ邮箱2.1.2.网易邮箱163邮箱2.2.测试程序三.二次封装libcurl验证码通知服务本来我们可以使用短信验证的但是由于国家政策个人无法再使用这个短信业务只有企业才有短信服务资质这样子的话我们就不能使用短信来进行这个验证了。因此我们这里采用了邮件来发送我们的验证码一.libcurl安装libcurlsudo apt install libcurl4-openssl-devlibcurl 是一个功能强大且广泛使用的客户端网络传输库它以跨平台、开源、轻量级和高性能著称是许多应用程序和工具实现网络通信的基础。什么是 libcurllibcurl 是由 Daniel Stenberg 于 1997 年开始开发的自由软件采用 MIT/X 衍生许可证允许在商业和开源项目中自由使用。它本质上是一个提供网络协议支持的函数库开发者可以通过调用其 API在应用程序中快速添加文件上传、下载、协议交互等功能而无需从头实现复杂的网络协议细节。libcurl 也是著名的命令行工具 curl 背后的核心引擎——curl 命令实际上是基于 libcurl 构建的一个可执行程序展示了该库的主要功能。主要特点跨平台支持 Windows、Linux、macOS、Android、iOS 以及各种 Unix 变体几乎能在所有主流操作系统上运行。多协议支持内置对超过 20 种网络协议的支持包括 HTTP/HTTPS、FTP/FTPS、SFTP、SCP、SMTP、POP3、IMAP、Telnet、TFTP、Gopher、MQTT 等覆盖了大多数常见的数据传输需求。易用且灵活提供简单直观的 C API同时有众多语言的绑定开发者可以方便地设置请求选项如 URL、超时、认证信息、代理等并接收回调来获取数据。高性能支持异步传输通过多接口、连接复用、管道化、断点续传等机制能高效处理大量并发请求。安全性通过集成 OpenSSL、GnuTLS、mbedTLS 等库支持 SSL/TLS 加密HTTPS、FTPS 等确保数据传输安全。同时支持各种认证方式Basic、Digest、NTLM、Kerberos 等。核心功能数据发送与接收向服务器发送请求GET、POST、PUT 等并接收响应数据支持上传和下载任意大小的文件。代理支持允许通过 HTTP、SOCKS4、SOCKS5 代理进行通信方便在受限网络环境中使用。Cookie 管理能够存储、发送和接收 HTTP Cookie模拟浏览器的会话状态。重定向处理自动或可控地跟随 HTTP 重定向。超时控制设置连接超时、传输超时等避免长时间阻塞。进度回调通过回调函数获取传输进度可用于显示进度条或取消操作。多种协议混合可以在同一个会话中操作不同协议例如先通过 FTP 下载文件再通过 HTTP 上传数据。架构组成libcurl 包含两个主要的接口层简单接口easy interface以同步方式处理单个传输任务是最常用的部分只需初始化一个“easy handle”设置选项然后执行即可。多接口multi interface提供异步传输能力允许在单个线程中同时管理多个连接适用于需要高并发或非阻塞操作的场景。此外libcurl 还提供了 C 风格的接口和数十种编程语言如 Python、PHP、Java、Go、Ruby 等的绑定使得开发者可以在熟悉的环境中直接使用 libcurl 的功能。常见应用场景网络数据抓取与爬虫用于从网站或 API 获取数据支持处理复杂的 HTTP 请求头和 Cookie。API 客户端许多软件内部通过 libcurl 与 RESTful 或 SOAP API 交互实现自动化数据交换。文件下载/上传工具如下载管理器、FTP 客户端、云存储同步工具等利用其断点续传和多协议特性。邮件客户端通过 SMTP、POP3、IMAP 协议发送和接收邮件。嵌入式系统由于其轻量级和跨平台特性常被用于路由器、智能家居设备等嵌入式环境的网络通信。测试与调试工具开发人员常用 libcurl 模拟各种网络请求测试服务端的行为。优势与生态成熟稳定历经二十多年发展代码质量高漏洞修复及时被无数项目验证过。活跃的社区官方持续更新添加新协议和功能同时有大量文档、教程和示例。高度可配置编译时可选择启用/禁用某些协议、SSL 库、特性以适应不同场景例如最小化体积或最大化功能。二.使用libcurl进行邮件验证码发送2.1.启动邮箱授权码我们采用的是通过自己的邮箱向他人邮箱发送验证码的方式。具体实现上我们使用 libcurl 库构建一个客户端通过该客户端向自己的邮箱例如 QQ 邮箱或 163 邮箱发起发送邮件的请求。自己的邮箱在接收到该请求后会将邮件转发至目标收件人需要注意的是客户端在连接邮箱服务器时并不能直接使用邮箱的登录密码进行身份验证而是必须使用邮箱授权码。授权码是邮箱服务商为第三方客户端提供的专用密码用于保障账户安全避免直接暴露用户的真实密码。该授权码需提前在邮箱账户的安全设置中生成并配置到客户端中作为连接邮箱服务器时的认证凭据。我们这里提供了QQ邮箱和163邮箱的邮箱授权码的获取方式在我们的客户端编写的时候大家只需要选择一种邮箱即可。2.1.1.QQ邮箱我们打开QQ邮箱然后点击右上角的设置进去之后我们点击发信设置然后往下翻找到账号管理并进入开启之后我们会得到下面这个授权码大家一定要记住了啊这个授权码就是我们需要的必须好好保存啊它只显示一次。如果你忘了也可以点击下面这个再次生成另外一个生成另外一个之后如果之前的不用了可以去下面这个地方对之前那个邮箱授权码进行停用2.1.2.网易邮箱163邮箱网易邮箱的就更容易了点进去这里有2种那么我们2个里面选一个开启即可。点击继续开启我们就得到了我们的邮箱授权码同样是只显示一次后续我们也可以在下面进行授权码的管理2.2.测试程序接下来我们就使用libcurl库来进行我们的邮箱验证码发送注意我们这里是使用163邮箱来作为发送方所以这个邮箱验证码必须是163邮箱的。test.cpp// 包含libcurl头文件 #include curl/curl.h #include iostream #include sstream // 回调函数当libcurl需要发送邮件数据时调用 // 参数bufferlibcurl提供的缓冲区用于写入数据 // size * nitems缓冲区大小字节数 // userdata用户自定义数据这里指向一个stringstream对象存储邮件内容 size_t callback(char *buffer, size_t size, size_t nitems, void *userdata) { // 将userdata转换为stringstream指针 std::stringstream *ss (std::stringstream*)userdata,; // 从stringstream中读取最多 size*nitems 字节到buffer ss-read(buffer, size * nitems); // 返回实际读取的字节数告诉libcurl本次提供了多少数据 return ss-gcount(); } int main() { // 邮箱账号配置请使用实际有效的账号和授权码 const std::string username JzSan521163.com;// 你的邮箱名 const std::string password UBTtCJ76iezXC9ws; // 你获取的邮箱授权码 const std::string url smtps://smtp.163.com:465; // 使用SMTPS协议端口465 const std::string from JzSan521163.com; // 发件人邮箱也就是我们自己的邮箱 const std::string to 270988047qq.com; // 收件人邮箱 // 1. 初始化libcurl全局环境 auto ret curl_global_init(CURL_GLOBAL_DEFAULT); if (ret ! CURLE_OK) { std::cout curl_global_init failed: curl_easy_strerror(ret) std::endl; return -1; } // 2. 创建一个easy句柄用于后续操作 auto curl curl_easy_init(); if (curl nullptr) { std::cout curl_easy_init failed std::endl; return -1; } // 3. 设置SMTP相关参数 // 设置SMTP服务器URL ret curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); if (ret ! CURLE_OK) { std::cout curl_easy_setopt CURLOPT_URL failed: curl_easy_strerror(ret) std::endl; return -1; } // 设置登录用户名 ret curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str()); if (ret ! CURLE_OK) { std::cout curl_easy_setopt CURLOPT_USERNAME failed: curl_easy_strerror(ret) std::endl; return -1; } // 设置登录密码或授权码 ret curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str()); if (ret ! CURLE_OK) { std::cout curl_easy_setopt CURLOPT_PASSWORD failed: curl_easy_strerror(ret) std::endl; return -1; } // 设置发件人地址MAIL FROM ret curl_easy_setopt(curl, CURLOPT_MAIL_FROM, from.c_str()); if (ret ! CURLE_OK) { std::cout curl_easy_setopt CURLOPT_MAIL_FROM failed: curl_easy_strerror(ret) std::endl; return -1; } // 构建收件人列表RCPT TO struct curl_slist *cs nullptr; cs curl_slist_append(cs, to.c_str()); // 可多次append添加多个收件人 ret curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, cs); if (ret ! CURLE_OK) { std::cout curl_easy_setopt CURLOPT_MAIL_RCPT failed: curl_easy_strerror(ret) std::endl; return -1; } // 准备邮件内容包括头部和正文 std::stringstream ss; // 通常邮件需要包含From和To头部但libcurl的MAIL_FROM和MAIL_RCPT已指定信封地址 // 许多邮件服务器仍要求邮件内容中包含这些头部此处注释掉了可根据需要取消注释。 // ss From: from \r\n; // ss To: to \r\n; ss Subject: 验证码\r\n; // 邮件主题 ss Content-Type: text/html\r\n; // 内容类型为HTML ss \r\n; // 头部与正文之间的空行 ss htmlbodyp你的验证码: b5678/b/pp验证码将在5分钟后失效./p/body/html\r\n; // HTML正文 // 设置读取数据的源即上面的stringstream ret curl_easy_setopt(curl, CURLOPT_READDATA, (void*)ss); if (ret ! CURLE_OK) { std::cout curl_easy_setopt CURLOPT_READDATA failed: curl_easy_strerror(ret) std::endl; return -1; } // 设置读取数据的回调函数 ret curl_easy_setopt(curl, CURLOPT_READFUNCTION, callback); if (ret ! CURLE_OK) { std::cout curl_easy_setopt CURLOPT_READFUNCTION failed: curl_easy_strerror(ret) std::endl; return -1; } // 设置为上传模式发送邮件本质是向服务器上传数据 ret curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); if (ret ! CURLE_OK) { std::cout curl_easy_setopt CURLOPT_UPLOAD failed: curl_easy_strerror(ret) std::endl; return -1; } // 4. 执行邮件发送请求 ret curl_easy_perform(curl); if (ret ! CURLE_OK) { std::cout curl_easy_perform failed: curl_easy_strerror(ret) std::endl; return -1; } // 5. 清理资源 curl_slist_free_all(cs); // 释放收件人列表 curl_easy_cleanup(curl); // 清理easy句柄 curl_global_cleanup(); // 清理全局环境 return 0; }makefiletest:test.cpp g -stdc17 $^ -o $ -lpthread -lcurl -lssl -lcrypto .PHONY:clean clean: rm -f test我们运行之后去QQ邮箱看看一点问题都没有啊。果然收到了我们发送的邮件。三.二次封装libcurl验证码通知服务那么后续我们会经常使用到这个邮箱验证码通知服务所以我们需要对它进行二次封装email.hpp#pragma once // 描述: 基于libcurl的邮件发送客户端用于发送验证码邮件 // 依赖: libcurl库, log.h (需提供LOG_ERROR, LOG_DEBUG宏定义) #include curl/curl.h // libcurl库头文件提供CURL相关函数和类型 #include iostream // 标准输入输出流此处未直接使用但可能用于调试 #include sstream // 字符串流用于构建邮件内容 #include memory // 智能指针支持 #include logger.hpp // 日志宏定义需提供LOG_ERROR、LOG_DEBUG等 namespace IMS { // 邮件服务配置信息结构体 struct mail_settings { std::string username; // 邮箱用户名即邮箱账号 std::string password; // 邮箱授权码不是登录密码需在邮箱设置中获取 std::string url; // SMTP服务器URL例如163邮箱为 smtps://smtp.163.com:465QQ邮箱为 smtps://smtp.qq.com:465 std::string from; // 发件人邮箱地址通常与username相同 }; // 验证码发送接口抽象基类 // 定义统一的发送接口方便扩展其他发送方式如短信等 class CodeClient { public: CodeClient() default; // 默认构造函数 virtual ~CodeClient() default; // 虚析构函数确保派生类正确释放资源 // 纯虚函数发送验证码到指定目标 // param to 收件地址如邮箱地址、手机号 // param code 验证码内容 // return 发送成功返回true失败返回false virtual bool send(const std::string to, const std::string code) 0; }; // 邮件客户端类继承自CodeClient使用libcurl通过SMTP协议发送验证码邮件 class MailClient : public CodeClient { public: using ptr std::shared_ptrMailClient; // 智能指针类型别名便于管理对象生命周期 // 构造函数初始化全局配置保存邮件服务器配置信息 // param settings 邮件服务器配置结构体 MailClient(const mail_settings settings) : _settings(settings) { // 初始化libcurl全局环境该函数必须在任何libcurl函数之前调用 // CURL_GLOBAL_DEFAULT 表示使用默认全局初始化选项 auto ret curl_global_init(CURL_GLOBAL_DEFAULT); if (ret ! CURLE_OK) { // 若初始化失败记录错误日志并终止程序因为后续所有操作都无法进行 LOG_ERROR(初始化CURL全局配置失败: {}, curl_easy_strerror(ret)); abort(); // 全局初始化失败则终止程序 } } // 析构函数释放libcurl全局配置资源 ~MailClient() { curl_global_cleanup(); // 清理全局环境必须在程序结束时调用 } // 发送邮件实现CodeClient接口 // param to 收件人邮箱地址 // param code 验证码 // return 成功返回true失败返回false virtual bool send(const std::string to, const std::string code) override { // 创建一个easy句柄代表一个libcurl会话 auto curl curl_easy_init(); if (curl nullptr) { LOG_ERROR(构造CURL操作句柄失败!); return false; } // 设置超时选项防止网络问题导致程序长时间阻塞 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L); // 连接超时时间秒超过则放弃连接 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // 整个请求的总超时时间秒 // 注意以下SSL验证选项仅适用于测试开发阶段实际生产环境应启用验证以确保安全性 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // 不验证对等端SSL证书跳过证书验证 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); // 不验证主机名跳过主机名验证 // 设置SMTP服务器URL包含协议、主机和端口 auto ret curl_easy_setopt(curl, CURLOPT_URL, _settings.url.c_str()); if (ret ! CURLE_OK) { LOG_ERROR(设置CURL的CURLOPT_URL参数失败: {}, curl_easy_strerror(ret)); curl_easy_cleanup(curl); // 清理句柄 return false; } // 设置SMTP认证用户名邮箱账号 ret curl_easy_setopt(curl, CURLOPT_USERNAME, _settings.username.c_str()); if (ret ! CURLE_OK) { LOG_ERROR(设置CURL的CURLOPT_USERNAME参数失败: {}, curl_easy_strerror(ret)); curl_easy_cleanup(curl); return false; } // 设置SMTP认证密码邮箱授权码 ret curl_easy_setopt(curl, CURLOPT_PASSWORD, _settings.password.c_str()); if (ret ! CURLE_OK) { LOG_ERROR(设置CURL的CURLOPT_PASSWORD参数失败: {}, curl_easy_strerror(ret)); curl_easy_cleanup(curl); return false; } // 设置发件人邮箱地址对应SMTP的MAIL FROM命令 ret curl_easy_setopt(curl, CURLOPT_MAIL_FROM, _settings.from.c_str()); if (ret ! CURLE_OK) { LOG_ERROR(设置CURL的CURLOPT_MAIL_FROM参数失败: {}, curl_easy_strerror(ret)); curl_easy_cleanup(curl); return false; } // 构建收件人列表对应SMTP的RCPT TO命令 // 使用curl_slist链表结构可添加多个收件人 struct curl_slist *cs nullptr; cs curl_slist_append(cs, to.c_str()); // 将收件人地址添加到链表 ret curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, cs); if (ret ! CURLE_OK) { LOG_ERROR(设置CURL的CURLOPT_MAIL_RCPT参数失败: {}, curl_easy_strerror(ret)); curl_slist_free_all(cs); // 释放收件人列表 curl_easy_cleanup(curl); return false; } // 构造邮件正文包含头部和HTML内容 auto body codeBody(to, code); // body 是一个 std::stringstream 对象 // 设置数据读取源即邮件内容的流对象libcurl将通过回调函数从中读取数据 ret curl_easy_setopt(curl, CURLOPT_READDATA, (void*)body); if (ret ! CURLE_OK) { LOG_ERROR(设置CURL的CURLOPT_READDATA参数失败: {}, curl_easy_strerror(ret)); curl_slist_free_all(cs); curl_easy_cleanup(curl); return false; } // 设置数据读取回调函数libcurl在需要发送邮件数据时会调用该函数 ret curl_easy_setopt(curl, CURLOPT_READFUNCTION, MailClient::callback); if (ret ! CURLE_OK) { LOG_ERROR(设置CURL的CURLOPT_READFUNCTION参数失败: {}, curl_easy_strerror(ret)); curl_slist_free_all(cs); curl_easy_cleanup(curl); return false; } // 指示这是一个上传操作发送邮件本质是向服务器上传数据 ret curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); if (ret ! CURLE_OK) { LOG_ERROR(设置CURL的CURLOPT_UPLOAD参数失败: {}, curl_easy_strerror(ret)); curl_slist_free_all(cs); curl_easy_cleanup(curl); return false; } // 执行邮件发送请求阻塞直到完成或出错 ret curl_easy_perform(curl); if (ret ! CURLE_OK) { LOG_ERROR(请求邮件服务器失败: {}, curl_easy_strerror(ret)); curl_slist_free_all(cs); curl_easy_cleanup(curl); return false; } // 清理资源 curl_slist_free_all(cs); // 释放收件人列表内存 curl_easy_cleanup(curl); // 释放easy句柄 LOG_DEBUG(发送邮件成功: {}-{}, to, code); // 记录调试信息 return true; } private: // 构造邮件正文HTML格式 // param to 收件人地址可用于个性化此处未使用但保留 // param code 验证码 // return 包含完整邮件内容的字符串流对象 std::stringstream codeBody(const std::string to, const std::string code) { std::stringstream ss; // 邮件头部 ss Subject: _title \r\n; // 邮件主题 ss Content-Type: text/html\r\n; // 内容类型为HTML ss \r\n; // 空行分隔头部和正文必须 // 邮件正文HTML格式 ss htmlbodyp你的验证码: b code /b/p p验证码将在5分钟后失效./p/body/html\r\n; return ss; } // libcurl读取数据回调函数静态成员函数 // 该函数由libcurl调用用于从自定义数据源stringstream中读取邮件数据并写入缓冲区 // param buffer libcurl提供的输出缓冲区 // param size 每个数据单元的大小字节数 // param nitems 数据单元的个数缓冲区总大小为 size * nitems // param userdata 用户自定义数据指针此处指向 std::stringstream 对象 // return 实际写入缓冲区的字节数返回0表示数据结束 static size_t callback(char *buffer, size_t size, size_t nitems, void *userdata) { std::stringstream *ss (std::stringstream*)userdata; ss-read(buffer, size * nitems); // 从流中读取数据 return ss-gcount(); // 返回实际读取的字节数 } private: const std::string _title 验证码; // 邮件标题固定前缀 mail_settings _settings; // 邮件服务器配置信息 }; } // namespace IMS我们来写一个测试程序看看#include memory #include ../../../common/email.hpp int main() { // 初始化日志调试模式输出到控制台 IMS::init_logger(false, , 0); // 调试模式日志级别 trace // 1. 构造邮件服务配置 IMS::mail_settings settings { .username JzSan521163.com, .password UBTtCJ76iezXC9ws, .url smtps://smtp.163.com:465, .from JzSan521163.com, }; // 2. 实例化邮件客户端对象 auto mail std::make_uniqueIMS::MailClient(settings); // 3. 发送邮件 bool ret mail-send(270988047qq.com, 6789); if (!ret) { std::cout 发送邮件失败 std::endl; } return 0; }可以看到非常的完美当然我们可以将验证码换成1234看看一点问题都没有