使用 acl 庫編寫發送郵件的客戶端程序

    郵件作爲最先和最廣的互聯應網用之一,已經與人們的生活息息相關。咱們雖然常常使用 Outlook Express/Outlook/Foxmail 等郵件客戶端發送郵件,但並不關心發送過程的細節。若是您是一名程序員,則偶爾須要本身寫一個小程序來實現發送郵件的目的,本文將會介紹如何使用 acl 裏的 smtp_client 模塊快速實現一個郵件發送的客戶端程序。下面是發送郵件一個簡單流程:程序員

 

220 inc365.com ESMTP mail for UNIX (mail1.0)小程序

ehlo localhost服務器

 

250-inc365.com併發

 

250-PIPELININGdom

250-SIZE 51200000svn

250-VRFY函數

250-ETRNoop

250-AUTH LOGINui

250-AUTH=LOGIN編碼

250-ENHANCEDSTATUSCODES

250-8BITMIME

250 DSN

 

auth login

334 dXNlcm5hbWU6

emhlbmdzaHV4aW5AaW5jMzY1LmNvbQ==

334 cGFzc3dvcmQ6

YWFhYWFh

235 2.0.0 Authentication successful

 

mail from: aaaa@sss.ssss.ssss

250 2.1.0 Ok

rcpt to: zhengshuxin@inc365.com

250 2.1.5 Ok

data

354 End data with <CR><LF>.<CR><LF>

subject: hello world

from: aaaa@sss.ssss.ssss

to: zhengshuxin@inc365.com


hello!

.

250 2.0.0 Ok: queued

quit

221 2.0.0 Bye

 

 

其中,黃色部分爲郵件客戶端的命令請求過程,紅色部分爲郵件服務器的命令響應過程。該交互過程並不複雜,其實客戶端發送的命令主要有:

1)ehlo/helo: 問候服務器命令

2)auth login: SMTP 身份認證命令

3)mail from: 發送發件人郵箱地址命令

4)rcpt to: 發送收件人郵箱地址命令

5)data: 發送開始發郵件數據的命令,以行爲單位發送數據,所發送數據須要通過編碼處理(採用 base64/qp 等編碼)轉爲可打印的字符串,當發送一行數據中只有 . 時則表示郵件數據完畢

6)quit: 退出發送過程命令

 

      郵件發送過程雖然簡單,但每次都從新寫發送過程未免羅嗦,因此在 acl 項目的 lib_protocol 庫中提供了 smtp_client 模塊用來發送郵件。在 lib_protocol/include/smtp/smtp_client.h 中提供了經常使用的 SMTP 發送協議的函數接口,以下所示:

 

/**
 * 遠程鏈接 SMTP 服務器
 * @param addr {const char*} SMTP 服務器地址,格式:domain:port
 * @param timeout {int} 鏈接超時時間及IO讀寫超時時間
 * @param line_limit {int} SMTP 會話過程當中每行的最大長度限制
 * @return {SMTP_CLIENT*} 鏈接成功返回非空值,不然返回 NULL
 */
SMTP_API SMTP_CLIENT *smtp_open(const char *addr, int timeout, int line_limit);

/**
 * 關閉由 smtp_open 打開的 SMTP 鏈接並釋放對象
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 */
SMTP_API void smtp_close(SMTP_CLIENT *client);

/**
 * 得到 SMTP 服務器的歡迎信息
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_get_banner(SMTP_CLIENT *client);

/**
 * 向 SMTP 服務器發送 HELO/EHLO 命令
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @param name {const char*} 握手信息,通常用域名
 * @param ehlo {int} 非 0 時使用 EHLO,不然使用 HELO
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */

SMTP_API int smtp_greet(SMTP_CLIENT *client, const char* name, int ehlo);

