一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - C/C++ - C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

2022-02-23 13:55link-初揚 C/C++

libcurl是一個免費開源的網(wǎng)絡(luò)傳輸庫,支持ftp、ftps、tftp,http、https、telnet、ldap、pop3、smtp等多種協(xié)議,接下來讓我們一起來了解吧

libcurl中封裝了支持這些協(xié)議的網(wǎng)絡(luò)通信模塊,支持跨平臺,支持Windows,Unix,Linux等多個操作系統(tǒng)。libcurl提供了一套統(tǒng)一樣式的API接口,我們不用關(guān)注各種協(xié)議下網(wǎng)絡(luò)通信的實現(xiàn)細(xì)節(jié),只需要調(diào)用這些API就能輕松地實現(xiàn)基于這些協(xié)議的數(shù)據(jù)通信。本文將簡單地講述一下使用libcurl實現(xiàn)郵件發(fā)送的相關(guān)細(xì)節(jié)。

 

1、為啥要選擇libcurl庫去實現(xiàn)郵件的發(fā)送

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

如果我們自己去使用socket套接字去編碼,實現(xiàn)連接smtp郵件服務(wù)器,并完成和服務(wù)器的smtp協(xié)議的交互,整個過程走下來會非常地復(fù)雜,特別是要處理網(wǎng)絡(luò)通信過程中的多種異常,整個流程的穩(wěn)定性和健壯性沒有保證。

而libcurl中已經(jīng)實現(xiàn)了smtp協(xié)議的所有流程,我們不需要去關(guān)注協(xié)議的具體實現(xiàn)細(xì)節(jié),我們只要去調(diào)用libcurl的API接口就能實現(xiàn)發(fā)送郵件的功能。libcurl庫的穩(wěn)定性是毋庸置疑的。

我們可以到官網(wǎng)上下載libcurl開源庫最新的源碼,直接使用Visual Studio編譯出要用的dll庫,至于使用Visual Studio如何編譯libcurl代碼,后面我會寫一篇文章去詳細(xì)介紹。

 

2、調(diào)用libcurl庫的API接口實現(xiàn)郵件發(fā)送

先調(diào)用curl_easy_init接口初始化libcurl庫,然后調(diào)用curl_easy_setopt(使用CURLOPT_URL選項)設(shè)置url請求地址,正是通過該url的前綴確定具體使用哪種協(xié)議。比如本例中發(fā)送郵件時需要使用smtp協(xié)議:

char urlBuf[256] = { 0 };
sprintf( urlBuf, "smtp://%s:%s", m_strServerName.c_str(), m_strPort.c_str() );
curl_easy_setopt(curl, CURLOPT_URL, urlBuf); 

設(shè)置url時使用的就是smtp前綴,然后帶上目標(biāo)服務(wù)器的IP和端口。

在使用相關(guān)協(xié)議完成數(shù)據(jù)交互時,可能還要設(shè)置一些其他的信息,比如用戶名和密碼等,都是通過調(diào)用curl_easy_setopt設(shè)置的:

curl_easy_setopt(curl, CURLOPT_USERNAME, m_strUserName.c_str());  
curl_easy_setopt(curl, CURLOPT_PASSWORD, m_strPassword.c_str()); 

要發(fā)送的數(shù)據(jù),則通過CURLOPT_READDATA選項去設(shè)置:

std::stringstream stream;  
stream.str(m_strMessage.c_str());  
stream.flush();

/* We're using a callback function to specify the payload (the headers and 
* body of the message). You could just use the CURLOPT_READDATA option to 
* specify a FILE pointer to read from. */  
curl_easy_setopt(curl, CURLOPT_READFUNCTION, &CSmtpSendMail::payload_source);  
curl_easy_setopt(curl, CURLOPT_READDATA, (void *)&stream);  
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);  

最后調(diào)用curl_easy_perform或者curl_multi_perform接口發(fā)起請求,該接口內(nèi)部將去連接url中指定的服務(wù)器,并完成指定的協(xié)議協(xié)商與交互,并最終完成與服務(wù)器之間的數(shù)據(jù)通信。

調(diào)用libcurl庫發(fā)送郵件的完整代碼如下所示:

