1. 程式人生 > >郵件正文及其附件的傳送的C++實現

郵件正文及其附件的傳送的C++實現

       這段程式碼我花了整整一天來編寫,如果轉載,請註明出處,謝謝!

   前面的一篇文章已經講了如何傳送郵件正文,原理我就不再敘述了,要了解的同學請到這裡檢視!

   網上很多傳送郵件附件的程式碼都不能用,所以我用心寫了一個,直接封裝成了一個類,需要的同學可以直接呼叫這個類來發送郵件,純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,	//smtp伺服器域名
		string userName,	//使用者名稱
		string 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;	//客戶端的套接字
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.使用者名稱錯誤3.密碼錯誤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採用別人的編碼,不過,這不是重點,重點是我完成了我的一個比較好的郵件傳送客戶端*/
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]);	//得到smtp伺服器的網路位元組序的ip地址   
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(port);
	int err = connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));   //向伺服器傳送請求 
	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); //得到使用者名稱

	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()) //傳送使用者名稱,並接收伺服器的返回
	{
		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()) //傳送使用者密碼,並接收伺服器的返回
	{
		return 1; /*錯誤碼1表示傳送失敗由於網路錯誤*/
	}

	if (NULL != strstr(buff, "550"))
	{
		return 2;/*錯誤碼2表示使用者名稱錯誤*/
	}

	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結尾,伺服器認為你沒有發完資料,所以不會返回什麼值
	{
		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,								/*smtp埠*/
		"smtp.163.com",					/*smtp伺服器地址*/
		"[email protected]",	/*你的郵箱地址*/
		"XXXXXXX",					/*郵箱密碼*/
		"[email protected]",	/*目的郵箱地址*/
		"好啊!",							/*主題*/
		"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: 使用者名稱錯誤,請核對!" << endl;
		if (err == 3)
			cout << "錯誤3: 使用者密碼錯誤,請核對!" << endl;
		if (err == 4)
			cout << "錯誤4: 請檢查附件目錄是否正確,以及檔案是否存在!" << endl;
	}
	system("pause");
	return 0;
}

        在VS2005下面,很有可能會出現帶中文的目錄或者中文名檔案打不開的情況,這個時候這麼解決:在兩個建構函式裡面的第一句加上:setlocale(LC_ALL,"Chinese-simplified");這一句話即可解決!

     在VS2013裡面貌似這個程式可以執行,微軟這朵奇葩!不說了!

     請儘量不使用QQ郵箱登陸,因為我試過,貌似連不上。qq郵箱的伺服器返回告訴你要求安全的連線,用SSL什麼的,哎,qq也是朵奇葩!


相關推薦

郵件正文及其附件傳送C++實現

       這段程式碼我花了整整一天來編寫,如果轉載,請註明出處,謝謝!    前面的一篇文章已經講了如何傳送郵件正文,原理我就不再敘述了,要了解的同學請到這裡檢視!    網上很多傳送郵件附件的程式碼都不能用,所以我用心寫了一個,直接封裝成了一個類,需要的同學可以直接

C#通過POP3收取郵件(正文附件)

使用方法:  獲取第1封郵件  複製程式碼 程式碼如下: Zgke.Net.POP3 _Popt = new Zgke.Net.POP3("192.168.0.1", 110);  DataTable _Mail = _Popt.GetMail("zk", "zk", 1)

