C++调用libcurl开源库实现邮件的发送功能流程详解

目录
  • 1、为啥要选择libcurl库去实现邮件的发送
  • 2、调用libcurl库的API接口实现邮件发送
  • 3、构造待发送的邮件内容
  • 4、开通163发送邮件账号的SMTP服务
  • 5、排查接收的邮件内容为空的问题

libcurl中封装了支持这些协议的网络通信模块,支持跨平台,支持Windows,Unix,Linux等多个操作系统。libcurl提供了一套统一样式的API接口,我们不用关注各种协议下网络通信的实现细节,只需要调用这些API就能轻松地实现基于这些协议的数据通信。本文将简单地讲述一下使用libcurl实现邮件发送的相关细节。

1、为啥要选择libcurl库去实现邮件的发送

如果我们自己去使用socket套接字去编码,实现连接smtp邮件服务器,并完成和服务器的smtp协议的交互,整个过程走下来会非常地复杂,特别是要处理网络通信过程中的多种异常,整个流程的稳定性和健壮性没有保证。

而libcurl中已经实现了smtp协议的所有流程,我们不需要去关注协议的具体实现细节,我们只要去调用libcurl的API接口就能实现发送邮件的功能。libcurl库的稳定性是毋庸置疑的。

我们可以到官网上下载libcurl开源库最新的源码,直接使用Visual Studio编译出要用的dll库,至于使用Visual Studio如何编译libcurl代码,后面我会写一篇文章去详细介绍。

2、调用libcurl库的API接口实现邮件发送

先调用curl_easy_init接口初始化libcurl库,然后调用curl_easy_setopt(使用CURLOPT_URL选项)设置url请求地址,正是通过该url的前缀确定具体使用哪种协议。比如本例中发送邮件时需要使用smtp协议:

char urlBuf[256] = { 0 };
sprintf( urlBuf, "smtp://%s:%s", m_strServerName.c_str(), m_strPort.c_str() );
curl_easy_setopt(curl, CURLOPT_URL, urlBuf); 

设置url时使用的就是smtp前缀,然后带上目标服务器的IP和端口。

在使用相关协议完成数据交互时,可能还要设置一些其他的信息,比如用户名和密码等,都是通过调用curl_easy_setopt设置的:

curl_easy_setopt(curl, CURLOPT_USERNAME, m_strUserName.c_str());
curl_easy_setopt(curl, CURLOPT_PASSWORD, m_strPassword.c_str()); 

要发送的数据,则通过CURLOPT_READDATA选项去设置:

std::stringstream stream;
stream.str(m_strMessage.c_str());
stream.flush();

/* We're using a callback function to specify the payload (the headers and
* body of the message). You could just use the CURLOPT_READDATA option to
* specify a FILE pointer to read from. */
curl_easy_setopt(curl, CURLOPT_READFUNCTION, &CSmtpSendMail::payload_source);
curl_easy_setopt(curl, CURLOPT_READDATA, (void *)&stream);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);  

最后调用curl_easy_perform或者curl_multi_perform接口发起请求,该接口内部将去连接url中指定的服务器,并完成指定的协议协商与交互,并最终完成与服务器之间的数据通信。

调用libcurl库发送邮件的完整代码如下所示:

CURLcode CSmtpSendMail::SendMail()
{
	CreatMessage();
	bool ret = true;
	CURL *curl;
	CURLcode res = CURLE_OK;
	struct curl_slist *recipients = NULL;  

	curl = curl_easy_init();
	if (curl) {
		/* Set username and password */
		curl_easy_setopt(curl, CURLOPT_USERNAME, m_strUserName.c_str());
		curl_easy_setopt(curl, CURLOPT_PASSWORD, m_strPassword.c_str());  

		char urlBuf[256] = { 0 };
		sprintf( urlBuf, "smtp://%s:%s", m_strServerName.c_str(), m_strPort.c_str() );
		curl_easy_setopt(curl, CURLOPT_URL, urlBuf);
		/* If you want to connect to a site who isn't using a certificate that is
		* signed by one of the certs in the CA bundle you have, you can skip the
		* verification of the server's certificate. This makes the connection
		* A LOT LESS SECURE.
		*
		* If you have a CA cert for the server stored someplace else than in the
		* default bundle, then the CURLOPT_CAPATH option might come handy for
		* you. */
#ifdef SKIP_PEER_VERIFICATION
		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
#endif  

		/* If the site you're connecting to uses a different host name that what
		* they have mentioned in their server certificate's commonName (or
		* subjectAltName) fields, libcurl will refuse to connect. You can skip
		* this check, but this will make the connection less secure. */
#ifdef SKIP_HOSTNAME_VERIFICATION
		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
#endif  

		/* Note that this option isn't strictly required, omitting it will result
		* in libcurl sending the MAIL FROM command with empty sender data. All
		* autoresponses should have an empty reverse-path, and should be directed
		* to the address in the reverse-path which triggered them. Otherwise,
		* they could cause an endless loop. See RFC 5321 Section 4.5.5 for more
		* details.
		*/
		//curl_easy_setopt(curl, CURLOPT_MAIL_FROM, FROM);
		curl_easy_setopt(curl, CURLOPT_MAIL_FROM, m_strSendMail.c_str());
		/* Add two recipients, in this particular case they correspond to the
		* To: and Cc: addressees in the header, but they could be any kind of
		* recipient. */
		for (size_t i = 0; i < m_vRecvMail.size(); i++) {  

			recipients = curl_slist_append(recipients, m_vRecvMail[i].c_str());
		}
		curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);  

		std::stringstream stream;
		stream.str(m_strMessage.c_str());
		stream.flush();
		/* We're using a callback function to specify the payload (the headers and
		* body of the message). You could just use the CURLOPT_READDATA option to
		* specify a FILE pointer to read from. */  

		// 注意回调函数必须设置为static
		curl_easy_setopt(curl, CURLOPT_READFUNCTION, &CSmtpSendMail::payload_source);
		curl_easy_setopt(curl, CURLOPT_READDATA, (void *)&stream);
		curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);  

		/* Since the traffic will be encrypted, it is very useful to turn on debug
		* information within libcurl to see what is happening during the
		* transfer */
		curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);  

		curl_multi_perform()
		/* Send the message */
		res = curl_easy_perform(curl);
		CURLINFO info = CURLINFO_NONE;
		curl_easy_getinfo(curl, info);
		/* Check for errors */  

		if (res != CURLE_OK) {
			fprintf(stderr, "curl_easy_perform() failed: %s\n\n",
				curl_easy_strerror(res));  

			char achErrInfo[512] = {0};
			sprintf( achErrInfo, "curl_easy_perform() failed, error info: %s\n\n", curl_easy_strerror(res) );
			::MessageBoxA( NULL, achErrInfo, "Tip", MB_OK);
			ret = false;  

			m_strErrDesription = achErrInfo;

			/*				Sleep( 100 );
			res = curl_easy_perform(curl); */
		}
		else
		{
			m_strErrDesription = "";
		}

		/* Free the list of recipients */
		curl_slist_free_all(recipients);  

		/* Always cleanup */
		curl_easy_cleanup(curl);
	}
	else
	{
		res = CURLE_FAILED_INIT;
		char achErrInfo[512] = {0};
		sprintf( achErrInfo, "curl_easy_init() failed, error info: %s\n\n", curl_easy_strerror(res) );
		m_strErrDesription = achErrInfo;
    }
	return res;
}  

3、构造待发送的邮件内容

libcurl负责和smtp邮件服务器建链,完成smtp简单邮件协议的协商与交互,但要发送的邮件内容则需要我们自己去根据协议的规范去构建。那邮件发送的内容的数据格式到底是什么样子的呢?其实很简单,找一个支持发送邮件的软件,发送邮件时抓一下包,就能抓出对应的格式,比如:

按照上面的格式构建就可以了,相关代码如下:

void CSmtpSendMail::CreatMessage()
{
	//m_strMessage = "Date: 13 Nov 2021 12:52:14 +0800";
	m_strMessage = "From: ";
	m_strMessage += m_strSendMail;
	m_strMessage += "\r\nReply-To: ";
	m_strMessage += m_strSendMail;
	m_strMessage += "\r\nTo: ";
	for (size_t i = 0; i < m_vRecvMail.size(); i++)
	{
		if (i > 0) {
			m_strMessage += ",";
		}
		m_strMessage += m_vRecvMail[i];
	}
	m_strMessage += "\r\n";
	m_strMessage += m_strSubject;
	m_strMessage += "\r\nX-Mailer: The Bat! (v3.02) Professional";
	m_strMessage += "\r\nMime-Version: 1.0";
	m_strMessage += "\r\nContent-Type: multipart/mixed;";
	m_strMessage += "boundary=\"simple boundary\"";  //__MESSAGE__ID__54yg6f6h6y456345
	//m_strMessage += "\r\nThis is a multi-part message in MIME format.";
	m_strMessage += "\r\n\r\n--simple boundary";
	//正文
	m_strMessage += "\r\nContent-Type: text/html;";
	m_strMessage += "charset=";
	//m_strMessage += "\"";
	m_strMessage += m_strCharset;
	//m_strMessage += "\"";
	m_strMessage += "\r\nContent-Transfer-Encoding: 7bit";
	m_strMessage += "\r\n";
	m_strMessage += m_strContent;  

	//附件
	std::string filename = "";
	std::string filetype = "";
	for (size_t i = 0; i < m_vAttachMent.size(); i++)
	{
		m_strMessage += "\r\n--simple boundary";
		GetFileName(m_vAttachMent[i], filename);
		GetFileType(m_vAttachMent[i], filetype);
		SetContentType(filetype);
		SetFileName(filename);  

		m_strMessage += "\r\nContent-Type: ";
		m_strMessage += m_strContentType;
		m_strMessage += "\tname=";
		m_strMessage += "\"";
		m_strMessage += m_strFileName;
		m_strMessage += "\"";
		m_strMessage += "\r\nContent-Disposition:attachment;filename=";
		m_strMessage += "\"";
		m_strMessage += m_strFileName;
		m_strMessage += "\"";
		m_strMessage += "\r\nContent-Transfer-Encoding:base64";
		m_strMessage += "\r\n\r\n";  

		FILE *pt = NULL;
		if ((pt = fopen(m_vAttachMent[i].c_str(), "rb")) == NULL) {  

			std::cerr << "打开文件失败: " << m_vAttachMent[i] <<std::endl;
			continue;
		}
		fseek(pt, 0, SEEK_END);
		int len = ftell(pt);
		fseek(pt, 0, SEEK_SET);
		int rlen = 0;
		char buf[55];
		for (size_t i = 0; i < len / 54 + 1; i++)
		{
			memset(buf, 0, 55);
			rlen = fread(buf, sizeof(char), 54, pt);
			m_strMessage += base64_encode((const unsigned char*)buf, rlen);
			m_strMessage += "\r\n";
		}  

		fclose(pt);
		pt = NULL;
	}  

	m_strMessage += "\r\n--simple boundary--\r\n";
}  

4、开通163发送邮件账号的SMTP服务

上述代码处理好后,运行如下的测试程序:

在上述界面中输入163的smtp服务器地址,使用默认的25端口,并填写发送邮件地址和发送邮件的密码,点击“发送测试邮件”按钮,结果邮件并没有发送成功。

在代码中添加断点调试,发现curl_easy_perform接口返回的错误码为CURLE_LOGIN_DENIED,如下所示:

于是通过CURLE_OK go到错误码定义的头文件中,去查看CURLE_LOGIN_DENIED错误码的含义:

注释中提示可能是发送邮件的用户名或密码错误引起的。用户名和密码填写的应该没问题啊?于是账号到网页上登陆一下163邮箱,可以成功登陆的,说明账号和密码是没问题的。那到底是咋回事呢?

后来想到,是不是要到发送邮件账号中去开启一下smtp服务才可以登陆到163的smtp服务器上?于是到网页上登陆,按下列的操作步骤找到开启当前账号的smtp服务入口:

点击开启按钮,会弹出如下的提示框:

点击继续开启,进入下面的页面:

提示需要扫码发送短信进行验证。于是使用网易邮件大师APP扫描了一下,自动跳转到发送短信的页面,发送验证短信即可。最后弹出如下的授权密码页面:

要将这个授权密码记录下来,登陆smtp服务器时需要使用这个授权密码,而不是账号的密码!

于是在测试页面中输入授权码,邮件就能发送成功了。

5、排查接收的邮件内容为空的问题

邮件是能正常发送出去了,邮件也能正常接收到,但接收到的邮件内容是空的:

这是啥情况?明明发送邮件时有设置邮件内容的,为啥收到的邮件内容是空的呢?

上述代码在几年前测试过,好像没问题的,难道163邮箱系统升级了,不再兼容老的数据格式了?于是想到了海康的视频监控客户端,该客户端可以到海康官网上下载,免费使用,其中系统设置中有个发送邮件的功能:

海康的上述界面中发送测试邮件是没问题的,接收到的邮件也是有内容的。于是赶紧抓一下海康发送邮件的数据包,以tcp.port==25过滤了一下,抓出海康发出去的邮件内容:

又抓取了一下我们软件发出去的邮件内容如下:

于是详细地对比了海康与我们发出去的数据内容,多次尝试修改我们构建邮件数据的代码,比如charset编码格式、boundry类型等,甚至是否会空行。最后经过多次尝试找到了原因,是在具体的邮件内容上面需要人为加上一个空行,我们代码在构造邮件数据时没有加空行,导致接收到的邮件内容是空的!

以上就是C++调用libcurl开源库实现邮件的发送功能流程详解的详细内容,更多关于C++ 邮件发送的资料请关注我们其它相关文章!

(0)

相关推荐

  • C++发送邮件实现代码

    本文实例为大家分享了C++发送邮件的具体代码,供大家参考,具体内容如下 首先,别忘了要设置发送邮箱的smtp,例如,假设你需要用网易邮箱,你需要去你的163邮箱设置开启smtp(有的邮箱还需要设置授权码).接着就可以用以下代码发送邮件了: // SendMail.h #ifndef _SEND_MAIL_H_ #define _SEND_MAIL_H_ #include <windows.h> #include <stdio.h> #include <WinSock.h>

  • C++实现含附件的邮件发送功能

    C++实现邮件发送程序在vs2013测试通过,一共3个文件,发邮件的程序封装为Csmtp 类. 1.测试用的主函数 // #include "Csmtp.h" #pragma comment(lib, "Kernel32.lib") int main() { Csmtp mail( 25, "smtp.126.com", "username@126.com",// 来源邮箱 "pwd", "use

  • C++实现发送邮件和附件功能

    本文实例为大家分享了C++实现发送邮件和附件的具体代码,供大家参考,具体内容如下 头文件 /************************* *发送邮件模块头文件 *可以发送文本和附件(支持多个附件一起发送) **************************/ #pragma once struct sMailInfo //邮件信息 { char* m_pcUserName;//用户登录邮箱的名称 char* m_pcUserPassWord;//用户登录邮箱的密码 char* m_pcS

  • C++发邮件简单实例详解

    C++发邮件用的是阻塞式socket模型,发送完数据后需要接收返回值,才能接着发送. 本程序不发送邮件附件,发附件的实例:C++实现含附件的邮件发送功能 #include <iostream> #include <string> #include <WinSock2.h> //适用平台 Windows using namespace std; #pragma comment(lib, "ws2_32.lib") /*链接ws2_32.lib动态链接库

  • C++实现邮件群发的方法

    本文实例讲述了C++实现邮件群发的方法.分享给大家供大家参考.具体如下: 关于生成随机QQ邮箱不精确的问题,在之后版本打算另写一个采集器插件进行帐号采集,所以,这个软件只用来进行内容发送,邮箱进行随机生成 如果你已经有采集来的QQ号,请复制到SendList.txt 替换内容即可 可以直接复制HTML代码到邮件内容,保存即可.目前邮件内容最大设置为10000字节,如果有增大的必要,欢迎提交留言. 这是我学习后VC编程中涉及到多线程,socket,及一些WINDOWS API的宗合应用 使用说明:

  • C++调用libcurl开源库实现邮件的发送功能流程详解

    目录 1.为啥要选择libcurl库去实现邮件的发送 2.调用libcurl库的API接口实现邮件发送 3.构造待发送的邮件内容 4.开通163发送邮件账号的SMTP服务 5.排查接收的邮件内容为空的问题 libcurl中封装了支持这些协议的网络通信模块,支持跨平台,支持Windows,Unix,Linux等多个操作系统.libcurl提供了一套统一样式的API接口,我们不用关注各种协议下网络通信的实现细节,只需要调用这些API就能轻松地实现基于这些协议的数据通信.本文将简单地讲述一下使用lib

  • go打包aar及flutter调用aar流程详解

    目录 一.目的 二.背景 三.流程 问题: 问题一:go如何打包为移动端的包 1.环境配置 2.go配置与打包 问题二:flutter如何调用aar 第一步:存放aar与修改gradle配置 第二步:修改MainActivity.java入口代码 第三步:flutter调用 四.结论 一.目的 本篇文章的目的是记录本人使用flutter加载与调用第三方aar包. 二.背景 本人go后端,业余时间喜欢玩玩flutter.一直有一个想法,go可以编译为第三方平台的可执行程序,而flutter可以是一

  • Python3标准库之functools管理函数的工具详解

    1. functools管理函数的工具 functools模块提供了一些工具来调整或扩展函数和其他callable对象,从而不必完全重写. 1.1 修饰符 functools模块提供的主要工具就是partial类,可以用来"包装"一个有默认参数的callable对象.得到的对象本身就是callable,可以把它看作是原来的函数.它与原函数的参数完全相同,调用时还可以提供额外的位置或命名函数.可以使用partial而不是lambda为函数提供默认参数,有些参数可以不指定. 1.1.1 部

  • Springboot整合实现邮件发送的原理详解

    目录 开发前准备 基础知识 进阶知识 加入依赖 配置邮件 测试邮件发送 通常在实际项目中,也有其他很多地方会用到邮件发送,比如通过邮件注册账户/找回密码,通过邮件发送订阅信息等等.SpringBoot集成邮件服务非常简单,通过简单的学习即可快速掌握邮件业务类的核心逻辑和企业邮件的日常服务 开发前准备 首先注册发件邮箱并设置客户端授权码,这里以QQ 免费邮箱为例,其他的邮箱的配置也大同小异. 登录 QQ 邮箱,点击设置->账户,开启IMAP/SMTP服务,并生成授权码. 基础知识 电子邮件需要在邮

  • npm脚本库组织在项目中的地位详解

    目录 一.脚本的地位 二.“脚本调度”的难题 三.如此简单? 四.此剑名曰: npm-run-all 4.1 安装 4.2 第一个命令: npm-run-all 4.3 第二个命令:npm-s 4.4 第三个命令:npm-p 4.5 通配符 4.6 更多实用能力 一.脚本的地位 脚本是 项目真正的入口 . 无论你是刚刚 clone 完公司的项目,抑或是你准备在开源社区做一点微小的贡献:你需要做的第一件事,永远是: 打开 package.json,看看 scripts 里都有哪些脚本. 有些脚本负

  • JS库之Three.js 简易入门教程(详解之一)

    开场白 webGL可以让我们在canvas上实现3D效果.而three.js是一款webGL框架,由于其易用性被广泛应用.如果你要学习webGL,抛弃那些复杂的原生接口从这款框架入手是一个不错的选择. 博主目前也在学习three.js,发现相关资料非常稀少,甚至官方的api文档也非常粗糙,很多效果需要自己慢慢敲代码摸索.所以我写这个教程的目的一是自己总结,二是与大家分享. 本篇是系列教程的第一篇:入门篇.在这篇文章中,我将以一个简单的demo为例,阐述three.js的基本配置方法.学完这篇文章

  • python 第三方库的安装及pip的使用详解

    python是一款简单易用的编程语言,特别是其第三方库,能够方便我们快速进入工作,但其第三方库的安装困扰很多人. 现在安装python时,已经能自动安装pip了 安装成功后,我们可以在Scripts 文件夹下看到pip 使用pip 安装类库也比较简单 pip install ... 即可 以上这篇python 第三方库的安装及pip的使用详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • C++调用Python基础功能实例详解

    c++调用Python首先安装Python,以win7为例,Python路径为:c:\Python35\,通过mingw编译c++代码. 编写makefile文件,首先要添加包含路径: inc_path += c:/Python35/include 然后添加链接参数: ld_flag += c:/Python35/libs/libpython35.a 在源文件中添加头文件引用: #include "Python.h" Python解释器需要进行初始化,完成任务后需要终止: void s

  • 用python标准库difflib比较两份文件的异同详解

    [需求背景] 有时候我们要对比两份配置文件是不是一样,或者比较两个文本是否异样,可以使用linux命令行工具diff a_file b_file,但是输出的结果读起来不是很友好.这时候使用python的标准库difflib就能满足我们的需求. 下面这个脚本使用了difflib和argparse,argparse用于解析我们给此脚本传入的两个参数(即两份待比较的文件),由difflib执行比较,比较的结果放到了一个html里面,只要找个浏览器打开此html文件,就能直观地看到比较结果,两份文件有差

  • JVM上高性能数据格式库包Apache Arrow入门和架构详解(Gkatziouras)

    Apache Arrow是是各种大数据工具(包括BigQuery)使用的一种流行格式,它是平面和分层数据的存储格式.它是一种加快应用程序内存密集型. 数据处理和数据科学领域中的常用库: Apache Arrow.诸如Apache Parquet,Apache Spark,pandas之类的开放源代码项目以及许多商业或封闭源代码服务都使用Arrow.它提供以下功能: 内存计算 标准化的柱状存储格式 一个IPC和RPC框架,分别用于进程和节点之间的数据交换 让我们看一看在Arrow出现之前事物是如何

随机推荐