Linux平台使用smtp协议 发送邮件

  • 前言
  • 项目简介
  • 项目开发环境
  • 项目代码
  • 测试结果以及存在问题
  • 写在最后

前言

 本人第一次写博客,主要目的是想记录一下自己的学习结果,本篇博客内容纯手打,参考资料来自于互联网,由于参考资料过多,就不一一列举参考来源,若是原作者认为侵犯了您的权益,请及时与我联系。

项目简介

 该项目是利用C/C++语言编写的基于MIME1.0协议的发送邮件的一个小程序,可以同时发送给多个收件人,可以添加多个附件。我写这个小程序的目的是想自己做一个验证码系统,用于自己其它项目的验证服务。

项目开发环境

操作系统: Ubuntu18.04 (内核:4.15.0-134-generic)
编程语言:C/C++ (C++11)
编译器: g++7.5.0
开发工具:vscode,cmake

vscode的工程配置以及cmakelists.txt由于不是本篇博客的重点,就略去,有需要的可以私信或者我改天上传到git上(emmm,这项目太小了,不想上传)。

项目代码

 项目代码我是从一篇前辈上的csdn上面的代码改的,我这里就不找了,有小伙伴知道的话在评论区里留言或者发我邮箱,或者等我找到后在把引用信息添加进去。具体修改的代码部分是:添加发送多个收件人,添加发送附件(可以发送多个附件)

smtp.h文件

