首页 > 代码库 > 邮件正文及其附件的发送的C++实现

邮件正文及其附件的发送的C++实现

       这段代码我花了整整一天来编写,假设转载,请注明出处,谢谢!

   前面的一篇文章已经讲了怎样发送邮件正文,原理我就不再叙述了。要了解的同学请到这里查看!

   http://blog.csdn.net/lishuhuakai/article/details/27503503

   网上非常多发送邮件附件的代码都不能用,所以我用心写了一个,直接封装成了一个类,须要的同学能够直接调用这个类来发送邮件,纯c++代码。(在VS2013下測试完美通过!)

   废话不多说。直接上代码!

   Smtp.h

#ifndef __SMTP_H__ //避免反复包括
#define __SMTP_H__

#include <iostream>
#include <list>
#include <WinSock2.h>
using namespace std;

const int MAXLEN = 1024;
const int MAX_FILE_LEN = 6000;

static const char base64Char[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

struct FILEINFO /*用来记录文件的一些信息*/
{
	char fileName[128]; /*文件名*/
	char filePath[256]; /*文件绝对路径*/
};

class CSmtp
{
public:
	CSmtp(void);
	CSmtp(
		int port,
		string srvDomain,	//smtpserver域名
		string userName,	//username
		string password,	//password
		string targetEmail, //目的邮件地址
		string emailTitle,  //主题
		string content       //内容
		);
public:
	~CSmtp(void);
public:
	int port;
public:
	string domain;
	string user;
	string pass;
	string targetAddr;
	string title;
	string content;
	/*为了方便加入文件。删除文件神马的。使用list容器最为方便,相信大家在数据结构里面都学过*/
	list <FILEINFO *> listFile;

public:
	char buff[MAXLEN + 1];
	int buffLen;
	SOCKET sockClient;	//client的套接字
public:
	bool CreateConn(); /*创建连接*/

	bool Send(string &message);
	bool Recv();

	void FormatEmailHead(string &email);//格式化要发送的邮件头部
	int Login();
	bool SendEmailHead();		//发送邮件头部信息
	bool SendTextBody();	    //发送文本信息
	//bool SendAttachment();	    //发送附件
	int SendAttachment_Ex();
	bool SendEnd();
public:
	void AddAttachment(string &filePath); //加入附件
	void DeleteAttachment(string &filePath); //删除附件
	void DeleteAllAttachment(); //删除全部的附件

	void SetSrvDomain(string &domain);
	void SetUserName(string &user);
	void SetPass(string &pass);
	void SetTargetEmail(string &targetAddr);
	void SetEmailTitle(string &title);
	void SetContent(string &content);
	void SetPort(int port);
	int SendEmail_Ex();
	/*关于错误码的说明:1.网络错误导致的错误2.username错误3.password错误4.文件不存在0.成功*/
	char* base64Encode(char const* origSigned, unsigned origLength);
};

#endif // !__SMTP_H__


     Smtp.cpp

#include "Smtp.h"
#include <iostream>
#include <fstream>
using namespace std;

#pragma  comment(lib, "ws2_32.lib")	/*链接ws2_32.lib动态链接库*/

/*base64採用别人的编码,只是,这不是重点,重点是我完毕了我的一个比較好的邮件发送client*/
char* CSmtp::base64Encode(char const* origSigned, unsigned origLength)
{
	unsigned char const* orig = (unsigned char const*)origSigned; // in case any input bytes have the MSB set
	if (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‘

	// Map each full group of 3 input bytes into 4 output base-64 characters:
	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];
	}

	// Now, take padding into account.  (Note: i == numOrig24BitValues)
	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;
}
CSmtp::CSmtp(void)
{
	this->content = "";
	this->port = 25;
	this->user = "";
	this->pass = "";
	this->targetAddr = "";
	this->title = "";
	this->domain = "";

	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 1);
	err = WSAStartup(wVersionRequested, &wsaData);
	this->sockClient = 0;

}

CSmtp::~CSmtp(void)
{
	DeleteAllAttachment();
	closesocket(sockClient);
	WSACleanup();
}