CURLcode CSmtpSendMail::SendMail()  
{  
	CreatMessage();  
	bool ret = true;  
	CURL *curl;  
	CURLcode res = CURLE_OK;  
	struct curl_slist *recipients = NULL;  

	curl = curl_easy_init();  
	if (curl) {  
		/* Set username and password */                                        
		curl_easy_setopt(curl, CURLOPT_USERNAME, m_strUserName.c_str());  
		curl_easy_setopt(curl, CURLOPT_PASSWORD, m_strPassword.c_str());  

		char urlBuf[256] = { 0 };
		sprintf( urlBuf, "smtp://%s:%s", m_strServerName.c_str(), m_strPort.c_str() );
		curl_easy_setopt(curl, CURLOPT_URL, urlBuf);  
		/* If you want to connect to a site who isn't using a certificate that is 
		* signed by one of the certs in the CA bundle you have, you can skip the 
		* verification of the server's certificate. This makes the connection 
		* A LOT LESS SECURE. 
		* 
		* If you have a CA cert for the server stored someplace else than in the 
		* default bundle, then the CURLOPT_CAPATH option might come handy for 
		* you. */  
#ifdef SKIP_PEER_VERIFICATION  
		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);  
#endif  

		/* If the site you're connecting to uses a different host name that what 
		* they have mentioned in their server certificate's commonName (or 
		* subjectAltName) fields, libcurl will refuse to connect. You can skip 
		* this check, but this will make the connection less secure. */  
#ifdef SKIP_HOSTNAME_VERIFICATION  
		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);  
#endif  

		/* Note that this option isn't strictly required, omitting it will result 
		* in libcurl sending the MAIL FROM command with empty sender data. All 
		* autoresponses should have an empty reverse-path, and should be directed 
		* to the address in the reverse-path which triggered them. Otherwise, 
		* they could cause an endless loop. See RFC 5321 Section 4.5.5 for more 
		* details. 
		*/  
		//curl_easy_setopt(curl, CURLOPT_MAIL_FROM, FROM);   
		curl_easy_setopt(curl, CURLOPT_MAIL_FROM, m_strSendMail.c_str());  
		/* Add two recipients, in this particular case they correspond to the 
		* To: and Cc: addressees in the header, but they could be any kind of 
		* recipient. */  
		for (size_t i = 0; i < m_vRecvMail.size(); i++) {  

			recipients = curl_slist_append(recipients, m_vRecvMail[i].c_str());  
		}  
		curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);  

		std::stringstream stream;  
		stream.str(m_strMessage.c_str());  
		stream.flush();  
		/* We're using a callback function to specify the payload (the headers and 
		* body of the message). You could just use the CURLOPT_READDATA option to 
		* specify a FILE pointer to read from. */  

		// 注意回調(diào)函數(shù)必須設(shè)置為static
		curl_easy_setopt(curl, CURLOPT_READFUNCTION, &CSmtpSendMail::payload_source);  
		curl_easy_setopt(curl, CURLOPT_READDATA, (void *)&stream);  
		curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);  

		/* Since the traffic will be encrypted, it is very useful to turn on debug 
		* information within libcurl to see what is happening during the 
		* transfer */  
		curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);  

		curl_multi_perform()
		/* Send the message */  
		res = curl_easy_perform(curl);  
		CURLINFO info = CURLINFO_NONE;  
		curl_easy_getinfo(curl, info);  
		/* Check for errors */  

		if (res != CURLE_OK) {  
			fprintf(stderr, "curl_easy_perform() failed: %s\n\n",  
				curl_easy_strerror(res));  

			char achErrInfo[512] = {0};
			sprintf( achErrInfo, "curl_easy_perform() failed, error info: %s\n\n", curl_easy_strerror(res) );
			::MessageBoxA( NULL, achErrInfo, "Tip", MB_OK);
			ret = false;  

			m_strErrDesription = achErrInfo;

			/*				Sleep( 100 );
			res = curl_easy_perform(curl); */ 
		}  
		else
		{
			m_strErrDesription = "";
		}

		/* Free the list of recipients */  
		curl_slist_free_all(recipients);  

		/* Always cleanup */  
		curl_easy_cleanup(curl);  
	}  
	else
	{
		res = CURLE_FAILED_INIT;
		char achErrInfo[512] = {0};
		sprintf( achErrInfo, "curl_easy_init() failed, error info: %s\n\n", curl_easy_strerror(res) );
		m_strErrDesription = achErrInfo;
  }
	return res;  
}  

 

