這個程序是筆者近日在實驗郵件發送系統時寫的,原本只想實現功能了事,可也許是程序員的慣常品性所至,幾經完善的結果就成了如今這個樣子了。網上也有不少有關於此的源碼,但這些程序多半是不完整的,或者屬於示例性程序,無法直接拿來使用。一些網絡編程的書也有類似介紹,但又過於復雜了。筆者所寫的這個程序以上述資源作為參考,並保有自身特點:功能齊全,小巧簡潔,取名SMailer也正是出於此意(Simple Mail Sender)。大家可以根據需要加入到自己的系統中去。
程序包括如下功能:
- 支持驗證功能,為可選項
- 支持包括html文本、普通文本在內的混排方式
- 支持按特定優先級發送郵件
- 支持一次發送多個附件,為可選項
- 支持多收件人發送,對於某封郵件,可以選擇一次只向一個人發送,也可以選擇發送給所有人
SMailer的另一個特點是,采用標准c++寫就,並具有良好的程序結構,麻雀雖小五髒俱全,諸類各司其職,共同構成了一個完整的小系統。SMailer中多數類都被定義於SMailer名字空間下,以下是程序關鍵部分的簡要講解:
MimeContent及其子類
針對郵件的正文部分和附件部分,SMailer定義了一個抽象類MimeContent,並聲明了幾個必要的成員函數:
class MimeContent
{
public:
MimeContent(const std::string content = "");
virtual std::string getType() const = 0;
virtual std::string getDisposition() const;
virtual std::string getTransEncoding() const = 0;
virtual std::string& getContent() = 0;
protected:
std::string _content;
};
隨後,SMailer又從MimeContent派生了三個子類:PlainTextContent、TextHtmlContent、AppOctStrmContent,分別代表普通文本的正文、html格式的正文和文件形式的附件。值得注意的是,AppOctStrmContent中記錄的是附件所在的路徑,只有當調用了getContent函數時,才會根據路徑讀取文件內容,隨後進行Base64編碼,這裡會用到FileHelper和Base64Helper兩個輔助類,後面會講到:
std::string& AppOctStrmContent::getContent()
{
// you can add more codes here, such as wrapping lines
// or replacing ''\n'' with ''\r\n'', etc.
MUtils::FileHelper::open(_file_name, _content);
_content = MUtils::Base64Helper::encode(_content);
return _content;
}
MailInfo
類MailInfo封裝了一封郵件所包含的全部信息,包括:發件人姓名地址、收件人姓名地址、主題、正文、附件等等。
class MailInfo
{
public:
MailInfo();
void setSenderName(const std::string name);
void setSenderAddress(const std::string address);
std::string getSenderName() const;
std::string getSenderAddress() const;
void addReceiver(const std::string name, const std::string address);
void setReceiver(const std::string name, const std::string address);
const Receivers& getReceivers() const;
void setPriority(std::string priority);
std::string getPriority() const;
void setSubject(const std::string subject);
std::string getSubject() const;
void addMimeContent(MimeContent* content);
void clearMimeContents();
const MimeContents& getMimeContents() const;
private:
std::string _sender_name;
std::string _sender_address;
Receivers _receivers;
std::string _priority;
std::string _subject;
MimeContents _contents;
};
有兩點說明:
- 由於收件人可能不止一個,所以在MailInfo中使用了一個std::multimap容器來記錄收件人的姓名和地址,上面的Receivers就是std::multimap,其中以姓名為key,地址為value。因為是multimap,所以也就滿足了同名同姓的情況了。
- 正如前面看到的,對於郵件的正文和附件,MailInfo采取了一視同仁的態度,能做到這一點應該要歸功於MimeContent的抽象特性。MailInfo中使用了一個std::vector容器,用來保存MimeContent子類對象的指針,上面的MimeContents就是std::vector<MimeContent*>。另外,函數addMimeContent、clearMimeContents和getMimeContents為外界提供了添加、刪除正文或附件的統一接口。
MailWrapper
MailWrapper對MailInfo做了進一步的包裝,用於對MailInfo的信息進行加工再處理,以適應真正的郵件發送的需要。它內含了一個指向MailInfo類對象的指針。另外,對收件人和正文/附件信息的遍歷,MailWrapper采用了類似Iterator Pattern的方法,但是考慮到僅限於此處用到了遍歷機制並且不想使程序變得過於復雜,於是此處直接將遍歷接口附著於MailWrapper之上了:
class MailWrapper
{
public:
MailWrapper(MailInfo* mail_info);
std::string getSenderAddress();
std::string getHeader();
std::string getEnd();
void traverseReceiver();
bool hasMoreReceiver();
std::string nextReceiverAddress();
void traverseContent();
bool hasMoreContent();
std::string& nextContent();
private:
static const std::string _mailer_name;
static const std::string _boundary;
MailInfo* _mail_info;
Receivers::const_iterator _rcv_itr;
std::string _content;
MimeContents::const_iterator _con_itr;
std::string prepareFrom();
std::string prepareTo();
std::string prepareDate();
std::string prepareName(const std::string raw_name);
};
MailSender
MailSender是真正的郵件發送類,實現了郵件發送的關鍵功能,其public接口甚是簡單,你所要做的就是:
- 構造一個MailSender對象,並傳入SMTP服務器地址以及用於認證的賬號信息(如有必要)
- 構造一個MailInfo對象,設置好相應的郵件信息,將之傳入一個MailWrapper對象
- 調用MailSender的setMail函數,將MailWrapper對象傳入其中
- 調用MailSender的sendMail函數,發送郵件
class MailSender
{
public:
MailSender(const std::string server_name,
const std::string user_name = "",
const std::string user_pwd = "");
~MailSender();
void setMail(MailWrapper* mail);
void sendMail();
private:
//...
};
其他輔助類
SMailer中還有幾個輔助類,分別位於SMailer名字空間和MUtils名字空間下,它們包括:
1、Priority:定義了三種級別的郵件優先級,包括:緊急、普通、緩慢。可以在設置MailInfo時使用它:
class Priority
{
public:
static const std::string important;
static const std::string normal;
static const std::string trivial;
};
2、ErrorMessage:用於為操作失敗提供統一的錯誤描述信息,采用了Singleton Pattern:
class ErrorMessage
{
public:
static ErrorMessage& getInstance();
std::string& request (MailSender::Operaion request_operation);
std::string& response(const std::string expected_response);
private:
std::map _request_errmsg_map;
std::map _respons_errmsg_map;
ErrorMessage();
};
3、MailException:一個異常類,派生自std::exception,程序出錯時會統一拋出該異常,通過調用what函數可以得到說明信息:
class MailException : public std::exception
{
public:
MailException(const std::string message = "")
: _message(message)
{
}
const char *what() const throw ()
{
return _message.c_str();
}
private:
std::string _message;
};
4、FileHelper:一個簡單的提供文件I/O功能的輔助類,在讀取附件內容時被用到。該類位於MUtils名字空間下。
5、WinSockHelper和WinSockException:提供針對WinSock編程所必要的支持功能。此二類位於MUtils名字空間下。
6、Base64Helper:提供Base64的編碼/解碼功能,在對附件和賬號信息進行編碼時需要用到。該類位於MUtils名字空間下。
另外,還有以下幾點說明:
1、關於源文件的組織
實現郵件發送功能的源文件是SMailer.h和SMailer.cpp,位於SMailer目錄下;相關的輔助源文件包括FileHelper.h、WinSockHelper.h、Base64Helper.h、Base64Helper.cpp,它們都位於MUtils目錄下。另外TestSMailer.cpp演示了如何使用SMailer的功能,這是一個命令行形式的應用程序,位於和SMailer、MUtils同級的目錄下。
2、關於GUI
時間的原因,我沒有編寫GUI,不過由於所有功能均已封裝,要將SMailer加入GUI系統中應該是易如反掌的。
3、關於移植性
程序在MSVC6編譯器下運行通過,並在Cygwin-b20下編譯通過(頭文件要做一點小小的改動),由於代碼中采用了S(T)L及BSD風格的socket,所以在其余平台上的移植應該也不會很麻煩。
本文配套源碼