C++ 邮件推送 (smtp+libcurl+openssl)

C++ 邮件推送 (smtp+libcurl+openssl)

  1. openssl 编译
  2. Libcurl 编译
  3. Smtp邮件推送

项目于中需要一个邮件定时推送功能,准备用smtp实现

本文中所有资源提供下载

  • 开箱可编译源码+编译相关环境

https://download.csdn.net/download/weixin_48991609/12575990

  • 直接可以使用的x64 libcurl+openssl文件

 https://download.csdn.net/download/weixin_48991609/12575966

  • libcurl+openssl+smtp 邮件发送例程

https://download.csdn.net/download/weixin_48991609/12575974

源码测试正常使用,其中的实现,是集百家饭+libcurl官网实例所得

例程中的这些配置需要按实际修改

openssl 编译

下载源码:https://www.openssl.org/ 源码github下载较慢可以选择从gitee下,或者用上面的的链接资源

找到NOTES.WIN文件,里面记录了编译的一些须知

基本上就是

环境准备:

  1.  Visual Studio  得有!
  1. 安装perl

https://www.activestate.com/products/perl/downloads/

安装包下载完成后,直接默认安装,perl会默认加入系统环境变量,不用担心设置问题

  1. 安装nasm

https://www.nasm.us/pub/nasm/releasebuilds/2.15.02/win64/

下载的nasm是一个zip包,

解压出文件夹,放入自己习惯的路径,然后添加进系统变量:

 

 

完成后,Win+R->cmd 调出console,验证nasm,是否正常:

开始编译

  1. 从开始菜单中找到

以管理员权限打开运行,切换到openssl 源码目录

如果编译32位库使用:

perl Configure VC-WIN32 --prefix="E:\Code\MY OPEN SRC\SMTP\resource\openssl_build" --openssldir="E:\Code\MY OPEN SRC\SMTP\resource\openssl_build"

 

如果编译64位库使用:

perl Configure VC-WIN64A --prefix="E:\Code\MY OPEN SRC\SMTP\resource\openssl_build" --openssldir="E:\Code\MY OPEN SRC\SMTP\resource\openssl_build"

 

注意

1.--prefix --openssldir 可以改为自己的目录,或者不加这两个参数,按默yogn认生成,如果加了自定义路径,路径中最好不要有空格!如果有空格,路径加引号。

 

2.如果运行出现Can't locate Win32/Console.pm in @INC 错误,

解决方法:

找到如上红框内目录,找到Config.pm,右键->属性,去掉该文件的只读属性

然后,进行编辑,找到400行所在的函数_warn,注释这个函数的实现

保存,退出。

执行后,在openssl目录将生成makefile文件

在openssl 源码目录中将有makefile文件,让,后执行nmake,进行编译

等待编译完成后,可以执行

nmake test

nmake install

完成后,在上面指定的目录中将生成库相关文件

至此,openssl编译完成

perl等若无后用,可以安心卸载

 

LibcurL编译

环境准备

https://github.com/curl/curl 下载源码

Libcurl的curl\projects\Windows目录中有各个版本的vs的项目文件,我们直接从cmake生成。

安装cmake

 

开始编译

打开cmake-gui,选择好路径,点击configure,选需要生成的版本

 

在配置中选择CMAKE_USER_OPENSSL,然后重新点击Configure

出现配置错误

用刚生成的openssl库配置

对于openssl(在1.0.x之前的版本中,文件为libeay32.dll和ssleay32.dll,在1.1.x之后的版本中,名字是libssl.dll和libcrypto.dll).

然后重新configure,完成后点击Generate,然后Open Project

这个libcurl就是我们要的,并且链接了刚生成的openssl库。分别编译Debug与Release版本,完成后生成lib与dll文件。

如何验证生成lib是带openssl的?生成解决方按中的curl项目,这个curl会生成curl.exe,这个curl.exe是链接我们生成的libcurl库的

生成有curl后,在cmd中做如下验证,如果提示libcurl-d.dll缺失,从刚生成的库中拷贝到curl.exe所在目录

上面的feature包含SSL,说明libcurl库开启openssl

至此,libcurl编译完成。

 

Libcurl+openssl+smtp 进行邮件发送

smtp的封装试下如下,代码用了好久了,里面的实现是 别人的封装+libcurl官方example+自己的修改,感谢源封装者,源链接等找到补上。

#pragma once  

#include <string>  
#include <vector>  

#define SKIP_PEER_VERIFICATION  
#define SKIP_HOSTNAME_VERIFICATION  