3、構(gòu)造待發(fā)送的郵件內(nèi)容

libcurl負(fù)責(zé)和smtp郵件服務(wù)器建鏈,完成smtp簡單郵件協(xié)議的協(xié)商與交互,但要發(fā)送的郵件內(nèi)容則需要我們自己去根據(jù)協(xié)議的規(guī)范去構(gòu)建。那郵件發(fā)送的內(nèi)容的數(shù)據(jù)格式到底是什么樣子的呢?其實很簡單,找一個支持發(fā)送郵件的軟件,發(fā)送郵件時抓一下包,就能抓出對應(yīng)的格式,比如:

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

按照上面的格式構(gòu)建就可以了,相關(guān)代碼如下:

void CSmtpSendMail::CreatMessage()  
{  
	//m_strMessage = "Date: 13 Nov 2021 12:52:14 +0800";
	m_strMessage = "From: ";  
	m_strMessage += m_strSendMail;  
	m_strMessage += "\r\nReply-To: ";  
	m_strMessage += m_strSendMail;  
	m_strMessage += "\r\nTo: ";  
	for (size_t i = 0; i < m_vRecvMail.size(); i++)  
	{  
		if (i > 0) {  
			m_strMessage += ",";  
		}  
		m_strMessage += m_vRecvMail[i];  
	}  
	m_strMessage += "\r\n";  
	m_strMessage += m_strSubject;  
	m_strMessage += "\r\nX-Mailer: The Bat! (v3.02) Professional";  
	m_strMessage += "\r\nMime-Version: 1.0";  
	m_strMessage += "\r\nContent-Type: multipart/mixed;";  
	m_strMessage += "boundary=\"simple boundary\"";  //__MESSAGE__ID__54yg6f6h6y456345
	//m_strMessage += "\r\nThis is a multi-part message in MIME format.";  
	m_strMessage += "\r\n\r\n--simple boundary";  
	//正文  
	m_strMessage += "\r\nContent-Type: text/html;";  
	m_strMessage += "charset=";  
	//m_strMessage += "\"";  
	m_strMessage += m_strCharset;  
	//m_strMessage += "\"";  
	m_strMessage += "\r\nContent-Transfer-Encoding: 7bit";  
	m_strMessage += "\r\n";  
	m_strMessage += m_strContent;  

	//附件  
	std::string filename = "";  
	std::string filetype = "";  
	for (size_t i = 0; i < m_vAttachMent.size(); i++)  
	{  
		m_strMessage += "\r\n--simple boundary";  
		GetFileName(m_vAttachMent[i], filename);  
		GetFileType(m_vAttachMent[i], filetype);  
		SetContentType(filetype);  
		SetFileName(filename);  

		m_strMessage += "\r\nContent-Type: ";  
		m_strMessage += m_strContentType;  
		m_strMessage += "\tname=";  
		m_strMessage += "\"";  
		m_strMessage += m_strFileName;  
		m_strMessage += "\"";  
		m_strMessage += "\r\nContent-Disposition:attachment;filename=";  
		m_strMessage += "\"";  
		m_strMessage += m_strFileName;  
		m_strMessage += "\"";  
		m_strMessage += "\r\nContent-Transfer-Encoding:base64";   
		m_strMessage += "\r\n\r\n";  


		FILE *pt = NULL;  
		if ((pt = fopen(m_vAttachMent[i].c_str(), "rb")) == NULL) {  

			std::cerr << "打開文件失敗: " << m_vAttachMent[i] <<std::endl;  
			continue;  
		}  
		fseek(pt, 0, SEEK_END);  
		int len = ftell(pt);  
		fseek(pt, 0, SEEK_SET);  
		int rlen = 0;  
		char buf[55];  
		for (size_t i = 0; i < len / 54 + 1; i++)  
		{  
			memset(buf, 0, 55);  
			rlen = fread(buf, sizeof(char), 54, pt);  
			m_strMessage += base64_encode((const unsigned char*)buf, rlen);  
			m_strMessage += "\r\n";  
		}  

		fclose(pt);  
		pt = NULL;  
	}  

	m_strMessage += "\r\n--simple boundary--\r\n";  
}  

 