/**
 * 向 SMTP 服務器發送 HELO 命令
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @param helo {const char*} 握手信息,通常用域名
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_helo(SMTP_CLIENT *client, const char *helo);

/**
 * 向 SMTP 服務器發送 EHLO 命令
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @param ehlo {const char*} 握手信息,通常用域名
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_ehlo(SMTP_CLIENT *client, const char *ehlo);

/**
 * 向 SMTP 服務器發送驗證信息
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @param user {const char*} SMTP 郵件帳號
 * @param pass {const char*} SMTP 郵件帳號密碼
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_auth(SMTP_CLIENT *client, const char *user, const char *pass);

/**
 * 向 SMTP 服務器發送 MAIL FROM 命令
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @param from {const char*} 發送者郵箱
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_mail(SMTP_CLIENT *client, const char *from);

/**
 * 向 SMTP 服務器發送 RCPT TO 命令
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @param to {const char*} 接收者郵箱
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_rcpt(SMTP_CLIENT *client, const char *to);

/**
 * 向 SMTP 服務器發送 DATA 命令
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_data(SMTP_CLIENT *client);

/**
 * 向 SMTP 服務器發送郵件體內容,能夠循環調用本函數直至數據發送完畢
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @param src {const char*} 遵照郵件 MIME 編碼格式的郵件體內容
 * @param len {size_t} src 數據長度
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_send(SMTP_CLIENT *client, const char* src, size_t len);

/**
 * 向 SMTP 服務器發送郵件體內容,能夠循環調用本函數直至數據發送完畢
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @param fmt {const char*} 格式字符串
 * @param ... 變參
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_printf(SMTP_CLIENT *client, const char* fmt, ...);

/**
 * 發送完郵件內容後調用本函數告訴 SMTP 服務器郵件數據完畢
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_data_end(SMTP_CLIENT *client);

/**
 * 向 SMTP 服務器發送指定件路徑的郵件文件
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @param filepath {const char*} 郵件文件路徑
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_send_file(SMTP_CLIENT *client, const char *filepath);

/**
 * 向 SMTP 服務器發送給定文件流的郵件內容
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @param int {ACL_VSTREAM*} 郵件文件輸入流
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_send_stream(SMTP_CLIENT *client, ACL_VSTREAM *in);

/**
 * 向 SMTP 服務器發送退出(QUIT)命令
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_quit(SMTP_CLIENT *client);

/**
 * 向 SMTP 服務器發送 NOOP 命令
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_noop(SMTP_CLIENT *client);

/**
 * 向 SMTP 服務器發送 RSET 命令
 * @param client {SMTP_CLIENT*} SMTP 鏈接對象
 * @return {int} 0 表示成功(SMTP_CLIENT::smtp_code 表示返回碼,
 *  SMTP_CLIENT::buf 存儲響應內容),不然表示出錯,應該關閉鏈接對象
 */
SMTP_API int smtp_rset(SMTP_CLIENT *client);

 

   讀者也許注意到每一個函數前都有一個前綴:SMTP_API,這個前綴當你使用 VC 編譯應用程序且選擇了動態連接庫時纔會用到,此時須要增長條件編譯開關:SMTP_DLL,以告訴VC編譯器須要導出函數接口;在使用VC的靜態連接方式或在UNIX下編譯時,SMTP_API 僅是一個空定義而已。 

 

   靈活使用這些函數接口,您即可以輕鬆實現一個郵件發送客戶端,如今給一個具體的實例來(該實例在 samples/smp_client/ 目錄下)說明函數接口的調用過程,以下:

 

/* smtp_client.cpp : 定義控制檯應用程序的入口點 */

#include "lib_acl.h"
#include "lib_protocol.h"

#ifdef WIN32
#define snprintf _snprintf
#endif