class CSmtpSendMail {
public:
	CSmtpSendMail(const std::string & charset = "UTF-8"); // 也可以传入utf

	//设置stmp用户名、密码、服务器、端口(端口其实不用指定,libcurl默认25,但如果是smtps则默认是465)  
	void SetSmtpServer(const std::string &username, const std::string& password, const std::string& servername, const std::string &port = "25");
	//发送者姓名,可以不用  

	void SetSendName(const std::string& sendname);

	//发送者邮箱   
	void SetSendMail(const std::string& sendmail);

	//添加收件人  
	void AddRecvMail(const std::string& recvmail);

	//设置主题  
	void SetSubject(const std::string &subject);

	//设置正文内容  
	void SetBodyContent(const std::string &content);

	//添加附件  
	void AddAttachment(const std::string &filename);

	//发送邮件  
	bool SendMail();
private:

	//回调函数,将MIME协议的拼接的字符串由libcurl发出  
	static size_t payload_source(void *ptr, size_t size, size_t nmemb, void *stream);

	//创建邮件MIME内容  
	void CreatMessage();

	//获取文件类型  
	int GetFileType(std::string const& stype);

	//设置文件名  
	void SetFileName(const std::string& FileName);

	//设置文件的contenttype  
	void SetContentType(std::string const& stype);

	//得到文件名  
	void GetFileName(const std::string& file, std::string& filename);

	//得到文件类型  
	void GetFileType(const std::string& file, std::string& stype);

private:
	std::string m_strCharset; //邮件编码  
	std::string m_strSubject; //邮件主题  
	std::string m_strContent; //邮件内容  
	std::string m_strFileName; //文件名  
	std::string m_strMessage;// 整个MIME协议字符串  
	std::string m_strUserName;//用户名  
	std::string m_strPassword;//密码  
	std::string m_strServerName;//smtp服务器  
	std::string m_strPort;//端口  
	std::string m_strSendName;//发送者姓名  
	std::string m_strSendMail;//发送者邮箱  
	std::string m_strContentType;//附件contenttype  
	std::string m_strFileContent;//附件内容  

	std::vector<std::string> m_vRecvMail; //收件人容器  
	std::vector<std::string> m_vAttachMent;//附件容器  
};
#include "smtpsendmail.h"
#include "../3rd/base64/base64.h"
#include "../3rd/libcurl/include/curl/curl.h"
#include <iostream>
#include <sstream>
#include <fstream>

CSmtpSendMail::CSmtpSendMail(const std::string & charset)
{
    m_strCharset = charset;
    m_vRecvMail.clear();
}

void CSmtpSendMail::SetSmtpServer(const std::string & username, const std::string &password, const std::string & servername, const std::string & port)
{
    m_strUserName = username;
    m_strPassword = password;
    m_strServerName = servername;
    m_strPort = port;
}

void CSmtpSendMail::SetSendName(const std::string & sendname)
{
    std::string strTemp = "";
    strTemp += "=?";
    strTemp += m_strCharset;
    strTemp += "?B?";
    strTemp += base64_encode((unsigned char *)sendname.c_str(), sendname.size());
    strTemp += "?=";
    m_strSendName = strTemp;
    //m_strSendName = sendname;
}

void CSmtpSendMail::SetSendMail(const std::string & sendmail)
{
    m_strSendMail = sendmail;
}

void CSmtpSendMail::AddRecvMail(const std::string & recvmail)
{
    m_vRecvMail.push_back(recvmail);
}

void CSmtpSendMail::SetSubject(const std::string & subject)
{
    std::string strTemp = "";
    strTemp = "Subject: ";
    strTemp += "=?";
    strTemp += m_strCharset;
    strTemp += "?B?";
    strTemp += base64_encode((unsigned char *)subject.c_str(), subject.size());
    strTemp += "?=";
    m_strSubject = strTemp;
}

void CSmtpSendMail::SetBodyContent(const std::string & content)
{
    m_strContent = content;
}

void CSmtpSendMail::AddAttachment(const std::string & filename)
{
    m_vAttachMent.push_back(filename);
}

