项目于中需要一个邮件定时推送功能,准备用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官网实例所得
例程中的这些配置需要按实际修改
下载源码:https://www.openssl.org/ 源码github下载较慢可以选择从gitee下,或者用上面的的链接资源
找到NOTES.WIN文件,里面记录了编译的一些须知
基本上就是
https://www.activestate.com/products/perl/downloads/
安装包下载完成后,直接默认安装,perl会默认加入系统环境变量,不用担心设置问题
安装nasmhttps://www.nasm.us/pub/nasm/releasebuilds/2.15.02/win64/
下载的nasm是一个zip包,
解压出文件夹,放入自己习惯的路径,然后添加进系统变量:
完成后,Win+R->cmd 调出console,验证nasm,是否正常:
以管理员权限打开运行,切换到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等若无后用,可以安心卸载
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编译完成。
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); } }
但是也有缺陷,比如在qq邮箱的收件箱中,需要手动点击图片显示
3.邮件的格式控制,用table tr td 来控制