各種排序演算法的實現及其比較(c++實現

轉載自: http://lib.csdn.net/article/datastructure/9028     CSDN資料結構與演算法 排序演算法是筆試和麵試中最喜歡考到的內容,今晚花了好幾個小時的時間把之前接觸過的排序演算法都重新實現了一遍。 主要是作為複習用。當

C#實現.Net對郵件進行DKIM簽名和驗證,支援附件傳送郵件簽名後直接投遞到對方伺服器(無需己方郵件伺服器)

專案地址 github.com/xiangyuecn/… 主要支援 對郵件進行DKIM簽名,支援帶附件 對整個郵件內容(.eml檔案)的DKIM簽名進行驗證 對MailMessage、SmtpClient進行了一次封裝,傳送郵件簡單易用,進行DKIM簽名後直接投遞到對方伺服器(無需己方郵件

C# 實現傳送電子郵件以及上傳附件

使用C#傳送電子郵件,使用微軟自帶的類庫,主要有一下類: 1、微軟封裝好的MailMessage類:主要處理髮送郵件的內容(如:收發人地址、標題、主體、圖片等等) 2、微軟封裝好的SmtpClient類:主要處理用smtp方式傳送此郵件的配置

C#實現的自定義郵件傳送類完整例項(支援多人多附件)

本文例項講述了C#實現的自定義郵件傳送類。分享給大家供大家參考,具體如下: /// <summary> /// 傳送郵件類 的摘要說明 /// </summary> class SendMail {

[原始碼和報告分享] C#實現的基於SMTP協議的E-MAIL電子郵件傳送客戶端軟體

利用SMTP和Pop協議從底層開發了這個軟體。SMTP全稱是簡單郵件傳輸協議,它專門用來發送郵件用的。Pop全稱是郵局協議,是專門用於接收郵件的。我主要是負責如何實現傳送郵件功能的。MailSend名稱空間是我整個程式的核心。它包括兩個類。在SmtpMail的類中包含了一個SendMail的方法,它

C#實現郵件傳送的功能

1.實現原理: 微軟封裝好的MailMessage類:主要處理髮送郵件的內容(如:收發人地址、標題、主體、圖片等等) 微軟封裝好的SmtpClient類:主要處理用smtp方式傳送此郵件的配置資訊(如:郵件伺服器、傳送埠號、驗證方式等等) SmtpClient主要進行了三層的封裝:Socket

C#傳送郵件(新增附件)!

using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.T

[php]mail函式傳送郵件(正文+附件+中文)

<?php $from = "[email protected]"; $to = "[email protected], [email protected]"; $subject = "郵件主題"; $subject = "=?UTF-8

C#實現簡單的SmtpClient傳送郵件

 SMTP(Simple Mail Transport Protocol)簡單郵件傳輸協議。在.NET Frameword類庫中提供SmtpClient類(System.Net.Mail),她提供了一個輕型方法來發送SMTP電子郵件資訊。SmtpClient類中的Bcc屬性是

java實現郵件傳送, 抄送及多附件傳送

import java.io.UnsupportedEncodingException; import java.util.Properties; import javax.activation.DataHandler; import javax.ac

python傳送郵件,含有正文附件正文中含有圖片(圖片直接在郵件內容中顯示)

#!/usr/bin/python #coding:utf-8  import smtplib import mimetypes from email.Header import Header  from email.mime.text import MIMEText fr

java實現郵件附件傳送功能

需要引用的pom <dependency> <groupId>com.sun.mail</groupId> <artifactId>javax.mail</art

C#實現郵件傳送

private static void Thread_Send() { try { // 獲取IP, 獲取城市地址 string ip="",

python 傳送郵件 正文中帶圖片 帶附件圖片或附件檔案例子

from email.mime.text import MIMEText from email.mime.image import MIMEImage from email.mime.base import MIMEBase from email.mime.multipa

C#實現SMTP郵件發送程序實例

lin ice 效果 using exceptio length string false ack 通常來說郵件發送功能在網站應用程序中經常會用到,包括大家經常看到的博客,在添加評論後,系統會自動發送郵件通知到我郵箱的,把系統發送郵件的功能整理了下,本文展示了一個客戶端D

C#實現發送郵件的三種方法

thumbnail catch plugins () listbox 幫助 哈希 .text sbo 本文實例講述了C#實現發送郵件的三種方法。分享給大家供大家參考。具體方法分析如下: 一、問題: 最近公司由於一個R&I;項目的需要,用戶要求在購買產品或出貨等

C# 實現郵件代發

完成 spa bsp console -c pos ima 驗證 來看 由於自己很好奇,有一些推廣之類的 郵件,發件人後面,都有一個 由 .... 代發。 所以,查找了一些資料,來驗證了一下實現方法。   咱們先來看看,實現代發的 理想效果圖    當然,

C#實現發送郵件找回密碼功能

保存 bject ESS () 裏的 輸入框 鏈接地址 添加 dpa 首先我們來分析一下思路: 三步走: 1.先要發送郵件 2.讓用戶點擊郵件裏的URL 3.實現修改密碼 1.為了保證安全性,需要生成發送到郵件的URL,主要參數(用戶名,過期時間,key(key 需要在每次