#ifndef __SMTP_H__
#define __SMTP_H__#include <unistd.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;const int MAX_EMAIL_MESSAGE_LEN = 4096;static const char base64Char[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";char *base64Encode(char const *origStr, unsigned int origLength);
char *base64Decode(char const *origStr, unsigned int *resLength);class Smtp
{public:Smtp();Smtp(int port,string srvDomain,   //smtp服务器域名string userName, //用户名string password,   //密码string targetEmail, //目的邮件地址string emailTitle,  //主题string content  //内容);~Smtp();void setSrvDomain(string &strDomain) { this->_domain = strDomain; }void setUserName(string &strUser) { this->_user = strUser; }void setPass(string &strPass) { this->_password = strPass; }void setEmailTitle(string &strTitle) { this->_title = strTitle; }void setContent(string &strContent) { this->_content = strContent; }void setPort(int nPort) { this->_port = nPort; }void addTargetEmail(string targetEmail);void addAccessory(string fileName);int sendEmail();private:int _port;string _domain;string _user;string _password;vector<string> _targetAddrs;string _title;//邮件正文string _content;//附件文件名包含路径vector<string> _accessories;char _buff[MAX_EMAIL_MESSAGE_LEN + 1];int _buffLen;int _sockClient;bool _createConn();bool _send(string &strMessage);bool _recv();void _formatEmailHead(string &strEmail);int _login();bool _sendEmailHead();bool _sendTextBody();bool _sendAccessory(const char *fileName);bool _sendEnd();
};#endif // !__SMTP_H__

smtp.cpp文件

#include "smtp.h"
#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;char *base64Encode(char const *origStr, unsigned int origLength)
{unsigned char const *orig = (unsigned char const *)origStr; // in case any input bytes have the MSB setif (orig == NULL)return NULL;unsigned const numOrig24BitValues = origLength / 3;bool havePadding = origLength > numOrig24BitValues * 3;bool havePadding2 = origLength == numOrig24BitValues * 3 + 2;unsigned const numResultBytes = 4 * (numOrig24BitValues + havePadding);char *result = new char[numResultBytes + 3]; // allow for trailing '/0'unsigned i;for (i = 0; i < numOrig24BitValues; ++i){result[4 * i + 0] = base64Char[(orig[3 * i] >> 2) & 0x3F];result[4 * i + 1] = base64Char[(((orig[3 * i] & 0x3) << 4) | (orig[3 * i + 1] >> 4)) & 0x3F];result[4 * i + 2] = base64Char[((orig[3 * i + 1] << 2) | (orig[3 * i + 2] >> 6)) & 0x3F];result[4 * i + 3] = base64Char[orig[3 * i + 2] & 0x3F];}if (havePadding){result[4 * i + 0] = base64Char[(orig[3 * i] >> 2) & 0x3F];if (havePadding2){result[4 * i + 1] = base64Char[(((orig[3 * i] & 0x3) << 4) | (orig[3 * i + 1] >> 4)) & 0x3F];result[4 * i + 2] = base64Char[(orig[3 * i + 1] << 2) & 0x3F];}else{result[4 * i + 1] = base64Char[((orig[3 * i] & 0x3) << 4) & 0x3F];result[4 * i + 2] = '=';}result[4 * i + 3] = '=';}result[numResultBytes] = '\0';return result;
}char *base64Decode(const char *origStr, unsigned int *resLength)
{//根据base64表,以字符找到对应的十进制数据int table[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,63, 52, 53, 54, 55, 56, 57, 58,59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0,1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,13, 14, 15, 16, 17, 18, 19, 20, 21,22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26,27, 28, 29, 30, 31, 32, 33, 34, 35,36, 37, 38, 39, 40, 41, 42, 43, 44,45, 46, 47, 48, 49, 50, 51};int len;     //编码后的长度int str_len; //解码后的长度char *res;   //解码后的字符串int i, j;//计算解码后的字符串长度len = strlen((char *)origStr);//判断编码后的字符串后是否有=if (strstr((char *)origStr, "=="))str_len = len / 4 * 3 - 2;else if (strstr((char *)origStr, "="))str_len = len / 4 * 3 - 1;elsestr_len = len / 4 * 3;if (nullptr != resLength){*resLength = str_len;}res = (char *)malloc(sizeof(unsigned char) * str_len + 1);res[str_len] = '\0';//以4个字符为一位进行解码for (i = 0, j = 0; i < len - 2; j += 3, i += 4){res[j] = ((unsigned char)table[origStr[i]]) << 2 | (((unsigned char)table[origStr[i + 1]]) >> 4);      //取出第一个字符对应base64表的十进制数的前6位与第二个字符对应base64表的十进制数的后2位进行组合res[j + 1] = (((unsigned char)table[origStr[i + 1]]) << 4) | (((unsigned char)table[origStr[i + 2]]) >> 2); //取出第二个字符对应base64表的十进制数的后4位与第三个字符对应bas464表的十进制数的后4位进行组合res[j + 2] = (((unsigned char)table[origStr[i + 2]]) << 6) | ((unsigned char)table[origStr[i + 3]]);        //取出第三个字符对应base64表的十进制数的后2位与第4个字符进行组合}return res;
}Smtp::Smtp()
{this->_content = "";this->_port = 25;this->_user = "";this->_password = "";this->_title = "";this->_domain = "";#ifdef _MSC_VERWORD wVersionRequested;WSADATA wsaData;int err;wVersionRequested = MAKEWORD(2, 1);err = WSAStartup(wVersionRequested, &wsaData);
#endifthis->_sockClient = 0;
}Smtp::~Smtp()
{#ifdef _MSC_VERclosesocket(sockClient);WSACleanup();
#elseclose(_sockClient);
#endif
}Smtp::Smtp(int port,string srvDomain,string userName,string password,string targetEmail,string emailTitle,string content)
{this->_content = content;this->_port = port;this->_user = userName;this->_password = password;this->_targetAddrs.emplace_back(targetEmail);this->_title = emailTitle;this->_domain = srvDomain;#ifdef _MSC_VERWORD wVersionRequested;WSADATA wsaData;int err;wVersionRequested = MAKEWORD(2, 1);err = WSAStartup(wVersionRequested, &wsaData);
#endifthis->_sockClient = 0;
}bool Smtp::_createConn()
{int skClientTemp = (int)socket(AF_INET, SOCK_STREAM, 0);sockaddr_in saddr;hostent *pHostent;pHostent = gethostbyname(_domain.c_str());saddr.sin_addr.s_addr = *((unsigned long *)pHostent->h_addr_list[0]);saddr.sin_family = AF_INET;saddr.sin_port = htons(_port);int err = connect(skClientTemp, (sockaddr *)&saddr, sizeof(saddr));if (err != 0)return false;this->_sockClient = (int)skClientTemp;if (false == _recv())return false;return true;
}bool Smtp::_send(string &strMessage)
{int err = (int)send(_sockClient, strMessage.c_str(), (int)strMessage.length(), 0);if (err < 0){return false;}// cout << strMessage.c_str() << endl;return true;
}bool Smtp::_recv()
{memset(_buff, 0, sizeof(char) * (MAX_EMAIL_MESSAGE_LEN + 1));int err = recv(_sockClient, _buff, MAX_EMAIL_MESSAGE_LEN, 0);if (err < 0){return false;}_buff[err] = '\0';cout << _buff << endl;return true;
}int Smtp::_login()
{string sendBuff;sendBuff = "EHLO ";sendBuff += _user;sendBuff += "\r\n";if (false == _send(sendBuff) || false == _recv())return 1;sendBuff.empty();sendBuff = "AUTH LOGIN\r\n";if (false == _send(sendBuff) || false == _recv())return 1;sendBuff.empty();int pos = (int)_user.find('@', 0);sendBuff = _user.substr(0, pos);char *ecode;ecode = base64Encode(sendBuff.c_str(), (unsigned int)strlen(sendBuff.c_str()));sendBuff.empty();sendBuff = ecode;sendBuff += "\r\n";delete[] ecode;if (false == _send(sendBuff) || false == _recv())return 1;sendBuff.empty();ecode = base64Encode(_password.c_str(), (unsigned int)strlen(_password.c_str()));sendBuff = ecode;sendBuff += "\r\n";delete[] ecode;if (false == _send(sendBuff) || false == _recv())return 1;if (NULL != strstr(_buff, "550"))return 2;if (NULL != strstr(_buff, "535"))return 3;return 0;
}bool Smtp::_sendEmailHead()
{string sendBuff;sendBuff = "MAIL FROM: <" + _user + ">\r\n";if (false == _send(sendBuff) || false == _recv())return false;sendBuff.empty();for (string targetAddr : _targetAddrs){sendBuff = "RCPT TO: <" + targetAddr + ">\r\n";if (false == _send(sendBuff) || false == _recv())return false;}sendBuff.empty();sendBuff = "DATA\r\n";if (false == _send(sendBuff) || false == _recv())return false;sendBuff.empty();_formatEmailHead(sendBuff);if (false == _send(sendBuff))return false;return true;
}void Smtp::_formatEmailHead(string &strEmail)
{strEmail = "From: ";strEmail += _user;strEmail += "\r\n";strEmail += "To: ";if (this->_targetAddrs.size() == 1){strEmail += _targetAddrs[0];}else{strEmail += "multi-users";}strEmail += "\r\n";strEmail += "Subject: ";strEmail += _title;strEmail += "\r\n";strEmail += "MIME-Version: 1.0";strEmail += "\r\n";strEmail += "Content-Type: multipart/mixed;boundary=\"qwertyuiop\"";strEmail += "\r\n";strEmail += "\r\n";
}bool Smtp::_sendTextBody()
{string sendBuff;sendBuff = "--qwertyuiop\r\n";sendBuff += "Content-Type: text/plain;";sendBuff += "charset=\"utf8\"\r\n\r\n";sendBuff += _content;sendBuff += "\r\n\r\n";return _send(sendBuff);
}bool Smtp::_sendAccessory(const char *fileName)
{//文件的大小int fileSize;//fileName中包含路径名,name中只有文件名string name;//打开文件FILE *fp = fopen(fileName, "r");if (fp == nullptr)return false;//获取linux系统下文件大小struct stat statbuf;stat(fileName, &statbuf);fileSize = statbuf.st_size;//linux下获取系统文件名name = strrchr(fileName, (int)'/') + 1;#endifstring sendBuff;sendBuff = "--qwertyuiop\r\n";// sendBuff += "Content-Type: application/octet-stream;\r\n";sendBuff += "Content-Type: image/jpeg;\r\n";sendBuff += "name=\"";sendBuff += name;sendBuff += "\"\r\n";sendBuff += "Content-Transfer-Encoding: base64\r\n";sendBuff += "fileName=\"";sendBuff += name;sendBuff += "\"\r\n\r\n";char *fileContent = (char *)malloc(fileSize);fread(fileContent, 1, fileSize, fp);char *encode;encode = base64Encode(fileContent, fileSize);free(fileContent);sendBuff += encode;delete[] encode;//将读入的字符转换为base64编码sendBuff += "\r\n\r\n";return _send(sendBuff);
}bool Smtp::_sendEnd()
{string sendBuff;sendBuff = "--qwertyuiop--";sendBuff += "\r\n.\r\n";if (false == _send(sendBuff) || false == _recv()){return false;}cout << _buff << endl;sendBuff.empty();sendBuff = "QUIT\r\n";return (_send(sendBuff) && _recv());
}void Smtp::addTargetEmail(string targetEmail)
{this->_targetAddrs.emplace_back(targetEmail);
}void Smtp::addAccessory(string fileName)
{this->_accessories.emplace_back(fileName);
}int Smtp::sendEmail()
{if (false == _createConn())return 1;int err = _login();if (err != 0)return err;if (false == _sendEmailHead())return 1;if (false == _sendTextBody())return 1;for (string fileName : this->_accessories){if (false == _sendAccessory(fileName.c_str()))return 1;}if (false == _sendEnd())return 1;return 0;
}

测试main.cpp文件

#include "smtp.h"
#include <iostream>
using namespace std;int main()
{Smtp smtp(25,                                                                    /*smtp端口*/"smtp.163.com",                                                        /*smtp服务器地址*/"yourEmailAddress@163.com",                                                 /*你的邮箱地址*/"password",                                                           /*邮箱密码*/"target@163.com",                                                     /*目的邮箱地址*/"这是邮件标题",                                                              /*主题*/"这是邮件正文."                                                        /*邮件正文*/);//添加第二个收件人smtp.addTargetEmail("wsad@qq.com");smtp.addAccessory("/home/bins/1.png");smtp.addAccessory("/home/bins/2.png");smtp.addAccessory("/home/bins/3.png");smtp.addAccessory("/home/bins/4.png");smtp.addAccessory("/home/bins/5.png");int err;if ((err = smtp.sendEmail()) != 0){if (err == 1)cout << "错误1: 由于网络不畅通,发送失败!" << endl;if (err == 2)cout << "错误2: 用户名错误,请核对!" << endl;if (err == 3)cout << "错误3: 用户密码错误,请核对!" << endl;if (err == 4)cout << "错误4: 请检查附件目录是否正确,以及文件是否存在!" << endl;}return 0;
}

测试结果以及存在问题

我测试时使用的邮箱是网易的163邮箱(假定为A@163.com),收信人的邮箱我测试了两种,一种是163邮箱(假定为B@163.com),另一种是qq邮箱(假定为C@qq.com),详细测试结果如下:
发送 接收 附件 结果
A@163.com B@163.com 成功
A@163.com C@qq.com 成功
A@163.com B@163.com、C@qq.com 成功
A@163.com B@163.com 单个附件 成功
A@163.com B@163.com 多个附件 成功
A@163.com B@163.com 单个附件(857.8kb) smtp服务器返回代码421

存在问题:
当单个附件大小大于800多kb时,sockett通讯会出错,调试时会导致send函数抛异常而暂停。使用网上的经验将send函数中的标识改为MSG_NOSIGNAL时smtp服务器会返回421的返回代码。
希望知道情况的大佬能够指教。

写在最后

第一次写博客,一定有很多问题,既包括语言表达也包括知识上的错误,欢迎各位指教。

Linux 平台使用smtp协议发送邮件相关推荐

  1. Python 使用SMTP协议发送邮件

    引言 问题基于<计算机网络自定向下>第二章的课后套接字编程作业:邮件客户 题目的下载链接:python 压缩包 题目如下: 这个实验结束时,您将能够更好地了解SMTP协议.您还将获得使用P ...

  2. SMTP协议发送邮件

    网上找了一个smtp邮箱的发送工具类,还行,用我自己的qq测试可以发送package com.exampleimport javax.mail.Address; import javax.mail.S ...

  3. smtp协议源ip是服务器地址吗,Smtp协议发送邮件

    Smtp协议发送邮件,是最全面的smtp协议发送邮件教程!有助于新手快速入门,从而跟好的进阶学习.本文档内容丰富,知识全面,简单易懂 Smtp协议发送邮件 MTP(Simple Mail Transf ...

  4. java qq协议 php_PHP使用SMTP协议发送邮件

    PHP开发者一般都是使用PHPMailer发送邮件,不知道你有没有看过它的源码呢?其实并不难,核心在于SMTP协议,下面我分享下如何不依赖PHPMailer来发送邮件. 我选择使用QQ邮箱的SMTP服 ...

  5. php中如何使用smtp,PHP使用SMTP协议发送邮件

    PHP开发者一般都是使用PHPMailer发送邮件,不知道你有没有看过它的源码呢?其实并不难,核心在于SMTP协议,下面我分享下如何不依赖PHPMailer来发送邮件. 我选择使用QQ邮箱的SMTP服 ...

  6. php smtp.qq.com,PHP使用SMTP协议发送邮件

    PHP开发者一般都是使用PHPMailer发送邮件,不知道你有没有看过它的源码呢?其实并不难,核心在于SMTP协议,下面我分享下如何不依赖PHPMailer来发送邮件. 我选择使用QQ邮箱的SMTP服 ...

  7. python怎么发送邮件_python中是如何借助smtp协议发送邮件的?

    前言 现如今邮件已经逐渐替代了书信传递,成为日常办公不可缺少的一部分.在Java开发中,我们会利用POP3协议和SMTP协议借助java类来发送邮件,从而我们了解到,要想发送邮件,需要拥有可以发送邮件 ...

  8. 开发第一步之SMTP协议发送邮件,获取手机的详细信息

    目前是测试代码,只有一个页面,运行软件时会自动的获取系统信息,并且发送,开机自启动等. 布局准备把应用的图标让他从下往上旋转变大,应用的名称从左往右移动,应用的版本从右往左移动,现在只是用文字代替,看 ...

  9. python应用系列教程——python使用smtp协议发送邮件:html文本邮件、图片邮件、文件附件邮件

    全栈工程师开发手册 (作者:栾鹏) python教程全解 python使用smtp协议发送电子邮件.包含发送html文本邮件.包含图片附件的邮件,包含其他文件附件的邮件.可设置邮件的收发人,主题,内容 ...

最新文章

  1. 小型音乐播放器插件APlayer.js的简单使用例子
  2. 会看会吃还要会做——自制小摊上的鸡蛋卷饼
  3. Java NIO理解与使用
  4. python自定义函数画图_利用Python绘图和可视化(长文慎入)
  5. linux 内核探测kprobe 初步了解
  6. python将索引升序_程序在Python中按升序删除元素后获取列表的索引
  7. python2.7是什么_python2.7是什么
  8. 99乘法表 (输入一个数,以该数为行数输出乘法表)
  9. diff命令两个服务器文件,LINUX命令diff-文件管理-比较给定的两个文件的不同
  10. Arduino 例程编译错误 error: unknown type name uint_farptr_t did you mean uint_fast8_t
  11. 基于matlab实现数字图像处理之图像复原
  12. 如何安装旧版iOS软件?
  13. 工作表保护密码忘了怎么办?
  14. NX二次开发(C#)-UIStyler-获取UI选择对象
  15. 【R1CS to QAP】
  16. opencv教程CV2模块——图片处理,HSV、色调、亮度调节
  17. 间歇性宏图大志,持续性混吃等死...
  18. mysql分组查询 groud by
  19. 【Pr剪辑】Pr下载链接,基础操作,渲染1080视频,视频导出,音频导出,视频变速和合并
  20. 通过VBA在Excel中添加复选输入框,实现数据验证不能做到的多选

热门文章

  1. 骁龙 821、Exynos 8890、麒麟 960 三款处理器的对比
  2. 华为鸿蒙系统支持旧机型,老机型福利,别用来换菜刀换盆了,老掉牙的麒麟960也能升级鸿蒙...
  3. 我俩的故事(*^_^*)
  4. CSS3梅花三弄特效
  5. 国内大厂应用在移动端 Flutter 框架使用分析
  6. CentOS7和CentOS8 FreeSWITCH 1.10.7 简单图形化界面2--初始化配置
  7. 在面试官面前优雅地种下红黑树
  8. 一秒识破诈骗信息 360手机卫士诈骗鉴定重磅上线
  9. 《炬丰科技-半导体工艺》硅片镍化学沉积的机理
  10. OpenXml操作Word的一些操作总结.无word组件生成word.