4、開通163發(fā)送郵件賬號的SMTP服務(wù)

上述代碼處理好后,運行如下的測試程序:

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

在上述界面中輸入163的smtp服務(wù)器地址,使用默認(rèn)的25端口,并填寫發(fā)送郵件地址和發(fā)送郵件的密碼,點擊“發(fā)送測試郵件”按鈕,結(jié)果郵件并沒有發(fā)送成功。

在代碼中添加斷點調(diào)試,發(fā)現(xiàn)curl_easy_perform接口返回的錯誤碼為CURLE_LOGIN_DENIED,如下所示:

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

于是通過CURLE_OK go到錯誤碼定義的頭文件中,去查看CURLE_LOGIN_DENIED錯誤碼的含義:

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

注釋中提示可能是發(fā)送郵件的用戶名或密碼錯誤引起的。用戶名和密碼填寫的應(yīng)該沒問題啊?于是賬號到網(wǎng)頁上登陸一下163郵箱,可以成功登陸的,說明賬號和密碼是沒問題的。那到底是咋回事呢?

后來想到,是不是要到發(fā)送郵件賬號中去開啟一下smtp服務(wù)才可以登陸到163的smtp服務(wù)器上?于是到網(wǎng)頁上登陸,按下列的操作步驟找到開啟當(dāng)前賬號的smtp服務(wù)入口:

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

點擊開啟按鈕,會彈出如下的提示框:

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

點擊繼續(xù)開啟,進入下面的頁面:

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

提示需要掃碼發(fā)送短信進行驗證。于是使用網(wǎng)易郵件大師APP掃描了一下,自動跳轉(zhuǎn)到發(fā)送短信的頁面,發(fā)送驗證短信即可。最后彈出如下的授權(quán)密碼頁面:

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

要將這個授權(quán)密碼記錄下來,登陸smtp服務(wù)器時需要使用這個授權(quán)密碼,而不是賬號的密碼!

于是在測試頁面中輸入授權(quán)碼,郵件就能發(fā)送成功了。

 

5、排查接收的郵件內(nèi)容為空的問題

郵件是能正常發(fā)送出去了,郵件也能正常接收到,但接收到的郵件內(nèi)容是空的:

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

這是啥情況?明明發(fā)送郵件時有設(shè)置郵件內(nèi)容的,為啥收到的郵件內(nèi)容是空的呢?

上述代碼在幾年前測試過,好像沒問題的,難道163郵箱系統(tǒng)升級了,不再兼容老的數(shù)據(jù)格式了?于是想到了海康的視頻監(jiān)控客戶端,該客戶端可以到海康官網(wǎng)上下載,免費使用,其中系統(tǒng)設(shè)置中有個發(fā)送郵件的功能:

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

海康的上述界面中發(fā)送測試郵件是沒問題的,接收到的郵件也是有內(nèi)容的。于是趕緊抓一下海康發(fā)送郵件的數(shù)據(jù)包,以tcp.port==25過濾了一下,抓出海康發(fā)出去的郵件內(nèi)容:

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

又抓取了一下我們軟件發(fā)出去的郵件內(nèi)容如下:

C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解

于是詳細(xì)地對比了海康與我們發(fā)出去的數(shù)據(jù)內(nèi)容,多次嘗試修改我們構(gòu)建郵件數(shù)據(jù)的代碼,比如charset編碼格式、boundry類型等,甚至是否會空行。最后經(jīng)過多次嘗試找到了原因,是在具體的郵件內(nèi)容上面需要人為加上一個空行,我們代碼在構(gòu)造郵件數(shù)據(jù)時沒有加空行,導(dǎo)致接收到的郵件內(nèi)容是空的!

以上就是C++調(diào)用libcurl開源庫實現(xiàn)郵件的發(fā)送功能流程詳解的詳細(xì)內(nèi)容,更多關(guān)于C++ 郵件發(fā)送的資料請關(guān)注服務(wù)器之家其它相關(guān)文章!

原文鏈接:https://blog.csdn.net/chenlycly/article/details/121318616

延伸 · 閱讀