static int smtp_sender(void)
{
	SMTP_CLIENT* conn;
	char  addr[128], line[256];

	acl_printf("please enter smtp server addr: ");
	if (acl_gets_nonl(line, sizeof(line)) == NULL)
	{
		acl_puts("invalid smtp server addr");
		return -1;
	}

	if (strchr(line, ':') == NULL)
		snprintf(addr, sizeof(addr), "%s:25", line);
	else
		snprintf(addr, sizeof(addr), line);

	/* 鏈接 SMTP 服務器 */
	conn = smtp_open(addr, 60, 1024);
	if (conn == NULL)
	{
		acl_printf("connect %s error %s\r\n", addr, acl_last_serror());
		return -1;
	}
	else
		acl_printf("connect smtpd(%s) ok\r\n", addr);

	/* 從 SMTP 服務器得到歡迎信息 */
	if (smtp_get_banner(conn) != 0)
	{
		acl_puts("get banner from server error");
		smtp_close(conn);
		return -1;
	}
	else
		acl_printf(">smtpd: %s\r\n", conn->buf);

	/* 向 SMTP 服務器發送 EHLO/HELO 命令 */
	if (smtp_greet(conn, "localhost", 0) != 0)
	{
		acl_printf("send ehlo cmd error: %s\r\n", conn->buf);
		smtp_close(conn);
		return -1;
	}
	else
		acl_printf(">smtpd: %s\r\n", conn->buf);

	/* 用戶是否須要進行 SMTP 身份認證 */
	acl_printf("Do you want to auth login? n[y|n]: ");
	if (acl_gets_nonl(line, sizeof(line)) == NULL)
	{
		acl_puts("invalid input");
		smtp_close(conn);
		return -1;
	}

	/* 對用戶身份進行 SMTP 身份認證 */
	else if (strcasecmp(line, "Y") == 0)
	{
		char user[128], pass[128];
		acl_printf("Please input user account: ");
		if (acl_gets_nonl(user, sizeof(user)) == NULL)
		{
			acl_puts("input invalid");
			smtp_close(conn);
			return -1;
		}

		acl_printf("Please input user password: ");
		if (acl_gets_nonl(pass, sizeof(pass)) == NULL)
		{
			acl_puts("input invalid");
			smtp_close(conn);
			return -1;
		}

		/* 開始進行 SMTP 身份認證 */
		if (smtp_auth(conn, user, pass) != 0)
		{
			acl_printf("smtp auth(%s, %s) error: %s, code: %d\r\n",
				user, pass, conn->buf, conn->smtp_code);
			smtp_close(conn);
			return -1;
		}
		else
			acl_printf(">smtpd: %s\r\n", conn->buf);
	}

	/* 得到發件人郵箱地址 */
	acl_printf("please input sender's email: ");
	if (acl_gets_nonl(line, sizeof(line)) == NULL)
	{
		acl_puts("invalid sender's email");
		smtp_close(conn);
		return -1;
	}

	/* 發送 MAIL FROM: 命令 */
	if (smtp_mail(conn, line) != 0)
	{
		acl_printf("smtp send MAIL FROM %s error\r\n", line);
		smtp_close(conn);
		return -1;
	}
	else
		acl_printf(">smtpd: %s\r\n", conn->buf);

	/* 發送 RCPT TO: 命令 */
	while (1)
	{
		acl_printf("please input mail recipients: ");
		if (acl_gets_nonl(line, sizeof(line)) == NULL)
		{
			acl_puts("invalid mail recipients");
			smtp_close(conn);
			return -1;
		}

		/* 發送 RCPT TO: 命令 */
		else if (smtp_rcpt(conn, line) != 0)
		{
			acl_printf("send RCPT TO: %s error: %s, code: %d\r\n",
				line, conn->buf, conn->smtp_code);
			smtp_close(conn);
			return -1;
		}
		else
			acl_printf(">smtpd: %s\r\n", conn->buf);

		acl_printf("Do you want to add another recipients? n[y|n]: ");
		if (acl_gets_nonl(line, sizeof(line)) == NULL)
		{
			acl_puts("input invalid");
			smtp_close(conn);
			return -1;
		}
		else if (strcasecmp(line, "y") != 0)
			break;
	}

	/* 發送 DATA: 命令 */
	if (smtp_data(conn) != 0)
	{
		acl_printf("send DATA error %s, code: %d\r\n",
			conn->buf, conn->smtp_code);
		smtp_close(conn);
		return -1;
	}
	else
		acl_printf(">smtpd: %s\r\n", conn->buf);

	/* 從終端接收用戶的輸入的郵件內容併發往 SMTP 服務器 */
	acl_puts("Please enter the email data below, end with \\r\\n.\\r\\n");

	while (1)
	{
		if (acl_gets_nonl(line, sizeof(line)) == NULL)
		{
			acl_puts("readline error");
			smtp_close(conn);
			return -1;
		}
		if (strcmp(line, ".") == 0)
			break;
		if (smtp_printf(conn, "%s\r\n", line) != 0)
		{
			acl_printf("send data to smtpd error, data: %s\r\n", line);
			smtp_close(conn);
			return -1;
		}
	}

	/* 發送 \r\n.\r\n 表示郵件數據發送完畢 */
	if (smtp_data_end(conn) != 0)
	{
		acl_printf("send . error: %s, code: %d\r\n",
			conn->buf, conn->smtp_code);
		smtp_close(conn);
		return -1;
	}
	else
		acl_printf(">smtpd: %s\r\n", conn->buf);

	/* 發送 QUIT 命令 */
	if (smtp_quit(conn) != 0)
	{
		acl_printf("smtp QUIT error: %s\r\n", conn->buf);
		smtp_close(conn);
		return -1;
	}
	else
		acl_printf(">smtpd: %s\r\n", conn->buf);

	smtp_close(conn);
	return 0;
}

int main(void)
{
	int   ret;

#ifdef WIN32
	acl_init();
#endif

	while (1)
	{
		char line[128];

		ret = smtp_sender();
		if (ret == -1)
			break;
		acl_printf("Do you want to send another email? n[y|n]: ");
		if (acl_gets_nonl(line, sizeof(line)) == NULL)
		{
			acl_puts("invalid input");
			break;
		}
		else if (strcasecmp(line, "y") != 0)
			break;
	}

#ifdef WIN32
	acl_vstream_printf("enter any key to exit\r\n");
	acl_vstream_getc(ACL_VSTREAM_IN);
#endif

	return ret;
}

 

不管使用 VC2003 編譯仍是在LINUX下用GCC編譯,運行可執行程序,均會獲得以下結果:

 

please enter smtp server addr: mail.inc365.com:25

connect smtpd(mail.inc365.com:25) ok

>smtpd: 220 inc365.com ESMTP mail for UNIX (mail1.0)

>smtpd: 250 inc365.com

Do you want to auth login? n[y|n]:

please input sender's email: zsxxsz@sina.com

>smtpd: 250 2.1.0 Ok

please input mail recipients: zhengshuxin@inc365.com

>smtpd: 250 2.1.5 Ok

Do you want to add another recipients? n[y|n]:

>smtpd: 354 End data with <CR><LF>.<CR><LF>

Please enter the email data below, end with \r\n.\r\n

subject: hello world

from: zsxxsz@sina.com

to: zhengshuxin@inc365.com

 

hello world

.

>smtpd: 250 2.0.0 Ok: queued

>smtpd: 221 2.0.0 Bye

 

 

 

 acl 庫下載: https://sourceforge.net/projects/acl/

 acl svn 地址:svn://svn.code.sf.net/p/acl/code/

 我的微博:http://weibo.com/zsxxsz

相關文章
相關標籤/搜索