CSmtp::CSmtp(
	int port,
	string srvDomain,
	string userName,
	string password,
	string targetEmail,
	string emailTitle,
	string content
	)
{
	this->content = content;
	this->port = port;
	this->user = userName;
	this->pass = password;
	this->targetAddr = targetEmail;
	this->title = emailTitle;
	this->domain = srvDomain;

	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 1);
	err = WSAStartup(wVersionRequested, &wsaData);
	this->sockClient = 0;
}

bool CSmtp::CreateConn()
{
	//为建立socket对象做准备。初始化环境
	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); //建立socket对象
	SOCKADDR_IN addrSrv;
	HOSTENT* pHostent;
	pHostent = gethostbyname(domain.c_str());  //得到有关于域名的信息

	addrSrv.sin_addr.S_un.S_addr = *((DWORD *)pHostent->h_addr_list[0]);	//得到smtpserver的网络字节序的ip地址   
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(port);
	int err = connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));   //向server发送请求 
	if (err != 0)
	{
		return false;
		//printf("链接失败\n");
	}
	this->sockClient = sockClient;
	if (false == Recv())
	{
		return false;
	}
	return true;
}

bool CSmtp::Send(string &message)
{
	int err = send(sockClient, message.c_str(), message.length(), 0);
	if (err == SOCKET_ERROR)
	{
		return false;
	}
	string message01;
	cout << message.c_str() << endl;
	return true;
}

bool CSmtp::Recv()
{
	memset(buff, 0, sizeof(char)* (MAXLEN + 1));
	int err = recv(sockClient, buff, MAXLEN, 0); //接收数据
	if (err == SOCKET_ERROR)
	{
		return false;
	}
	buff[err] = ‘\0‘;
	cout << buff << endl;
	return true;
}

int CSmtp::Login()
{
	string sendBuff;
	sendBuff = "EHLO ";
	sendBuff += user;
	sendBuff += "\r\n";

	if (false == Send(sendBuff) || false == Recv()) //既接收也发送
	{
		return 1; /*1表示发送失败因为网络错误*/
	}

	sendBuff.empty();
	sendBuff = "AUTH LOGIN\r\n";
	if (false == Send(sendBuff) || false == Recv()) //请求登陆
	{
		return 1; /*1表示发送失败因为网络错误*/
	}

	sendBuff.empty();
	int pos = user.find(‘@‘, 0);
	sendBuff = user.substr(0, pos); //得到username

	char *ecode;
	/*在这里顺带扯一句,关于string类的length函数与C语言中的strlen函数的差别,strlen计算出来的长度,仅仅到‘\0‘字符为止,而string::length()函数实际上返回的是string类中字符数组的大小,你自己能够測试一下。这也是为什么我以下不使用string::length()的原因*/

	ecode = base64Encode(sendBuff.c_str(), strlen(sendBuff.c_str()));
	sendBuff.empty();
	sendBuff = ecode;
	sendBuff += "\r\n";
	delete[]ecode;

	if (false == Send(sendBuff) || false == Recv()) //发送username,并接收server的返回
	{
		return 1; /*错误码1表示发送失败因为网络错误*/
	}

	sendBuff.empty();
	ecode = base64Encode(pass.c_str(), strlen(pass.c_str()));
	sendBuff = ecode;
	sendBuff += "\r\n";
	delete[]ecode;

	if (false == Send(sendBuff) || false == Recv()) //发送用户密码,并接收server的返回
	{
		return 1; /*错误码1表示发送失败因为网络错误*/
	}

	if (NULL != strstr(buff, "550"))
	{
		return 2;/*错误码2表示username错误*/
	}

	if (NULL != strstr(buff, "535")) /*535是认证失败的返回*/
	{
		return 3; /*错误码3表示密码错误*/
	}
	return 0;
}

