SMTP功能在clsSMTP.h和clsSMTP.cpp中完成,但clsSMTP.cpp需要额外几个模块支持:
FlexibleArray.h,FlexibleArray.cpp:柔性数组的简单操作(本文不提供代码)
BASE64.h,BASE64.cpp:BASE64编码及解码模块(本人其他文章中有完整代码)
String.h,String.cpp:字符串基本操作函数(每个函数都有ANSI和UNCIDE两个版本,可以使用编译器提供的函数进行替换,不提供)
使用class的核心原因是为了方便调用,同时避免重复性的操作。
使用柔性数组是为了避免使用链表去存储浮动性的数据,柔性数组的操作方式相对于链表要简单一些且对于内存占用更加友好,可以使用单向链表进行替换。
//<h-begin>和<h-end>之间是clsSMTP.h文件的完整内容;
//<cpp-begin>和<cpp-end>之间是clsSMTP.cpp文件的完整内容;
//代码已经测试并通过了。
//因为本人当前需求上面不需要附件,所以没有去实现上传附件的代码,有需求的可以自行添加。
//可以直接复制出来并使用,核心部分在clsSMTP::Send()函数中,这里面是完整的业务逻辑。其他函数仅是管理数据的。
//<h-begin>clsSMTP,v1.0.001
//SMTP协议客户端模块
//updated:2024-05-07
//
//#include "clsSMTP.h"
#pragma once
//for vc++ 6.0
#ifndef wchar_t
typedef unsigned short wchar_t;
#endif
class clsSMTP
{
public:
clsSMTP();
~clsSMTP();
bool SetSmtpW/*********/(const wchar_t * pstrAddressOfSMTP);
bool SetSmtpA/*********/(const char * pstrAddressOfSMTP);
bool Send/*************/(const char * pstrNameOfDomain, const char * pstrUsername, const char * pstrPassword, int iAuthMode);
bool SetSender/********/(const char * pstrCharset, const void * pvNameOfSender, unsigned int uiLen);
bool SetSubject/*******/(const char * pstrCharset, const void * pvSubject, unsigned int uiLen);
bool SetMessage/*******/(const char * pstrCharset, const void * pvMessage, unsigned int uiLen);
bool SetRecipient/*****/(const char * pstrEmailOfRecipient);
bool AddRecipient/*****/(const char * pstrEmailOfRecipient);
bool DelRecipient/*****/(const char * pstrEmailOfRecipient);
bool CleanRecipient/***/(void);
bool SetCarbonCopy/****/(const char * pstrEmailOfCC);
bool AddCarbonCopy/****/(const char * pstrEmailOfCC);
bool DelCarbonCopy/****/(const char * pstrEmailOfCC);
bool CleanCarbonCopy/**/(void);
//bool AddAttachment/****/(const wchar_t * pstrNameOfAttachment, const void * pvData, unsigned int uiLen);
//bool DelAttachment/****/(const wchar_t * pstrNameOfAttachment);
//bool CleanAttachment/**/(void);
unsigned long GetReport/********/(char * pstrReport);
private:
//SMTP服务器信息
char cch_AddressOfSmtp[32];//-----SMTP服务器的地址(尺寸需要可以容纳SOCKADDR结构体)
//缓冲区
char cstr_Recv[8192];//-----------接收缓冲区(接收SMTP服务端应答文本)
char cstr_Send[8192];//-----------发送缓冲区(自动化发送专用)
char cstr_Temp[1024];//-----------临时数据
char cstr_Proof[512];//-----------登录凭证临时存储区
unsigned int cui_LenOfSendUsed;//---------发送缓冲区已使用掉的字节量
//存储的数据
char * cpstr_EmailOfSender;//-------发件人的email地址;
char * cpstr_NameOfSender;//--------(在邮件内容中显示的)发件人名称(非必要);
char * cpv_Subject;//---------------邮件主题(编码后的内容);
char * cpv_Message;//---------------邮件正文内容(编码后的内容);
char * cpch_ArrayOfRecipientList;//-收件人列表(柔性数组);
char * cpch_ArrayOfCarbonCopyList;//抄送列表(柔性数组);
char * cpch_ArrayOfAyyachment;//----附件列表(柔性数组);
unsigned int cui_SizeOfRecipientList;//---收件人列表(柔性数组)的大小;
unsigned int cui_SizeOfCarbonCopyList;//--抄送列表(柔性数组)的大小;
unsigned int cui_SizeOfAyyachment;//------附件列表(柔性数组)的大小
};
//<h-end>
//<cpp-begin>clsSMTP,v1.0.001
//SMTP协议客户端模块
//updated:2024-05-07
//
#pragma warning (disable:4996)
#include "clsSMTP.h"
#include <winsock2.h>//--------------Windows提供的网络传输模块
#pragma comment (lib, "ws2_32.lib")//ws2_32.dll
#include "FlexibleArray.h"//---------柔性数组基本操作模块(自编写)
#include "String.h"//----------------字符串基本操作模块(自编写)
#include "BASE64.h"//----------------BASE64编码及解码模块(自编写)
#ifndef NULL
#define NULL 0
#endif
#define CHARSET_DEFAULT "UTF-8" /*默认字符集名称*/
#define SIZEOFBLOCK 4096 /*柔性数组每一次步进的字节量*/
#define LEN_CHARSET 16 /*字符集名称最大长度*/
//----------------------------------------------------------------------------------------------------assigt function-begin
//SOCKET发送和接收封装函数
static unsigned int socket2_put(SOCKET socket, char * pvSend, unsigned int uiSizeOfSend, unsigned int * puiPosOfSend, const char * pvData, unsigned int uiLenOfData);
static unsigned int socket2_pop(SOCKET socket, unsigned long ulTimeout, char * pvRecv, unsigned int uiLen);
//----------------------------------------------------------------------------------------------------clsSMTP-begin
//使用goto语句及failed标识,只是为了简化逻辑,便于同种操作类型的函数的代码复用,同样为后期添加功能预留一定空间;
clsSMTP::clsSMTP()
{
Str_SetNullA(cch_AddressOfSmtp, sizeof(cch_AddressOfSmtp));
Str_SetNullA(cstr_Recv, sizeof(cstr_Recv));
Str_SetNullA(cstr_Send, sizeof(cstr_Send));
Str_SetNullA(cstr_Proof, sizeof(cstr_Proof));
Str_SetNullA(cstr_Temp, sizeof(cstr_Temp));
cui_LenOfSendUsed/***********/ = 0;//---发送缓冲区已使用掉的字节量
cpstr_EmailOfSender/*********/ = NULL;//发件人的email地址;
cpstr_NameOfSender/**********/ = NULL;//(在邮件内容中显示的)发件人名称(非必要);
cpv_Subject/*****************/ = NULL;//邮件主题(编码后的内容);
cpv_Message/*****************/ = NULL;//邮件正文内容(编码后的内容);
cpch_ArrayOfRecipientList/***/ = NULL;//收件人列表(柔性数组);
cpch_ArrayOfCarbonCopyList/**/ = NULL;//抄送列表(柔性数组);
cpch_ArrayOfAyyachment/******/ = NULL;//附件列表(柔性数组);
cui_SizeOfRecipientList/*****/ = 0;//---收件人列表(柔性数组)的大小;
cui_SizeOfCarbonCopyList/****/ = 0;//---抄送列表(柔性数组)的大小;
cui_SizeOfAyyachment/********/ = 0;//---附件列表(柔性数组)的大小
}
clsSMTP::~clsSMTP()
{
if (NULL != cpstr_EmailOfSender)//发件人的email地址;
{
delete [] cpstr_EmailOfSender;
cpstr_EmailOfSender = NULL;
}
if (NULL != cpstr_NameOfSender)//(在邮件内容中显示的)发件人名称(非必要);
{
delete [] cpstr_NameOfSender;
cpstr_NameOfSender = NULL;
}
if (NULL != cpv_Subject)//邮件主题;
{
delete [] cpv_Subject;
cpv_Subject = NULL;
}
if (NULL != cpv_Message)//邮件正文内容;
{
delete [] cpv_Message;
cpv_Message = NULL;
}
if (NULL != cpch_ArrayOfRecipientList)//收件人列表(柔性数组);;
{
delete [] cpch_ArrayOfRecipientList;
cpch_ArrayOfRecipientList = NULL;
cui_SizeOfRecipientList = 0;
}
if (NULL != cpch_ArrayOfCarbonCopyList)//抄送列表(柔性数组);
{
delete [] cpch_ArrayOfCarbonCopyList;
cpch_ArrayOfCarbonCopyList = NULL;
cui_SizeOfCarbonCopyList = 0;
}
if (NULL != cpch_ArrayOfAyyachment)//附件列表(柔性数组);
{
//清空所有附件的数据-begin
//
//清空所有附件的数据-end
delete[] cpch_ArrayOfAyyachment;
cpch_ArrayOfAyyachment = NULL;
cui_SizeOfAyyachment = 0;
}
}
bool clsSMTP::SetSmtpA/*********/(const char * pstrAddressOfSMTP)
{
int iLenOfAddr = sizeof(SOCKADDR);//SOCKADDR结构体的字节量
//SMTP服务器地址转换("ip:port"转换成"ip"+"port")
if (NULL == pstrAddressOfSMTP)
{
Str_SetNullA(cch_AddressOfSmtp, sizeof(cch_AddressOfSmtp));
} else
{
if (0 != WSAStringToAddressA((LPSTR)pstrAddressOfSMTP, AF_INET, NULL, (SOCKADDR*)cch_AddressOfSmtp, &iLenOfAddr)) return false;
}
return true;
}
bool clsSMTP::SetSmtpW/*********/(const wchar_t * pstrAddressOfSMTP)
{
int iLenOfAddr = sizeof(SOCKADDR);//SOCKADDR结构体的字节量
//SMTP服务器地址转换("ip:port"转换成"ip"+"port")
if (NULL == pstrAddressOfSMTP)
{
Str_SetNullA(cch_AddressOfSmtp, sizeof(cch_AddressOfSmtp));
} else
{
if (0 == WSAStringToAddressW((LPWSTR)pstrAddressOfSMTP, AF_INET, NULL, (SOCKADDR*)cch_AddressOfSmtp, &iLenOfAddr)) return false;
}
return true;
}
bool clsSMTP::SetSender/********/(const char * pstrCharset, const void * pvNameOfSender, unsigned int uiLen)
{//设置邮件中发件人的显示名
unsigned int uiLenOfCharset = Str_LenA(pstrCharset);
unsigned int uiLenOfEncoded = BASE64_Encoded(NULL, 0, (const char*)pvNameOfSender, uiLen);
char * pstrData = NULL;
if (NULL != pvNameOfSender && 0 == uiLen)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
if (LEN_CHARSET <= uiLenOfCharset || 0 == uiLenOfEncoded)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
if (NULL != pvNameOfSender && 0 != uiLen)
{
pstrData = new char[LEN_CHARSET + uiLenOfEncoded + 1];
if (NULL == pstrData)
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
goto failed;
}
RtlZeroMemory(pstrData, LEN_CHARSET);
if (0 == uiLenOfCharset)
{
RtlCopyMemory(&pstrData[0], CHARSET_DEFAULT, Str_LenA(CHARSET_DEFAULT));
} else
{
RtlCopyMemory(&pstrData[0], pstrCharset, uiLenOfCharset);
}
BASE64_Encoded(&pstrData[LEN_CHARSET], uiLenOfEncoded + 1, (const char*)pvNameOfSender, uiLen);
}
if (NULL != cpstr_NameOfSender)
{
delete[] cpstr_NameOfSender;
cpstr_NameOfSender = NULL;
}
cpstr_NameOfSender = pstrData;
return true;
failed:
if (NULL != pstrData) delete[] pstrData;
return false;
}
bool clsSMTP::SetSubject/*******/(const char * pstrCharset, const void * pvSubject, unsigned int uiLen)
{
unsigned int uiLenOfCharset = Str_LenA(pstrCharset);//如果pstrCharset为NULL,Str_LenA()返回值是0;
unsigned int uiLenOfEncoded = BASE64_Encoded(NULL, 0, (const char*)pvSubject, uiLen);
char * pstrData = NULL;
if (NULL != pvSubject && 0 == uiLen)
{//必须明确主题占用的字节量(如果pvSubject为NULL,则表示只是清空先前的设置)
SetLastError(ERROR_INVALID_PARAMETER);//ERROR_INVALID_PARAMETER:87L,参数错误
return false;
}
if (LEN_CHARSET <= uiLenOfCharset || 0 == uiLenOfEncoded)
{//字符集名称不可以超过上限;主题的明文加密后长度不能为0;
SetLastError(ERROR_INVALID_PARAMETER);//ERROR_INVALID_PARAMETER:87L,参数错误
return false;
}
//准备新的数据
if (NULL != pvSubject && 0 != uiLen)
{
//uiLenOfEncoded为加密后的密文长度,不包含空中止,所以需要额外1个字节
pstrData = new char[LEN_CHARSET + uiLenOfEncoded + 1];
if (NULL == pstrData)
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);//ERROR_NOT_ENOUGH_MEMORY:8L,存储空间不足
goto failed;
}
RtlZeroMemory(pstrData, LEN_CHARSET);
if (0 == uiLenOfCharset)
{
RtlCopyMemory(&pstrData[0], CHARSET_DEFAULT, Str_LenA(CHARSET_DEFAULT));
} else
{
RtlCopyMemory(&pstrData[0], pstrCharset, uiLenOfCharset);
}
BASE64_Encoded(&pstrData[LEN_CHARSET], uiLenOfEncoded + 1, (const char*)pvSubject, uiLen);
}
//清理旧的数据
if (NULL != cpv_Subject)
{
delete[] cpv_Subject;
cpv_Subject = NULL;
}
//记录新的数据
cpv_Subject = pstrData;
return true;
failed:
if (NULL != pstrData) delete[] pstrData;
return false;
}
bool clsSMTP::SetMessage/*******/(const char * pstrCharset, const void * pvMessage, unsigned int uiLen)
{
unsigned int uiLenOfCharset = Str_LenA(pstrCharset);//如果pstrCharset为NULL,Str_LenA()返回值是0;
unsigned int uiLenOfEncoded = BASE64_Encoded(NULL, 0, (const char*)pvMessage, uiLen);
char * pstrData = NULL;
if (NULL != pvMessage && 0 == uiLen)
{//必须明确正文占用的字节量(如果pvMessage为NULL,则表示只是清空先前的设置)
SetLastError(ERROR_INVALID_PARAMETER);//ERROR_INVALID_PARAMETER:87L,参数错误
return false;
}
if (LEN_CHARSET <= uiLenOfCharset || 0 == uiLenOfEncoded)
{//字符集名称不可以超过上限;主题的明文加密后长度不能为0;
SetLastError(ERROR_INVALID_PARAMETER);//ERROR_INVALID_PARAMETER:87L,参数错误
return false;
}
//准备新的数据
if (NULL != pvMessage && 0 != uiLen)
{
//uiLenOfEncoded为加密后的密文长度,不包含空中止,所以需要额外1个字节
pstrData = new char[LEN_CHARSET + uiLenOfEncoded + 1];
if (NULL == pstrData)
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);//ERROR_NOT_ENOUGH_MEMORY:8L,存储空间不足
goto failed;
}
RtlZeroMemory(pstrData, LEN_CHARSET);
if (0 == uiLenOfCharset)
{
RtlCopyMemory(&pstrData[0], CHARSET_DEFAULT, Str_LenA(CHARSET_DEFAULT));
} else
{
RtlCopyMemory(&pstrData[0], pstrCharset, uiLenOfCharset);
}
BASE64_Encoded(&pstrData[LEN_CHARSET], uiLenOfEncoded + 1, (const char*)pvMessage, uiLen);
}
//清理旧的数据
if (NULL != cpv_Message)
{
delete[] cpv_Message;
cpv_Message = NULL;
}
//记录新的数据
cpv_Message = pstrData;
return true;
failed:
if (NULL != pstrData) delete[] pstrData;
return false;
}
bool clsSMTP::SetRecipient/*****/(const char * pstrEmailOfRecipient)
{
unsigned long ulLen = 0, ulSizeOfNew = 0;
char * pchArray = NULL;//柔性数组新的存储空间
//准备新的数据
//Set允许参数为NULL,表示清空之前的所有列表
if (NULL != pstrEmailOfRecipient)
{
ulLen = Str_LenA(pstrEmailOfRecipient) * sizeof(char);//strlen(pstrEmailOfRecipient) * sizeof(char)
if (0 != ulLen)
{
//确定柔性数组合适的尺寸,尺寸对齐到"步进字节量"的整数倍;
ulSizeOfNew = (ulLen + (SIZEOFBLOCK - 1)) / SIZEOFBLOCK * SIZEOFBLOCK;
//假定调用者会把邮箱地址长于2048个宽字节,即便正常情况下不会发生,-_-!!!
pchArray = new char[ulSizeOfNew];
if (NULL == pchArray) goto failed;
RtlZeroMemory(pchArray, ulSizeOfNew);//memset(pchArray, 0, ulSizeOfNew);
}
}
//清理旧的数据
if (NULL != cpch_ArrayOfRecipientList)
{
delete [] cpch_ArrayOfRecipientList;
cpch_ArrayOfRecipientList = NULL;
cui_SizeOfRecipientList = 0;
}
//记录新的数据
cpch_ArrayOfRecipientList = pchArray;
cui_SizeOfRecipientList = ulSizeOfNew;
if (NULL != cpch_ArrayOfRecipientList && NULL != pstrEmailOfRecipient)
{
ulSizeOfNew = FlexibleArray_Add(cpch_ArrayOfRecipientList, cui_SizeOfRecipientList, (void*)pstrEmailOfRecipient, (unsigned int)ulLen + 1);
if (0 == ulSizeOfNew) return false;
if (cui_SizeOfRecipientList < ulSizeOfNew) return false;
}
return true;
failed:
if (NULL != pchArray) delete [] pchArray;
return false;
}
bool clsSMTP::AddRecipient/*****/(const char * pstrEmailOfRecipient)
{
if (NULL == cpch_ArrayOfRecipientList) return false;
unsigned long ulLen = Str_LenA(pstrEmailOfRecipient) * sizeof(char);//strlen(pstrEmailOfRecipient) * sizeof(char);
unsigned long ulSizeOfNew = 0;
char * pchArray = NULL;//柔性数组新的存储空间
//setp frist-尝试向柔性数组中添加新数据
ulSizeOfNew = FlexibleArray_Add(cpch_ArrayOfRecipientList, cui_SizeOfRecipientList, (void*)pstrEmailOfRecipient, ulLen + 1);
if (0 == ulSizeOfNew)
{//发生错误(cpch_ArrayOfRecipientList为NULL;pstrEmailOfRecipient为NULL;ulLen为0;)
//对所有需要的参数都进行了检查;
goto failed;
} else if (cui_SizeOfRecipientList < ulSizeOfNew)
{//添加新数据后柔性数组的字节量已经超过旧柔性数组的存储空间大小时,需要重新分配存储空间;
//新存储空间的尺寸需要对齐到"步进字节量"的整数倍;
ulSizeOfNew = (ulSizeOfNew + (SIZEOFBLOCK - 1)) / SIZEOFBLOCK * SIZEOFBLOCK;
pchArray = new char[ulSizeOfNew];
if (NULL == pchArray) goto failed;
//清空新柔性数组对应的存储空间
RtlZeroMemory(pchArray, ulSizeOfNew);//memset(pchArray, 0, ulSizeOfNew);
//将旧柔性数组的数据复制到新柔性数组的存储空间
RtlCopyMemory(pchArray, cpch_ArrayOfRecipientList, cui_SizeOfRecipientList);//memcpy(pchArray, cpch_ArrayOfRecipientList, cui_SizeOfRecipientList);
//释放旧的柔性数组
delete[] cpch_ArrayOfRecipientList;
//存储新的柔性数组
cpch_ArrayOfRecipientList = pchArray;
cui_SizeOfRecipientList = ulSizeOfNew;
//再次将数据保存到柔性数组(此时一定不会失败)
FlexibleArray_Add(cpch_ArrayOfRecipientList, cui_SizeOfRecipientList, (void*)pstrEmailOfRecipient, ulLen + 1);
} else//ulSizeOfOld >= ulSizeOfNew
{//不需要任何操作
//setp frist的返回值到这儿,实际上已经添加成功了.
//所以只能假装做点什么了.
}
return true;
failed:
if (NULL != pchArray) delete[] pchArray;
return false;
}
bool clsSMTP::DelRecipient/*****/(const char * pstrEmailOfRecipient)
{
if (NULL == cpch_ArrayOfRecipientList) return false;
unsigned long ulLen = Str_LenA(pstrEmailOfRecipient) * sizeof(char);
unsigned int uiIndex = 0;
if (0 == ulLen) return false;
if (false == FlexibleArray_Test(cpch_ArrayOfRecipientList, cui_SizeOfRecipientList, (void*)pstrEmailOfRecipient, ulLen, &uiIndex)) return false;
if (false == FlexibleArray_Del(cpch_ArrayOfRecipientList, cui_SizeOfRecipientList, uiIndex)) return false;//实际上并不会失败
return true;
}
bool clsSMTP::CleanRecipient/***/(void)
{
if (NULL != cpch_ArrayOfRecipientList)
{
delete[] cpch_ArrayOfRecipientList;
cpch_ArrayOfRecipientList = NULL;
cui_SizeOfRecipientList = 0;
}
return true;
}
bool clsSMTP::SetCarbonCopy/****/(const char * pstrEmailOfCC)
{
unsigned long ulLen = 0, ulSizeOfNew = 0;
char * pchArray = NULL;
if (NULL != pstrEmailOfCC)
{
ulLen = Str_LenA(pstrEmailOfCC) * sizeof(char);
if (0 != ulLen)
{
ulSizeOfNew = (ulLen + (SIZEOFBLOCK - 1)) / SIZEOFBLOCK * SIZEOFBLOCK;
pchArray = new char[ulSizeOfNew];
if (NULL == pchArray) goto failed;
RtlZeroMemory(pchArray, ulSizeOfNew);
}
}
if (NULL != cpch_ArrayOfCarbonCopyList)
{
delete[] cpch_ArrayOfCarbonCopyList;
cpch_ArrayOfCarbonCopyList = NULL;
cui_SizeOfCarbonCopyList = 0;
}
cpch_ArrayOfCarbonCopyList = pchArray;
cui_SizeOfCarbonCopyList = ulSizeOfNew;
if (NULL != cpch_ArrayOfCarbonCopyList && NULL != pstrEmailOfCC)
{
ulSizeOfNew = FlexibleArray_Add(cpch_ArrayOfCarbonCopyList, cui_SizeOfCarbonCopyList, (void*)pstrEmailOfCC, (unsigned int)ulLen + 1);
if (0 == ulSizeOfNew) return false;
if (cui_SizeOfCarbonCopyList < ulSizeOfNew) return false;
}
return true;
failed:
if (NULL != pchArray) delete[] pchArray;
return false;
}
bool clsSMTP::AddCarbonCopy/****/(const char * pstrEmailOfCC)
{
if (NULL == cpch_ArrayOfCarbonCopyList) return false;
unsigned long ulLen = Str_LenA(pstrEmailOfCC) * sizeof(char);
unsigned long ulSizeOfNew = 0;
char * pchArray = NULL;
ulSizeOfNew = FlexibleArray_Add(cpch_ArrayOfCarbonCopyList, cui_SizeOfCarbonCopyList, (void*)pstrEmailOfCC, ulLen + 1);
if (0 == ulSizeOfNew)
{
goto failed;
} else if (cui_SizeOfCarbonCopyList < ulSizeOfNew)
{
ulSizeOfNew = (ulSizeOfNew + (SIZEOFBLOCK - 1)) / SIZEOFBLOCK * SIZEOFBLOCK;
pchArray = new char[ulSizeOfNew];
if (NULL == pchArray) goto failed;
RtlZeroMemory(pchArray, ulSizeOfNew);
RtlCopyMemory(pchArray, cpch_ArrayOfCarbonCopyList, cui_SizeOfCarbonCopyList);
delete[] cpch_ArrayOfCarbonCopyList;
cpch_ArrayOfCarbonCopyList = pchArray;
cui_SizeOfCarbonCopyList = ulSizeOfNew;
FlexibleArray_Add(cpch_ArrayOfCarbonCopyList, cui_SizeOfCarbonCopyList, (void*)pstrEmailOfCC, ulLen + 1);
} else
{
}
return true;
failed:
if (NULL != pchArray) delete[] pchArray;
return false;
}
bool clsSMTP::DelCarbonCopy/****/(const char * pstrEmailOfCC)
{
if (NULL == cpch_ArrayOfCarbonCopyList) return false;
unsigned long ulLen = Str_LenA(pstrEmailOfCC) * sizeof(char);
unsigned int uiIndex = 0;
if (0 == ulLen) return false;
if (false == FlexibleArray_Test(cpch_ArrayOfCarbonCopyList, cui_SizeOfCarbonCopyList, (void*)pstrEmailOfCC, ulLen, &uiIndex)) return false;
if (false == FlexibleArray_Del(cpch_ArrayOfCarbonCopyList, cui_SizeOfCarbonCopyList, uiIndex)) return false;
return true;
}
bool clsSMTP::CleanCarbonCopy/**/(void)
{
if (NULL != cpch_ArrayOfCarbonCopyList)
{
delete[] cpch_ArrayOfCarbonCopyList;
cpch_ArrayOfCarbonCopyList = NULL;
cui_SizeOfCarbonCopyList = 0;
}
return true;
}
unsigned long clsSMTP::GetReport/********/(char * pstrReport)
{
unsigned long i = sizeof(cstr_Recv);
unsigned long ulLen = 0;
while (0 != cstr_Recv[ulLen] && ulLen < i)
{
ulLen++;
}
if (2 <= ulLen)
{
if ('\r' == cstr_Recv[ulLen - 2] && '\n' == cstr_Recv[ulLen - 1])
{
cstr_Recv[ulLen - 2] = 0;
cstr_Recv[ulLen - 1] = 0;
ulLen -= 2;
}
}
if (NULL == pstrReport) return ulLen;
for (i = 0; i <= ulLen; i++)
{
pstrReport[i] = cstr_Recv[i];
}
return ulLen;
}
bool clsSMTP::Send/*************/(const char * pstrNameOfDomain, const char * pstrUsername, const char * pstrPassword, int iAuthMode)
{
//基本参数检查
if (NULL == pstrUsername || NULL == pstrPassword)
{
SetLastError(ERROR_INVALID_PARAMETER);//ERROR_INVALID_PARAMETER:87L,参数错误
return false;
}
//不强制必须提交账号和密码
switch (iAuthMode)
{
case 0:
iAuthMode = 2;
break;
case 1://"AUTH LOGIN"
case 2://"AUTH PLAIN"
case 3://"AUTH CRAM-MD5"
break;
default:
SetLastError(ERROR_INVALID_PARAMETER);//ERROR_INVALID_PARAMETER:87L,参数错误
return false;
}
//本函数需要的所有临时变量列表
SOCKET socketMe/*********/ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//会话套接字
unsigned int index/************/ = 0;//----------------------------------------访问柔性数组时的索引
unsigned int uiSizeOfRecv/*****/ = sizeof(cstr_Recv);//------------------------接收缓冲区的尺寸
unsigned int uiSizeOfSend/*****/ = sizeof(cstr_Send);//------------------------发送缓冲区的尺寸
unsigned int uiSizeOfProof/****/ = sizeof(cstr_Proof);//-----------------------凭证缓冲区的尺寸
unsigned int uiSizeOfTemp/*****/ = sizeof(cstr_Temp);//------------------------临时数据缓冲区的尺寸
unsigned int uiLenOfUsername/**/ = Str_LenA(pstrUsername);//-------------------参数指明的账号的长度
unsigned int uiLenOfPassword/**/ = Str_LenA(pstrPassword);//-------------------参数指明的密码的长度
unsigned int uiLenOfEncoded/***/ = 0;//----------------------------------------数据编码后的长度
unsigned int uiLenOfSend/******/ = 0;//----------------------------------------需要发送的数据的长度,其他临时性长度;
//函数功能正式开始
if(0 == uiLenOfUsername) goto failed;
if (INVALID_SOCKET == socketMe) goto failed;
//step first:建立与SMTP服务器的连接
if (SOCKET_ERROR == WSAConnect(socketMe, (SOCKADDR*)cch_AddressOfSmtp, sizeof(SOCKADDR), NULL, NULL, NULL, NULL)) goto failed;
//step 1:获取SMTP服务端状态,响应码"220"为"服务就绪"
if (0 == socket2_pop(socketMe, 5000, cstr_Recv, uiSizeOfRecv)) goto failed;//阻塞模式,但限制超时时间为5秒
if (0 != Str_CmpA(cstr_Recv, "220", 3)) goto failed;
//step 2:向发送缓冲区添加"EHLO"命令,及需要的参数;
//-----"EHLO"命令添加到发送缓冲区;
Str_CopyA(cstr_Temp, "EHLO ");
uiLenOfSend = Str_LenA(cstr_Temp);//多一条步骤,只是为了下面一行代码的整洁度.
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//-----"账号所在域名"添加到发送缓冲区;
uiLenOfSend = Str_LenA(pstrNameOfDomain);//pstrNameOfDomain为NULL时,Str_LenA()返回值是0,则不会发生错误;
if (0 == uiLenOfSend)
{//如果没有指明域名,则以账号后缀为准;
Str_SetNullA(cstr_Temp);
if (false == Str_SearchA(pstrUsername, "@", 0, (unsigned long*)&uiLenOfSend)) goto failed;
if(uiLenOfSend >= uiLenOfUsername) goto failed;//防止账号最后一个字符是"@",错误的参数内容
Str_CopyA(cstr_Temp, &pstrUsername[uiLenOfSend]);//复制出域名
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, &pstrUsername[uiLenOfSend], uiLenOfSend);
} else
{//如果指明了域名,则以参数声明的域名为准
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, pstrNameOfDomain, uiLenOfSend);
}
//-----"结束标记"添加到发送缓冲区;
Str_CopyA(cstr_Temp, "\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//-----没有数据需要添加了,将发送缓冲区中的数据发送出去;
if (0 == socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, NULL, 0)) goto failed;
//-----每次执行发送数据的任务后,立即重置发送缓冲区已使用的字节量计数器,表示的是忽略发送未完成的情况;
cui_LenOfSendUsed = 0;
//step 3:接收"EHLO"命令的回复
//-----每次接收SMTP服务器回复时,先清空上一次的回复内容;因为socket2_pop()函数不是以空中止为结束标准;
//-----避免字符串覆盖的问题;如果不需要取出发生错误时的回复,则可以不进行重置操作.
Str_SetNullA(cstr_Recv, uiSizeOfRecv);
if (0 == socket2_pop(socketMe, 5000, cstr_Recv, uiSizeOfRecv)) goto failed;//阻塞模式,但限制超时时间为5000ms.
//-----检查"EHLO"命令的回复
if (0 != Str_CmpA(cstr_Recv, "250", 3)) goto failed;
//step 4:依auth登录方式生成登录凭证,并向服务端发送凭证
switch (iAuthMode)
{
case 1://"AUTH LOGIN":账号和密码分开发送,使用BASE64编码;
{
//s 1:"AUTH LOGIN"命令添加到发送缓冲区;
Str_CopyA(cstr_Temp, "AUTH LOGIN\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//s 2:没有数据需要添加了,将发送缓冲区中的数据发送出去;
if (0 == socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, NULL, 0)) goto failed;
cui_LenOfSendUsed = 0;
//s 3:接收"AUTH LOGIN"命令回复
Str_SetNullA(cstr_Recv, uiSizeOfRecv);
if (0 == socket2_pop(socketMe, 5000, cstr_Recv, uiSizeOfRecv)) goto failed;//阻塞模式,但限制超时时间为5000ms.
//s 4:检查"AUTH LOGIN"命令回复
if (0 != Str_CmpA(cstr_Recv, "334", 3)) goto failed;
//s 5:准备发送账号信息
uiLenOfEncoded = BASE64_Encoded(cstr_Proof, uiSizeOfProof, pstrUsername, uiLenOfUsername);
uiLenOfSend = uiLenOfEncoded + 2;//密文后面需要增加1个换行符
if (uiLenOfSend > uiSizeOfProof) goto failed;//防止越界(主要参数可能存在异常长度的字符串)
//-----补充换行符(一起发送,减少数据包数量)
cstr_Proof[uiLenOfEncoded + 0] = '\r';
cstr_Proof[uiLenOfEncoded + 1] = '\n';
//-----将凭证(编码后的账号信息)添加到发送缓冲区
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Proof, uiLenOfSend);
//-----立即清空凭证缓冲区中的密文信息
Str_SetNullA(cstr_Proof, uiSizeOfProof);
//s 6:将发送缓冲区中的数据全部发送出去
if (0 == socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, NULL, 0)) goto failed;
cui_LenOfSendUsed = 0;
//s 7:发送账号后,接收应答
Str_SetNullA(cstr_Recv, uiSizeOfRecv);
if (0 == socket2_pop(socketMe, 5000, cstr_Recv, uiSizeOfRecv)) goto failed;
//-----检查应答
if (0 != Str_CmpA(cstr_Recv, "334", 3)) goto failed;
//s 8:准备发送密码信息
//-----使用BASE64对密码进行编码
uiLenOfEncoded = BASE64_Encoded(cstr_Proof, uiSizeOfProof, pstrPassword, uiLenOfPassword);
//-----如果pstrPassword是NULL或空文本,则uiLenOfEncoded返回值是0;通常SMTP服务器不允许空密码,但此函数保留了提交空密码的功能;
uiLenOfSend = uiLenOfEncoded + 2;//密文后面需要增加1个换行符
if (uiLenOfSend > uiSizeOfProof) goto failed;//防止数组越界
//-----补充换行符(一起发送,减少数据包数量)
cstr_Proof[uiLenOfEncoded + 0] = '\r';
cstr_Proof[uiLenOfEncoded + 1] = '\n';
//-----将凭证(编码后的密码信息)添加到发送缓冲区
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Proof, uiLenOfSend);
//-----立即清空凭证缓冲区中的密文信息
Str_SetNullA(cstr_Proof, uiSizeOfProof);
//s 6:将发送缓冲区中的数据全部发送出去
if (0 == socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, NULL, 0)) goto failed;
//-----发送密码后,接收应答
Str_SetNullA(cstr_Recv, uiSizeOfRecv);
if (0 == socket2_pop(socketMe, 5000, cstr_Recv, uiSizeOfRecv)) goto failed;//阻塞模式,但限制超时时间为5000ms.
//-----检查应答
if (0 != Str_CmpA(cstr_Recv, "235", 3)) goto failed;
cui_LenOfSendUsed = 0;
}
break;
case 2://"AUTH PLAIN":账号和密码合并发送,使用BASE64编码;
{
//s 1:"AUTH PLAIN"命令添加到发送缓冲区;
Str_CopyA(cstr_Temp, "AUTH PLAIN\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);//多一个步骤,只是为了下面一行代码的整洁度;
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//s 2:没有数据需要添加了,将发送缓冲区中的数据发送出去;
if (0 == socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, NULL, 0)) goto failed;
cui_LenOfSendUsed = 0;
//s 3:接收"AUTH PLAIN"命令回复
Str_SetNullA(cstr_Recv, uiSizeOfRecv);
if (0 == socket2_pop(socketMe, 5000, cstr_Recv, uiSizeOfRecv)) goto failed;//阻塞模式,但限制超时时间为5000ms.
//s 4:检查"AUTH PLAIN"命令回复
if (0 != Str_CmpA(cstr_Recv, "334", 3)) goto failed;
//s 5:准备账号和密码,编码后一起发送
//-----凭证内容为"[\0]+[username]+[\0]+password",对这串字符串进行BASE64编码,然后再补充换行符(换行符的作用是告诉SMTP服务器文本传输完毕);
//-----合并账号和密码到临时缓冲区
Str_SetNullA(cstr_Temp, uiSizeOfTemp);
Str_CopyA(&cstr_Temp[1], pstrUsername);
Str_CopyA(&cstr_Temp[uiLenOfUsername + 2], pstrPassword);
uiLenOfSend = uiLenOfUsername + uiLenOfPassword + 2;
//-----对临时缓冲区中的数据进行编码,输出到凭证缓冲区;
uiLenOfEncoded = BASE64_Encoded(cstr_Proof, uiSizeOfProof, cstr_Temp, uiLenOfSend);
//-----完成编码后,立即清空临时缓冲区中存储的原始账号和密码
Str_SetNullA(cstr_Temp, uiSizeOfTemp);
//-----对编码后的密文补充换行符
uiLenOfSend = uiLenOfEncoded + 2;
if (uiLenOfSend > uiSizeOfProof) goto failed;//防止越界
cstr_Proof[uiLenOfEncoded + 0] = '\r';
cstr_Proof[uiLenOfEncoded + 1] = '\n';
//s 6:将凭证添加到发送缓冲区
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Proof, uiLenOfSend);
//-----立即清空凭证缓冲区中存储的密文信息
Str_SetNullA(cstr_Proof, uiSizeOfProof);
//s 7:没有数据需要添加了,将发送缓冲区中的数据发送出去;
if (0 == socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, NULL, 0)) goto failed;
cui_LenOfSendUsed = 0;
//s 8:接收凭证验证回复
Str_SetNullA(cstr_Recv, uiSizeOfRecv);
if (0 == socket2_pop(socketMe, 5000, cstr_Recv, uiSizeOfRecv)) goto failed;//阻塞模式,但限制超时时间为5000ms.
if (0 != Str_CmpA(cstr_Recv, "235", 3)) goto failed;
cui_LenOfSendUsed = 0;
}
break;
case 3://"AUTH CRAM-MD5":
{//这个是MD5加密(或者说MD5信息摘要算法),手上暂时没有自编写的算法函数
//
cui_LenOfSendUsed = 0;
}
break;
}
//step 5:传输数据,发件人信息
if(true)//这行代码只是为了在开发环境中方便折叠代码段;
{
//-----向发送缓冲区添加"MAIL"命令
Str_CopyA(cstr_Temp, "MAIL FROM:<");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//-----向发送缓冲区添加账号信息
uiLenOfSend = uiLenOfUsername;//只是为了下面一行代码的逻辑相同性
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, pstrUsername, uiLenOfSend);
//-----向发送缓冲区添加结束标记
Str_CopyA(cstr_Proof, ">\r\n\0");
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Proof, Str_LenA(cstr_Proof));
//-----没有更多数据需要添加了,将发送缓冲区中的数据发送出去
if (0 == socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, NULL, 0)) goto failed;
cui_LenOfSendUsed = 0;
//-----接收"MAIL"命令的回复
Str_SetNullA(cstr_Recv, uiSizeOfRecv);
if (0 == socket2_pop(socketMe, 5000, cstr_Recv, uiSizeOfRecv)) goto failed;//阻塞模式,但限制超时时间为5000ms.
//-----检查"MAIL"命令的回复
if (0 != Str_CmpA(cstr_Recv, "250", 3)) goto failed;
}
//step 6:传输数据,收件人信息(允许多个收件人)
if (NULL != cpch_ArrayOfRecipientList && 0 != cui_SizeOfRecipientList)
{//cpch_ArrayOfRecipientList是柔性数组的基地址,cui_SizeOfRecipientList是柔性数组所在内存的空间尺寸;
//设置收件人是重复使用"RCPT"命令来依次添加
//使用"柔性数组"的访问函数遍历每一个成员
index = 0;
while (FlexibleArray_Get(cpch_ArrayOfRecipientList, cui_SizeOfRecipientList, index++, cstr_Proof, uiSizeOfProof))
{
//-----向发送缓冲区添加"RCPT"命令
Str_CopyA(cstr_Temp, "RCPT TO:<");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//-----向发送缓冲区添加收件信邮箱地址
uiLenOfSend = Str_LenA(cstr_Proof);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Proof, uiLenOfSend);
//-----向发送缓冲区添加结束标记
Str_CopyA(cstr_Temp, ">\r\n\0");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//-----没有更多数据需要添加,将发送缓冲区中的数据发送出去
if(0 == socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, NULL, 0)) goto failed;
cui_LenOfSendUsed = 0;
//-----接收"RCPT"命令的回复
Str_SetNullA(cstr_Recv, uiSizeOfRecv);
if (0 == socket2_pop(socketMe, 5000, cstr_Recv, uiSizeOfRecv)) goto failed;//阻塞模式,但限制超时时间为5000ms.
//-----检查"RCPT"命令的回复
if (0 != Str_CmpA(cstr_Recv, "250", 3)) goto failed;
}
}
//step 7:发送邮件主体数据
//step 7-first:进入邮件主体数据传输状态
//-----s 1:将"DATA"命令添加到发送缓冲区;
Str_CopyA(cstr_Temp, "DATA\r\n\0");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//-----s 2:将发送缓冲区中的数据发送出去;
if (0 == socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, NULL, 0)) goto failed;
cui_LenOfSendUsed = 0;
//-----s 3:接收"DATA"命令回复
Str_SetNullA(cstr_Recv, uiSizeOfRecv);
if (0 == socket2_pop(socketMe, 5000, cstr_Recv, uiSizeOfRecv)) goto failed;//阻塞模式,但限制超时时间为5000ms.
//-----s 4:检查"DATA"命令的回复
if (0 != Str_CmpA(cstr_Recv, "354", 3)) goto failed;
//step 7-1:生成"发件人信息"并发送
if (NULL == cpstr_NameOfSender)
{//没有设置发件人的显示名
//格式:[FROM:<]+[发件人邮箱地址]+[>]+[\r\n]
Str_CopyA(cstr_Temp, "FROM:<");//FROM关键词
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//发件人的邮箱地址(即账号)
uiLenOfSend = uiLenOfUsername;
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, pstrUsername, uiLenOfSend);
//结束标记
Str_CopyA(cstr_Temp, ">\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
} else
{//设置了发件人的显示名
//格式:[FROM:<SP>]+[=?CharacterSet?]+[编码方式代码]+[?]+[发件人名称]+[?=<SP><]+[发件人邮箱地址]+[>]+[\r\n]
Str_CopyA(cstr_Temp, "FROM: =?");//FROM关键词
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//字符集标识
uiLenOfSend = Str_LenA(cpstr_NameOfSender);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cpstr_NameOfSender, uiLenOfSend);
//编码方式
Str_CopyA(cstr_Temp, "?B?");//B表示BASE64编码
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//发件人的显示名(BASE64编码后)
uiLenOfSend = Str_LenA(&cpstr_NameOfSender[LEN_CHARSET]);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, &cpstr_NameOfSender[LEN_CHARSET], uiLenOfSend);
//显示名与发件人邮箱的分隔
Str_CopyA(cstr_Temp, "?= <");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//发件人的邮箱地址(即账号)
uiLenOfSend = uiLenOfUsername;
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, pstrUsername, uiLenOfSend);
//结束标记
Str_CopyA(cstr_Temp, ">\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
}
//step 7-2:生成"收件人信息"并发送
if (NULL != cpch_ArrayOfRecipientList && 0 != cui_SizeOfRecipientList)
{//cpch_ArrayOfRecipientList是柔性数组的基地址,cui_SizeOfRecipientList是柔性数组所在内存的空间尺寸;
Str_CopyA(cstr_Temp, "TO:");//TO关键词
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//格式:[TO:]+
// [<SP>=?CharacterSet?]+[编码方式代码]+[?]+[收件人名称]+[?=<SP><]+[收件人邮箱地址]+[>,]+
// [<SP>=?CharacterSet?]+[编码方式代码]+[?]+[收件人名称]+[?=<SP><]+[收件人邮箱地址]+[>,]+
// ...重复上述内容...
// [\r\n]
//或:[TO:]<收件人邮箱地址>,<收件人邮箱地址>,...\r\n
//这里使用第二种格式,即不支持在邮件中显示收件人名称,只显示收件人邮箱地址
//将每一个"收件人"的信息添加到邮件的正文,使用"柔性数组"的访问函数遍历每一个成员
index = 0;
while (FlexibleArray_Get(cpch_ArrayOfRecipientList, cui_SizeOfRecipientList, index++, cstr_Proof, uiSizeOfProof))
{
Str_CopyA(cstr_Temp, "<");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
uiLenOfSend = Str_LenA(cstr_Proof);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Proof, uiLenOfSend);
Str_CopyA(cstr_Temp, ">,");//虽然最后一个收件人会导致多出来一个",",但并不影响实际功能
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
}
//收件人结束标记
Str_CopyA(cstr_Temp, "\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
}
//step 7-3:生成"抄送信息"并发送
if(NULL != cpch_ArrayOfCarbonCopyList && 0 != cui_SizeOfCarbonCopyList)
{//cpch_ArrayOfCarbonCopyList是柔性数组的基地址,cui_SizeOfCarbonCopyList是柔性数组所在内存的空间尺寸;
Str_CopyA(cstr_Temp, "Cc: ");//Cc关键词
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//格式:[CC:]+
// [<SP>=?CharacterSet?]+[编码方式代码]+[?]+[收件人名称]+[?=<SP><]+[收件人邮箱地址]+[>,]+
// [<SP>=?CharacterSet?]+[编码方式代码]+[?]+[收件人名称]+[?=<SP><]+[收件人邮箱地址]+[>,]+
// ...重复上述内容...
// [\r\n]
//或:[CC:]<收件人邮箱地址>,<收件人邮箱地址>,...\r\n
//这里使用第二种格式,即不支持在邮件中显示抄送人名称,只显示抄送人邮箱地址
//将每一个"抄送人"的信息添加到邮件的正文,使用"柔性数组"的访问函数遍历每一个成员
index = 0;
Str_SetNullA(cstr_Proof, uiSizeOfProof);
while (FlexibleArray_Get(cpch_ArrayOfCarbonCopyList, cui_SizeOfCarbonCopyList, index++, cstr_Proof, uiSizeOfProof))
{
Str_CopyA(cstr_Temp, "<");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
uiLenOfSend = Str_LenA(cstr_Proof);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Proof, uiLenOfSend);
Str_CopyA(cstr_Temp, ">,");//虽然最后一个抄送人会导致多出来一个",",但并不影响实际功能
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
}
//结束标记
Str_CopyA(cstr_Recv, "\r\n");
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Recv, Str_LenA(cstr_Recv));
}
//step 7-4:"邮件主题信息"发送
if(NULL != cpv_Subject)
{
//cpv_Subject[]格式是:[字符集说明]+[主题的文本]+[空中止]
//主题的格式:
//[SUBJECT:<SP>]+[=?CharacterSet?]+[编码方式代码]+[?]+[主题文本(编码后)]+[?=\r\n]
//SUBJECT关键词
Str_CopyA(cstr_Temp, "SUBJECT: =?");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//字符集
uiLenOfSend = Str_LenA(cpv_Subject);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cpv_Subject, uiLenOfSend);
//编码方式,B表示BASE64编码
Str_CopyA(cstr_Temp, "?B?");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//邮件主题(编码后)
uiLenOfSend = Str_LenA(&cpv_Subject[LEN_CHARSET]);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, &cpv_Subject[LEN_CHARSET], uiLenOfSend);
//结束标记
Str_CopyA(cstr_Temp, "?=\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
}
//step 7-5:邮件正文发送
if(NULL != cpv_Message)
{
Str_CopyA(cstr_Temp, "MIME-Version: 1.0\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
Str_CopyA(cstr_Temp, "Content-Type:multipart/mixed; boundary=\"----=_NextPart_000_0005.00\"\r\n\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
Str_CopyA(cstr_Temp, "------=_NextPart_000_0005.00\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
Str_CopyA(cstr_Temp, "Content-Type: multipart/alternative; boundary=\"----=_NextPart_000_0005.01\"\r\n\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
Str_CopyA(cstr_Temp, "This is a multipart message in MIME format.\r\n\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
Str_CopyA(cstr_Temp, "------=_NextPart_000_0005.01\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
Str_CopyA(cstr_Temp, "Content-Type: text/plain; charset=\"");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//字符集说明
uiLenOfSend = Str_LenA(cpv_Message);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cpv_Message, uiLenOfSend);
Str_CopyA(cstr_Temp, "\"\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
Str_CopyA(cstr_Temp, "Content-Transfer-Encoding: BASE64\r\n\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
//邮件正文(编码后的内容)
uiLenOfSend = Str_LenA(&cpv_Message[LEN_CHARSET]);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, &cpv_Message[LEN_CHARSET], uiLenOfSend);
Str_CopyA(cstr_Temp, "\r\n\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
Str_CopyA(cstr_Temp, "------=_NextPart_000_0005.01--\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
Str_CopyA(cstr_Temp, "------=_NextPart_000_0005.00--\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
}
//step 7-6:"邮件结束"标记发送
{
Str_CopyA(cstr_Temp, "\r\n.\r\n");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
}
//所有数据准备完毕,发送数据
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, NULL, 0);
cui_LenOfSendUsed = 0;
//接收SMTP服务器回复
Str_SetNullA(cstr_Recv, uiSizeOfRecv);
if (0 == socket2_pop(socketMe, 5000, cstr_Recv, uiSizeOfRecv)) goto failed;//阻塞模式,但限制超时时间为5000ms.
//检查SMTP服务器回复
if (0 != Str_CmpA(cstr_Recv, "250", 3)) goto failed;
//step last:通知SMTP服务端结束会话
Str_CopyA(cstr_Temp, "QUIT\r\n\0");
uiLenOfSend = Str_LenA(cstr_Temp);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, cstr_Temp, uiLenOfSend);
socket2_put(socketMe, cstr_Send, uiSizeOfSend, &cui_LenOfSendUsed, NULL, 0);
cui_LenOfSendUsed = 0;
//可以不接收回复
Str_SetNullA(cstr_Recv, uiSizeOfRecv);
if (0 == socket2_pop(socketMe, 5000, cstr_Recv, uiSizeOfRecv)) goto failed;//阻塞模式,但限制超时时间为5000ms.
closesocket(socketMe);
socketMe = INVALID_SOCKET;
return true;
failed:
cui_LenOfSendUsed = 0;
Str_SetNullA(cstr_Proof, uiSizeOfProof);
if (INVALID_SOCKET != socketMe) closesocket(socketMe);
return false;
}
//----------------------------------------------------------------------------------------------------clsSMTP-end
//----------------------------------------------------------------------------------------------------assigt function-begin
//socket2_put:自动管理发送缓冲区
//socket:要操作的套接字;
//pvSend:指向发送缓冲区的指针[输入,输出];
//uiSizeOfSend:声明发送缓冲区可以容纳的字节量上限[输入];
//puiPosOfSend:指向发送缓冲区当前位置的指针[输入,输出];
//pvData:指向一个数据地址,函数会将此地址中的内容添加到发送缓冲区[输入];
//uiLenOfData:向发送缓冲区添加的数据的长度[输入];
//函数会将想要添加的数据添加到发送缓冲区;函数每次将缓冲区存满时就会立即发送整个缓冲区的数据;要添加的数据可以比发送缓冲区大,函数可以自动完成分段发送;
//函数返回值是发送缓冲区在本次调用时向外发送的字节量总和
unsigned int socket2_put(SOCKET socket, char * pvSend, unsigned int uiSizeOfSend, unsigned int * puiPosOfSend, const char * pvData, unsigned int uiLenOfData)
{
if (NULL == pvSend || 0 == uiSizeOfSend || NULL == puiPosOfSend) return 0;
if (NULL == pvData && 0 != uiLenOfData) return 0;
if (NULL != pvData && 0 == uiLenOfData) return 0;
unsigned int uiLenOfSend/**/ = 0;
unsigned int uiLenOfCopy/**/ = 0;
unsigned int i/************/ = 0;
int iResult/******/ = 0;
//判断是向发送缓冲区添加数据,还是,需要立即将发送缓冲区中的数据发送出去;
if (0 == uiLenOfData)
{//发送缓冲区停止接收数据,并将当前内容全部发送出去
if (0 == *puiPosOfSend) return 0;
iResult = send(socket, (const char*)pvSend, (int)*puiPosOfSend, 0);
if (SOCKET_ERROR == iResult) return 0;
if (*puiPosOfSend == (unsigned int)iResult)
{//"发送成功的字节量"和"发送缓冲中使用的字节量"相等,则说明完全发送成功.
*puiPosOfSend = 0;
return (unsigned int)iResult;
} else//发送不完全
{//将未发送完成的字节前移,以使可以继续发送
for (i = 0; i < (*puiPosOfSend - iResult); i++)
{
pvSend[i] = pvSend[i + iResult];
}
(*puiPosOfSend) -= iResult;
SetLastError(ERROR_NET_WRITE_FAULT);//ERROR_NET_WRITE_FAULT:88,网络上发生写入错误;
return (unsigned int)iResult;
}
} else
{//向发送缓冲区添加数据
while (uiLenOfCopy < uiLenOfData)
{//循环处理,以支持添加的数据量可以超过缓冲区总大小
pvSend[(*puiPosOfSend)++] = pvData[uiLenOfCopy++];
if (*puiPosOfSend == uiSizeOfSend)
{//发送缓冲区已满,需要立即发送
iResult = send(socket, (const char*)pvSend, (int)*puiPosOfSend, 0);
if (SOCKET_ERROR == iResult) return uiLenOfSend;
uiLenOfSend += (unsigned int)iResult;
if (*puiPosOfSend == (unsigned int)iResult)
{//"发送成功的字节量"和"发送缓冲中使用的字节量"相等,则说明完全发送成功.
*puiPosOfSend = 0;
continue;//继续
} else//发送不完全
{//将未发送完成的字节前移,以使可以继续发送
for (i = 0; i < ((*puiPosOfSend) - iResult); i++)
{
pvSend[i] = pvSend[i + iResult];
}
(*puiPosOfSend) -= iResult;
SetLastError(ERROR_NET_WRITE_FAULT);//ERROR_NET_WRITE_FAULT:88,网络上发生写入错误;
return uiLenOfSend;
}
}
}
}
return uiLenOfSend;
}
//socket2_pop:适用于阻塞模式的套接字读取
//socket:要操作的套接字
//ulTimeout:超时时间,单位是毫秒;
//pvRecv:指向接收缓冲区的指针,此参数不可以为NULL;
//uiLen:声明缓冲区可以容纳的字节量,此参数不可以为0;
//函数返回值:0,失败;小于参数uiLen,读取了一定的字节量,但没有达到参数要求;等于uiLen参数,成功;
unsigned int socket2_pop(SOCKET socket, unsigned long ulTimeout, char * pvRecv, unsigned int uiLen)
{//不使用ioctlsocket()函数来检测套接字的可读性,原因是ioctlsocket()函数需要Windows8及以上版本的系统才能使用;但本函数希望可以在Windows 7上也能使用;
fd_set fds/******/ = { 0 };
timeval timeout/**/ = { (long)ulTimeout / 1000, (long)ulTimeout % 1000 };
unsigned int uiPos/****/ = 0;
int iResult/**/ = 0;
//指针必须有效,同时必须指定缓冲区大小,以防止越界;
if (NULL == pvRecv || 0 == uiLen) return 0;
FD_ZERO(&fds);
FD_SET(socket, &fds);
//检测套接字的可读性,首次检测时使用调用者指定的超时时间(若为0值则无限期等待,因为本函数适用于阻塞模式);
iResult = select(0, &fds, NULL, NULL, &timeout);
if (SOCKET_ERROR == iResult) return 0;//发生了错误
if (0 == iResult) return 0;//WSAETIMEDOUT,在等待了指定的时间后套接字依然不可读;
//套接字可读,修改超时时间,以使本函数整体超时时间不会偏离参数限制的超时时间太多
timeout.tv_sec = 0;
timeout.tv_usec = 10;//这个毫秒数不能太短;
//逐字节读取,直到到达参数指定的长度,或,没有更多可读的内容
while (uiPos < uiLen)
{
//套接字可读,取出1字节的数据;
iResult = recv(socket, &pvRecv[uiPos], 1, 0);//适用于阻塞模式
//if (0 == iResult) return uiPos;//连接已经关闭
if (1 != iResult) return uiPos;//发生了错误
//检测套接字的可读性,为读取下一字节做准备工作
iResult = select(0, &fds, NULL, NULL, &timeout);
if (SOCKET_ERROR == iResult) return uiPos;//发生了错误;
if (0 == iResult) return uiPos;
//recv()函数成功接收了1个字节,准备接收下一个字节
uiPos++;
}
return uiPos;
}
//----------------------------------------------------------------------------------------------------assigt function-end
//<cpp-end>
下面的代码是使用方式示例:
#include <iostream>
using namespace std;
#include "socket2.h" //套接字常用操作的二次封装模块
#include <Windows.h>
#include "String.h" //自编写的字符串常用操作模块
#include "clsSMTP.h" //自编写的SMTP客户端模块
#include "FileH.h" //自编写的常用文件操作模块
int main()
{
clsSMTP objSmtp;
HANDLE hFile = NULL;
unsigned long ulLen = 2048;
wchar_t strMessage[1024] = { 0 };
wchar_t strSubject[1024] = { 0 };
char strUrl[64] = { 0 };//SMTP服务器的地址,格式是123.123.123.123:12345
char strUsername[64] = { 0 };//使用的邮箱账号
char strPassword[64] = { 0 };//账号的密码
char * pstrResponse = NULL;//SMTP服务器返回的报告
//可以从配置文件中读取SMTP服务器地址及账号和密码信息.这儿只是测试
Str_CopyA(strUrl, "192.168.127.131:25");
Str_CopyA(strUsername, "wwz@testdomain.com");
Str_CopyA(strPassword, "123456");
//初始化socket所在DLL
socket2_Init();
//设置SMTP服务器的地址
if (false == objSmtp.SetSmtpA(strUrl))
{
cout << "设置SMTP服务器地址失败,原因:" << WSAGetLastError() << endl;
return 0;
}
//在编译器中传入的字符的字符集并不是utf-8,这是导致乱码的直接原因,从文件中读取即可;
//从文件读取主题(以使可以使用中文)
hFile = FileH_OpenExistFileByReadOnlyA("Z:\\subject.txt", false, false, 0);
FileRW_Read(hFile, strSubject, ulLen);//ulLen为2048,假定主题长度不超过2048字节
CloseHandle(hFile);
//从文件读取正文(以使可以使用中文)
hFile = FileH_OpenExistFileByReadOnlyA("Z:\\msg.txt", false, false, 0);
FileRW_Read(hFile, strMessage, ulLen);//ulLen为2048,假定正文长度不超过2048字节
CloseHandle(hFile);
//设置发件人在邮件中的显示名
objSmtp.SetSender("8bit", "Czechoslovakia", 14);
//设置邮件主题
objSmtp.SetSubject("utf-8", strSubject, Str_LenW(strSubject) * sizeof(wchar_t));
//设置邮件正文
objSmtp.SetMessage("utf-8", strMessage, Str_LenW(strMessage) * sizeof(wchar_t));
//设置收件人(初始化)
objSmtp.SetRecipient("qc@testdomain.com");
//添加另一个收件人
objSmtp.AddRecipient("a@testdomain.com");
objSmtp.AddRecipient("b@testdomain.com");
//重复调用AddRecipient()可以继续添加收件人
//抄送列表只会出现在邮件中,而不会真的将邮件发送给抄送人,需要调用者另行发送;
//objSmtp.AddCarbonCopy("c@testdomain.com");
if (objSmtp.Send(NULL, strUsername, strPassword, 1))
{
cout << "邮件发送成功" << endl;
} else
{
cout << "邮件发送失败"<<endl;
ulLen = objSmtp.GetReport(NULL);
cout << "Len=" << ulLen << endl;
if (0 != ulLen)
{
pstrResponse = new char[ulLen + 1];
if (NULL == pstrResponse)
{
cout << "无法查询最后回复内容,失败原因是无法申请内存." << endl;
}else
{
objSmtp.GetReport(pstrResponse);
cout << "最后回复内容:<" << pstrResponse << ">" << endl;
delete[] pstrResponse;
}
}
}
socket2_Uninit();
return 0;
}