bool 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());
        std::string tmp = "smtps://";
        tmp += m_strServerName;
        tmp += ":";
        tmp += m_strPort;
        // 注意不能直接传入tmp,应该带上.c_str(),否则会导致下面的
        // curl_easy_perform调用返回CURLE_COULDNT_RESOLVE_HOST错误
        // 码
        curl_easy_setopt(curl, CURLOPT_URL, tmp.c_str());
        /* 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, 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. */
        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 */
        int nTimes = 0;
        /* Send the message */
        res = curl_easy_perform(curl);
        CURLINFO info = CURLINFO_NONE;
        curl_easy_getinfo(curl, info);
        /* Check for errors */
        while (res != CURLE_OK)
        {
            nTimes++;
            if (nTimes > 5)
            {
                break;
            }
            fprintf(stderr, "curl_easy_perform() failed: %s\n\n", curl_easy_strerror(res));
            ret = false;
            /*				Sleep( 100 );
            res = curl_easy_perform(curl); */
        }
        /* Free the list of recipients */
        curl_slist_free_all(recipients);
        /* Always cleanup */
        curl_easy_cleanup(curl);
    }
    return ret;
}

size_t CSmtpSendMail::payload_source(void *ptr, size_t size, size_t nmemb, void *stream)
{
    size_t num_bytes = size * nmemb;
    char* data = (char*)ptr;
    std::stringstream* strstream = (std::stringstream*)stream;
    strstream->read(data, num_bytes);
    return strstream->gcount();
}

void CSmtpSendMail::CreatMessage()
{
    m_strMessage = "From: ";
    m_strMessage += m_strSendName + "<" + m_strSendMail + ">"/*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: JXO Mailer V1.2";
    m_strMessage += "\r\nMime-Version: 1.0";
    // 	m_strMessage += "\r\nContent-Type: multipart/mixed;";
    // 	m_strMessage += "boundary=\"simple boundary\"";
    // 	m_strMessage += "\r\nThis is a multi-part message in MIME format.";
    // 	m_strMessage += "\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\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 (int 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";*/
}


int CSmtpSendMail::GetFileType(std::string const & stype)
{
    if (stype == "txt")
    {
        return 0;
    }
    else if (stype == "xml")
    {
        return 1;
    }
    else if (stype == "html")
    {
        return 2;
    }
    else if (stype == "jpeg")
    {
        return 3;
    }
    else if (stype == "png")
    {
        return 4;
    }
    else if (stype == "gif")
    {
        return 5;
    }
    else if (stype == "exe")
    {
        return 6;
    }
    return -1;
}

void CSmtpSendMail::SetFileName(const std::string & FileName)
{
    std::string EncodedFileName = "=?";
    EncodedFileName += m_strCharset;
    EncodedFileName += "?B?";//修改
    EncodedFileName += base64_encode((unsigned char *)FileName.c_str(), FileName.size());
    EncodedFileName += "?=";
    m_strFileName = EncodedFileName;
}

void CSmtpSendMail::SetContentType(std::string const & stype)
{
    int type = GetFileType(stype);
    switch (type)
    {
    //
    case 0:
        m_strContentType = "plain/text;";
        break;
    case 1:
        m_strContentType = "text/xml;";
        break;
    case 2:
        m_strContentType = "text/html;";
    case 3:
        m_strContentType = "image/jpeg;";
        break;
    case 4:
        m_strContentType = "image/png;";
        break;
    case 5:
        m_strContentType = "image/gif;";
        break;
    case 6:
        m_strContentType = "application/x-msdownload;";
        break;
    default:
        m_strContentType = "application/octet-stream;";
        break;
    }
}

void CSmtpSendMail::GetFileName(const std::string& file, std::string& filename)
{
    std::string::size_type p = file.find_last_of('/');
    if (p == std::string::npos)
        p = file.find_last_of('\\');
    if (p != std::string::npos)
    {
        p += 1; // get past folder delimeter
        filename = file.substr(p, file.length() - p);
    }
}

void CSmtpSendMail::GetFileType(const std::string & file, std::string & stype)
{
    std::string::size_type p = file.find_last_of('.');
    if (p != std::string::npos)
    {
        p += 1; // get past folder delimeter
        stype = file.substr(p, file.length() - p);
    }
}

 

一些注意事项

  1. 发送的内容跟主题,传入的内容,使用“utf-8”编码格式,如果有在Unicode环境下,有中文等字符,注意自己转换
  2. 邮件内容,可以使用html进行格式化,但注意编码格式。
  3. 如果需要在html中使用图片,图片如果可以采用网络路径,比如cdn等,也可以使用base64编码,下图中注释内是网链的形式。

但是也有缺陷,比如在qq邮箱的收件箱中,需要手动点击图片显示

3.邮件的格式控制,用table tr td 来控制

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值