bool CSmtp::SendEmailHead()		//发送邮件头部信息
{
	string sendBuff;
	sendBuff = "MAIL FROM: <" + user + ">\r\n";
	if (false == Send(sendBuff) || false == Recv())
	{
		return false; /*表示发送失败因为网络错误*/
	}


	sendBuff.empty();
	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))
		//发送完头部之后不必调用接收函数,因为你没有\r\n.\r\n结尾,server觉得你没有发完数据,所以不会返回什么值
	{
		return false; /*表示发送失败因为网络错误*/
	}
	return true;
}

void CSmtp::FormatEmailHead(string &email)
{/*格式化要发送的内容*/
	email = "From: ";
	email += user;
	email += "\r\n";

	email += "To: ";
	email += targetAddr;
	email += "\r\n";

	email += "Subject: ";
	email += title;
	email += "\r\n";

	email += "MIME-Version: 1.0";
	email += "\r\n";

	email += "Content-Type: multipart/mixed;boundary=qwertyuiop";
	email += "\r\n";
	email += "\r\n";
}

bool CSmtp::SendTextBody()  /*发送邮件文本*/
{
	string sendBuff;
	sendBuff = "--qwertyuiop\r\n";
	sendBuff += "Content-Type: text/plain;";
	sendBuff += "charset=\"gb2312\"\r\n\r\n";
	sendBuff += content;
	sendBuff += "\r\n\r\n";
	return Send(sendBuff);
}

int CSmtp::SendAttachment_Ex() /*发送附件*/
{
	for (list<FILEINFO *>::iterator pIter = listFile.begin(); pIter != listFile.end(); pIter++)
	{
		cout << "Attachment is sending ~~~~~" << endl;
		cout << "Please be patient!" << endl;
		string sendBuff;
		sendBuff = "--qwertyuiop\r\n";
		sendBuff += "Content-Type: application/octet-stream;\r\n";
		sendBuff += " name=\"";
		sendBuff += (*pIter)->fileName;
		sendBuff += "\"";
		sendBuff += "\r\n";

		sendBuff += "Content-Transfer-Encoding: base64\r\n";
		sendBuff += "Content-Disposition: attachment;\r\n";
		sendBuff += " filename=\"";
		sendBuff += (*pIter)->fileName;
		sendBuff += "\"";

		sendBuff += "\r\n";
		sendBuff += "\r\n";
		Send(sendBuff);
		ifstream ifs((*pIter)->filePath, ios::in | ios::binary);
		if (false == ifs.is_open())
		{
			return 4; /*错误码4表示文件打开错误*/
		}
		char fileBuff[MAX_FILE_LEN];
		char *chSendBuff;
		memset(fileBuff, 0, sizeof(fileBuff));
		/*文件使用base64加密传送*/
		while (ifs.read(fileBuff, MAX_FILE_LEN))
		{
			//cout << ifs.gcount() << endl;
			chSendBuff = base64Encode(fileBuff, MAX_FILE_LEN);
			chSendBuff[strlen(chSendBuff)] = ‘\r‘;
			chSendBuff[strlen(chSendBuff)] = ‘\n‘;
			send(sockClient, chSendBuff, strlen(chSendBuff), 0);
			delete[]chSendBuff;
		}
		//cout << ifs.gcount() << endl;
		chSendBuff = base64Encode(fileBuff, ifs.gcount());
		chSendBuff[strlen(chSendBuff)] = ‘\r‘;
		chSendBuff[strlen(chSendBuff)] = ‘\n‘;
		int err = send(sockClient, chSendBuff, strlen(chSendBuff), 0);

		if (err != strlen(chSendBuff))
		{
			cout << "文件传送出错!" << endl;
			return 1;
		}
		delete[]chSendBuff;
	}
	return 0;
}

bool CSmtp::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());
}

int CSmtp::SendEmail_Ex()
{
	if (false == CreateConn())
	{
		return 1;
	}
	//Recv();
	int err = Login(); //先登录
	if (err != 0)
	{
		return err; //错误代码必需要返回
	}
	if (false == SendEmailHead()) //发送EMAIL头部信息
	{
		return 1; /*错误码1是因为网络的错误*/
	}
	if (false == SendTextBody())
	{
		return 1; /*错误码1是因为网络的错误*/
	}
	err = SendAttachment_Ex();
	if (err != 0)
	{
		return err;
	}
	if (false == SendEnd())
	{
		return 1; /*错误码1是因为网络的错误*/
	}
	return 0; /*0表示没有出错*/
}

