郵件作爲最先和最廣的互聯應網用之一,已經與人們的生活息息相關。咱們雖然常常使用 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/