精彩推薦
  • C/C++C語言中炫酷的文件操作實例詳解

    C語言中炫酷的文件操作實例詳解

    內(nèi)存中的數(shù)據(jù)都是暫時的,當(dāng)程序結(jié)束時,它們都將丟失,為了永久性的保存大量的數(shù)據(jù),C語言提供了對文件的操作,這篇文章主要給大家介紹了關(guān)于C語言中文件...

    針眼_6702022-01-24
  • C/C++c++ 單線程實現(xiàn)同時監(jiān)聽多個端口

    c++ 單線程實現(xiàn)同時監(jiān)聽多個端口

    這篇文章主要介紹了c++ 單線程實現(xiàn)同時監(jiān)聽多個端口的方法,幫助大家更好的理解和學(xué)習(xí)使用c++,感興趣的朋友可以了解下...

    源之緣11542021-10-27
  • C/C++詳解c語言中的 strcpy和strncpy字符串函數(shù)使用

    詳解c語言中的 strcpy和strncpy字符串函數(shù)使用

    strcpy 和strcnpy函數(shù)是字符串復(fù)制函數(shù)。接下來通過本文給大家介紹c語言中的strcpy和strncpy字符串函數(shù)使用,感興趣的朋友跟隨小編要求看看吧...

    spring-go5642021-07-02
  • C/C++深入理解goto語句的替代實現(xiàn)方式分析

    深入理解goto語句的替代實現(xiàn)方式分析

    本篇文章是對goto語句的替代實現(xiàn)方式進行了詳細(xì)的分析介紹,需要的朋友參考下...

    C語言教程網(wǎng)7342020-12-03
  • C/C++C/C++經(jīng)典實例之模擬計算器示例代碼

    C/C++經(jīng)典實例之模擬計算器示例代碼

    最近在看到的一個需求,本以為比較簡單,但花了不少時間,所以下面這篇文章主要給大家介紹了關(guān)于C/C++經(jīng)典實例之模擬計算器的相關(guān)資料,文中通過示...

    jia150610152021-06-07
  • C/C++C++之重載 重定義與重寫用法詳解

    C++之重載 重定義與重寫用法詳解

    這篇文章主要介紹了C++之重載 重定義與重寫用法詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下...

    青山的青6062022-01-04
  • C/C++學(xué)習(xí)C++編程的必備軟件

    學(xué)習(xí)C++編程的必備軟件

    本文給大家分享的是作者在學(xué)習(xí)使用C++進行編程的時候所用到的一些常用的軟件,這里推薦給大家...

    謝恩銘10102021-05-08
  • C/C++C語言實現(xiàn)電腦關(guān)機程序

    C語言實現(xiàn)電腦關(guān)機程序

    這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)電腦關(guān)機程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    xiaocaidayong8482021-08-20
主站蜘蛛池模板: 色yeye在线观看 | 国产精品亚洲片在线不卡 | 国产3p在线 | 狠狠干日日操 | 国产精品视频1区 | 日韩欧美在线一区二区三区 | 久久热这里面只有精品 | 欧美日韩高清观看一区二区 | 欧洲老太玩小伙 | 欧美日日操 | 亚洲午夜久久久久国产 | 免费观看二十女人一摸是水 | 成人网址大全 | 成年人免费在线播放 | 91麻豆国产 | 欧洲肥女大肥臀 | 美女扒开胸罩露出奶 | 国产高清露脸学生在线观看 | 久久成人永久免费播放 | 99国产精品| 亚洲 欧美 日韩 国产 视频 | 九九九九在线视频播放 | 男人猛进猛出女人下面视频 | 乌克兰13一14娇小 | 日韩一区二区三区四区五区 | 97导航| 2022超帅男同gayxxx | 日韩一区二区在线视频 | jzzjlzz亚洲乱熟在线播放 | 全黄一级裸片视频免费 | 国产成人亚洲精品乱码在线观看 | 国产在视频线在精品 | 国产精品成人亚洲 | 91热这里只有精品 | 亚洲sss综合天堂久久久 | 国产一区二区不卡 | 99热久久这里只精品国产www | 日日舔 | 99久久国产综合精品1尤物 | 黑帮少爷爱上我第8集最新 荷兰精品女人性hd 和日本免费不卡在线v | 日韩欧美高清视频 |