void CSmtp::AddAttachment(string &filePath) //加入附件
{
	FILEINFO *pFile = new FILEINFO;
	strcpy_s(pFile->filePath, filePath.c_str());
	const char *p = filePath.c_str();
	strcpy_s(pFile->fileName, p + filePath.find_last_of("\\") + 1);
	listFile.push_back(pFile);
}

void CSmtp::DeleteAttachment(string &filePath) //删除附件
{
	list<FILEINFO *>::iterator pIter;
	for (pIter = listFile.begin(); pIter != listFile.end(); pIter++)
	{
		if (strcmp((*pIter)->filePath, filePath.c_str()) == 0)
		{
			FILEINFO *p = *pIter;
			listFile.remove(*pIter);
			delete p;
			break;
		}
	}
}

void CSmtp::DeleteAllAttachment() /*删除全部的文件*/
{
	for (list<FILEINFO *>::iterator pIter = listFile.begin(); pIter != listFile.end();)
	{
		FILEINFO *p = *pIter;
		pIter = listFile.erase(pIter);
		delete p;
	}
}

void CSmtp::SetSrvDomain(string &domain)
{
	this->domain = domain;
}

void CSmtp::SetUserName(string &user)
{
	this->user = user;
}

void CSmtp::SetPass(string &pass)
{
	this->pass = pass;
}
void CSmtp::SetTargetEmail(string &targetAddr)
{
	this->targetAddr = targetAddr;
}
void CSmtp::SetEmailTitle(string &title)
{
	this->title = title;
}
void CSmtp::SetContent(string &content)
{
	this->content = content;
}
void CSmtp::SetPort(int port)
{
	this->port = port;
}

   測试代码例如以下:

   main.cpp

#include "Smtp.h"
#include <iostream>
using namespace std;

int main()
{

	CSmtp smtp(
		25,								/*smtpport*/
		"smtp.163.com",					/*smtpserver地址*/
		"it_is_just_a_test@163.com",	/*你的邮箱地址*/
		"XXXXXXX",					/*邮箱password*/
		"it_is_just_a_test@126.com",	/*目的邮箱地址*/
		"好啊!",							/*主题*/
		"XXX同学,你好。收到请回复!"		/*邮件正文*/
		);
	/**
	//加入附件时注意,\一定要写成\\,由于转义字符的缘故
	string filePath("D:\\课程设计报告.doc");
	smtp.AddAttachment(filePath);
	*/
	
	/*还能够调用CSmtp::DeleteAttachment函数删除附件,另一些函数,自己看头文件吧!*/
	//filePath = "C:\\Users\\李懿虎\\Desktop\\sendEmail.cpp";
	//smtp.AddAttachment(filePath);

	int err;
	if ((err = smtp.SendEmail_Ex()) != 0)
	{
		if (err == 1)
			cout << "错误1: 由于网络不畅通,发送失败!" << endl;
		if (err == 2)
			cout << "错误2: username错误,请核对!" << endl;
		if (err == 3)
			cout << "错误3: 用户password错误,请核对!" << endl;
		if (err == 4)
			cout << "错误4: 请检查附件文件夹是否正确,以及文件是否存在!" << endl;
	}
	system("pause");
	return 0;
}

        在VS2005以下,非常有可能会出现带中文的文件夹或者中文名文件打不开的情况。这个时候这么解决:在两个构造函数里面的第一句加上:setlocale(LC_ALL,"Chinese-simplified");这一句话就可以解决!

     在VS2013里面貌似这个程序能够执行。微软这朵奇葩!

不说了!

     请尽量不使用QQ邮箱登陆,由于我试过。貌似连不上。qq邮箱的server返回告诉你要求安全的连接,用SSL什么的。哎。qq也是朵奇葩。

     附上我的一个project:https://github.com/lishuhuakai/Mail


邮件正文及其附件的发送的C++实现