c++ 實現發郵件功能
阿新 • • 發佈:2022-04-13
實現了發郵件功能,根據設定不同可設定qq郵箱傳送,或163郵箱傳送。
原始碼:h檔案
/* **CSendMail標頭檔案 **實現郵件的傳送功能,支援多個使用者接收,支援附件 **program by six_beauty */ #pragma once #include <string> #include <list> #include <map> #include "LogInfo.h" #include <winsock2.h> //型別定義 const int MAX_BUFFER_SIZE = 255; //send和recv的快取buffer的size typedef std::map<std::string, std::string> RECEIVERS; //CSendMail類 class CSendMail { public: CSendMail(); ~CSendMail(); //////////////////////////////////////設定郵件資訊///////////////////////////////////////////////////////////////////////////////// /////////////////////////connent/////////////////////////////////// void setServerName(const std::string server_name); //smtp伺服器地址 void setServerPort(int port); //smtp伺服器埠號 void setUserName(const std::string user_name); //郵箱使用者名稱 void setUserPwd(const std::string user_pwd); //郵箱使用者密碼 /////////////////////////SendMail////////////////////////////////// void setSenderName(const std::string sender_name); //傳送者的名字 void setSenderAddress(const std::string sender_addr); //傳送者的郵箱(mail form:) //郵件接收者 void setReceiver(const std::string name, const std::string address); //先clear再add void addReceiver(const std::string name, const std::string address); //增加郵件接收者,name是收件人名字,mail是地址 void clearReceiver(); //情況郵件接收者 //新增附件 void AddFilePath(std::string szFilePath); //新增附件路徑到附件列表中,一般的smtp伺服器處理附件不超過50MB void DeleteFilePath(std::string szFilePath); //刪除附件路徑,如果有的話 void DeleteAllPath(); //刪除全部附件的路徑 /////////////////////////////////////傳送郵件////////////////////////////////////////////////////////////////////////////////////// //連線 bool Connent(); //郵件傳送 bool SendMail(const std::string mail_title, const std::string send_content); //傳送郵件的函式 private: //功能函式 inline std::string& replace_all(string& str, const string& old_value, const string& new_value); //其實就是CString的Replace std::string GetFileName(std::string&szFilePath); //從附件的路徑中獲取檔名稱 std::string GetFileData(std::string szFilePath); //以字元形式讀入附件內容 std::string Base64Encode(std::string in_str); //把char型別轉換成Base64型別 //獲取時間 std::string prepareDate(); //通訊recv和send的封裝 int sendRequest(const std::string content, bool bout = false); //返回傳送了多少位元組 bool rcvResponse(const std::string expected_response); //返回接收的結果和expected_response是否相同 //工作函式 bool CReateSocket(); //建立socket連線 bool Logon(); //登入郵箱,主要進行發郵件前的準備工作 bool SendHead(); //傳送郵件頭 bool SendTextBody(); //傳送郵件文字正文 bool SendFileBody(); //傳送郵件附件 bool SendEnd(); //傳送郵件結尾 SOCKET _socket; LogInfo m_logInfo; /////////////////////////郵件資訊/////////////////////////////////// /////////////////////////connent/////////////////////////////////// std::string m_ServerName; //smtp伺服器地址 int m_Port; //smtp伺服器埠號 std::string m_UserName; //郵箱使用者 std::string m_UserPwd; //郵箱使用者密 /////////////////////////SendMail////////////////////////////////// std::string m_SenderName; //傳送者的名 std::string m_SenderAddr; //傳送者的郵箱(mail form:) std::string m_MailTitle; //郵件標題(subject) std::string m_TextBody; //郵件正文 RECEIVERS m_Receivers; //郵件接收者(name,email_address) std::list<std::string> m_FilePathList; //附件路徑_list /////////////////////////郵件資訊/////////////////////////////////// };
原始碼:cpp檔案
/* **CSendMail原始檔 **實現郵件的傳送功能,支援多個使用者接收,支援附件 **program by six_beauty */ //#include <afx.h> #include "CSendMail.h" #include "time.h" #include <sstream> #include <fstream> #pragma comment(lib,"WSOCK32") #pragma comment(lib, "ws2_32") const std::string _AppOctStrmContent_encode_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; //類的實現 CSendMail::CSendMail(void) { } CSendMail::~CSendMail(void) { clearReceiver(); DeleteAllPath(); } //連線 bool CSendMail::Connent() { //郵件資訊設定判斷 if (m_ServerName.empty() || m_UserName.empty() || m_UserPwd.empty()) { //m_logInfo.logInfo("Connect 失敗,請先設定郵件登陸資訊!"); return false; } if (!CReateSocket())//建立連線 { //m_logInfo.logInfo("建立連線失敗!"); return false; } if (!Logon())//建立連線 { //m_logInfo.logInfo("登陸失敗!"); return false; } return true; } //傳送郵件的函式送 bool CSendMail::SendMail(const std::string mail_title, const std::string send_content) { //引數賦值 m_MailTitle = mail_title; m_TextBody = send_content; if (m_SenderName.empty() || m_SenderAddr.empty() || m_Receivers.empty()) { //m_logInfo.logInfo("[SendMail]郵件引數設定錯誤,請檢查郵件傳送設定資訊是否完整!"); return false; } if (!SendHead())//傳送郵件頭 { //m_logInfo.logInfo("傳送郵件頭失敗!"); return false; } if (!SendTextBody())//傳送郵件文字部分 { return false; } if (!SendFileBody())//傳送附件 { return false; } if (!SendEnd())//結束郵件,並關閉sock { return false; } return true; } ////////////////////////////////////////////設定郵件資訊///////////////////////////////////////////////////////////////////// void CSendMail::setServerName(const std::string server_name) //smtp伺服器地址 { m_ServerName = server_name; } void CSendMail::setServerPort(int port) { m_Port = port; } void CSendMail::setUserName(const std::string user_name) //郵箱使用者名稱 { m_UserName = user_name; } void CSendMail::setUserPwd(const std::string user_pwd) //郵箱使用者密碼 { m_UserPwd = user_pwd; } void CSendMail::setSenderName(const std::string sender_name) //傳送者的名字 { m_SenderName = sender_name; } void CSendMail::setSenderAddress(const std::string sender_addr) //傳送者的郵箱(mail form:) { m_SenderAddr = sender_addr; } void CSendMail::addReceiver(const std::string name, const std::string address) { m_Receivers.insert(RECEIVERS::value_type(name, address)); } void CSendMail::setReceiver(const std::string name, const std::string address) { m_Receivers.clear(); m_Receivers.insert(RECEIVERS::value_type(name, address)); } void CSendMail::clearReceiver() { m_Receivers.clear(); } void CSendMail::AddFilePath(std::string szFilePath)//新增附件路徑 { for (std::list<std::string>::iterator itrList = m_FilePathList.begin(); itrList != m_FilePathList.end(); ++itrList) { if (itrList->compare(szFilePath) == 0) { //已經存在 return; } } //還未加入 m_FilePathList.push_back(szFilePath); } void CSendMail::DeleteFilePath(std::string szFilePath)//刪除附件路徑 { for (std::list<std::string>::iterator itrList = m_FilePathList.begin(); itrList != m_FilePathList.end();) { if (itrList->compare(szFilePath) == 0) { itrList = m_FilePathList.erase(itrList); } else { itrList++; } } } void CSendMail::DeleteAllPath(void) { m_FilePathList.clear(); } ////////////////////////////////////////////功能函式/////////////////////////////////////////////////////////////////// //實現CString的Replace string& CSendMail::replace_all(string& str, const string& old_value, const string& new_value) { while (true) { string::size_type pos(0); if ((pos = str.find(old_value)) != string::npos) str.replace(pos, old_value.length(), new_value); else break; } return str; } //從附件的路徑中獲取檔名稱 std::string CSendMail::GetFileName(std::string &szFilePath) { replace_all(szFilePath, "/", "\\"); string szFileName = szFilePath.substr(szFilePath.rfind("\\") + 1, szFilePath.length()); return szFileName; } //以字元形式讀入附件內容 std::string CSendMail::GetFileData(std::string szFilePath) { std::string szBuffer; if (szFilePath.empty()) { //m_logInfo.logInfo("[SendFileBody]Error:附件路徑為空!"); return szBuffer; } ifstream ifFile(szFilePath.c_str(), ios::binary | ios::in); if (!ifFile) { //m_logInfo.logInfo("[SendFileBody]Error:開啟附件路徑錯誤!"); return szBuffer; } ifFile.seekg(0, ios::beg); std::ostringstream tmp; tmp << ifFile.rdbuf(); szBuffer = tmp.str(); ifFile.close(); return szBuffer; } //把char型別轉換成Base64型別 std::string CSendMail::Base64Encode(std::string in_str) { std::string out_str; unsigned char c1, c2, c3; int i = 0; int len = in_str.length(); while (i < len) { // read the first byte c1 = in_str[i++]; if (i == len) // pad with "=" { out_str += _AppOctStrmContent_encode_chars[c1 >> 2]; out_str += _AppOctStrmContent_encode_chars[(c1 & 0x3) << 4]; out_str += "=="; break; } // read the second byte c2 = in_str[i++]; if (i == len) // pad with "=" { out_str += _AppOctStrmContent_encode_chars[c1 >> 2]; out_str += _AppOctStrmContent_encode_chars[((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)]; out_str += _AppOctStrmContent_encode_chars[(c2 & 0xF) << 2]; out_str += "="; break; } // read the third byte c3 = in_str[i++]; // convert into four bytes string out_str += _AppOctStrmContent_encode_chars[c1 >> 2]; out_str += _AppOctStrmContent_encode_chars[((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)]; out_str += _AppOctStrmContent_encode_chars[((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)]; out_str += _AppOctStrmContent_encode_chars[c3 & 0x3F]; } return out_str; } int CSendMail::sendRequest(const std::string content, bool bout) { int len_s = send(_socket, content.c_str(), content.length(), 0); if (len_s < 0) { //m_logInfo.logInfo("[ERROR]SEND:%s", content.c_str()); return false; } //輸出資訊 if (bout) { //m_logInfo.logInfo("[INFO]SEND:%s", content.c_str()); } return len_s; } bool CSendMail::rcvResponse(const std::string expected_response) { int recv_bytes = 0; char response_buffer[MAX_BUFFER_SIZE]; if ((recv_bytes = recv(_socket, response_buffer, MAX_BUFFER_SIZE, 0)) < 0) { //m_logInfo.logInfo("[ERROR]RECV:%s", expected_response.c_str()); return false; } //輸出資訊 std::string response(response_buffer, recv_bytes); //m_logInfo.logInfo("[INFO]RECV(%s):%s", expected_response.c_str(), response.c_str()); if (response.substr(0, 3) != expected_response) { return false; } return true; } std::string CSendMail::prepareDate() { char date_string[MAX_BUFFER_SIZE]; time_t seconds; time(&seconds); strftime(date_string, MAX_BUFFER_SIZE, "%a, %d %b %y %H:%M:%S +0800", localtime(&seconds)); // +0800 maybe hard code return date_string; } ////////////////////////////////////////////////工作函式////////////////////////////////////////////////////////////////////// bool CSendMail::CReateSocket() { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { //m_logInfo.logInfo("WSAStartup呼叫失敗!"); return false; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { WSACleanup(); return false; } _socket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (_socket == INVALID_SOCKET) { //m_logInfo.logInfo("socket建立失敗!"); return false; } sockaddr_in servaddr; memset(&servaddr, 0, sizeof(sockaddr_in)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(m_Port);//發郵件一般都是25埠 qq郵箱587 struct hostent *hp = gethostbyname(m_ServerName.c_str());//使用名稱 if (hp == NULL) { DWORD dwErrCode = GetLastError(); return false; } servaddr.sin_addr.s_addr = *(int*)(*hp->h_addr_list); int ret = connect(_socket, (sockaddr*)&servaddr, sizeof(servaddr));//建立連線 if (ret == SOCKET_ERROR) { DWORD dwErr = GetLastError(); return false; } if (!rcvResponse("220")) return false; return true; } bool CSendMail::Logon() { char local_host[MAX_BUFFER_SIZE]; if (gethostname(local_host, MAX_BUFFER_SIZE) != 0) { //m_logInfo.logInfo("Get local host name error!"); return false; } std::string msg; msg = "HELO "; msg += std::string(local_host) + "\r\n"; sendRequest(msg); if (!rcvResponse("250")) { return false; } msg = "AUTH LOGIN\r\n"; sendRequest(msg); if (!rcvResponse("334")) { return false; } msg = Base64Encode(m_UserName) + "\r\n"; sendRequest(msg); if (!rcvResponse("334")) { return false; } msg = Base64Encode(m_UserPwd) + "\r\n"; sendRequest(msg); if (!rcvResponse("235")) { return false; } return true;//登入成功 } ///////////////////////////////////SendMail//////////////////////////////////////////////////// //傳送郵件頭 bool CSendMail::SendHead() { std::string msg; msg = "MAIL FROM:<"; msg += m_SenderAddr + ">\r\n"; sendRequest(msg); if (!rcvResponse("250")) { //m_logInfo.logInfo("郵件地址錯誤:%s", m_SenderAddr.c_str()); return false; } //遍歷獲得receiver for (RECEIVERS::iterator itrRec = m_Receivers.begin(); itrRec != m_Receivers.end(); itrRec++) { msg = "RCPT TO: <"; msg += itrRec->second + ">\r\n"; sendRequest(msg); if (!rcvResponse("250")) { return false; } } msg = "DATA\r\n"; sendRequest(msg); if (!rcvResponse("354")) { return false; } //傳送Headers msg = "From:\"" + m_SenderName + "\"<" + m_SenderAddr + ">\r\n"; //遍歷receiver msg += "To: "; for (RECEIVERS::iterator itrRec = m_Receivers.begin(); itrRec != m_Receivers.end(); itrRec++) { std::string szRecv; szRecv = "\"" + itrRec->first + "\"<" + itrRec->second + ">, "; msg += szRecv; } msg += "\r\n"; msg += "Date: "; msg += prepareDate() + "\r\n"; msg += "Subject: "; msg += m_MailTitle + "\r\n"; msg += "X-Mailer: six_beauty \r\n"; msg += "MIME-Version: 1.0\r\n"; msg += "Content-type: multipart/mixed; boundary=\"INVT\"\r\n\r\n"; msg += "\r\n"; sendRequest(msg); return true; } bool CSendMail::SendTextBody() { std::string msg; msg = "--INVT\r\nContent-Type: text/plain;\r\n charset=\"gb2312\"\r\n\r\n"; msg += m_TextBody; msg += "\r\n\r\n"; int len_s = sendRequest(msg, true); if (len_s != msg.length()) { //m_logInfo.logInfo("傳送郵件正文出錯,應該傳送長度(%d):實際傳送長度(%d)", msg.length(), len_s); return false; } return true; } bool CSendMail::SendFileBody() { std::string msg; //遍歷傳送附件檔案 for (std::list<std::string>::iterator itrList = m_FilePathList.begin(); itrList != m_FilePathList.end(); itrList++) { std::string filePath = *itrList; std::string fileName = GetFileName(filePath); std::string szContent = GetFileData(filePath); msg = "--INVT\r\nContent-Type: application/octet-stream;\r\n name=\""; msg += fileName; msg += "\"\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment;\r\n filename=\""; msg += fileName; msg += "\"\r\n\r\n"; sendRequest(msg, true); int npos = 0, len = szContent.length(); while (npos < len) { std::string szBuffer = Base64Encode(szContent.substr(npos, min(len - npos, 3000))); szBuffer += "\r\n"; sendRequest(szBuffer); npos += min(len - npos, 3000); } } return true; } bool CSendMail::SendEnd() { std::string msg; msg = "--INVT--\r\n.\r\n"; sendRequest(msg, true); msg = "QUIT\r\n"; sendRequest(msg, true); closesocket(_socket); WSACleanup(); return true; }
原始碼:輸出(h檔案,功能被註釋,沒來得及刪掉,需要加進去)
#include<iostream> #include<stdarg.h> using namespace std; const int BUF_SIZE=4096; //實現輸出類 class LogInfo { public: LogInfo(){}; ~LogInfo(){}; void logInfo(char *szFormat,...) { char szBuf[BUF_SIZE]={}; va_list args; //第一步 va_start(args,szFormat); //第二步 _vsnprintf(szBuf,BUF_SIZE,szFormat,args); //第三步 va_end(args); //第四步 //在這是實現輸出方式 std::cout<<szBuf<<endl; return ; } };
原始碼:測試程式碼,這幾個引數自己賦值即可
CSendMail sMailer; //郵箱smtp,如"smtp.126.com" sMailer.setServerName(c_serverName); //郵箱埠號,163埠25,qq埠587 sMailer.setServerPort(serverPort); //郵箱賬號名,如"****@126.com" sMailer.setUserName(c_userName); //郵箱密碼:163郵箱為網易授權密碼,不是郵箱賬號對應的密碼 類似DGJNQYDJXTULMGLS sMailer.setUserPwd(c_userPwd); //發件人名字 sMailer.setSenderName("sender"); //傳送郵箱地址,填你賬號的地址,上面的郵箱賬號名"****@163.com",即填賬號名 sMailer.setSenderAddress(c_userName); //新增郵件接收者,可新增多個 sMailer.setReceiver("receiver", c_receiverAddress); //新增附件 //sMailer.AddFilePath("F:\\mailfile\\out.txt"); //傳送郵件 if (sMailer.Connent())//每次發郵件前都需要connect { //第一個字串是郵件標題,第二個是郵件內容 if (sMailer.SendMail(c_head, c_content)) { //郵件傳送完成! } }
參考了https://www.cnblogs.com/sixbeauty/p/3983525.html
一開始用的163郵箱,試了多次,就發出去兩次(163往qq郵箱發),雖然每次都成功。後來換成qq,每次都能發過去(qq往163郵箱發)。不知道是不是網易郵箱有什麼限制。