2019独角兽企业重金招聘Python工程师标准>>>

邮件做为最早和最广的互联应网用之一,已经与人们的生活息息相关。我们虽然经常使用 Outlook Express/Outlook/Foxmail 等邮件客户端发送邮件,但并不关心发送过程的细节。如果您是一名程序员,则偶尔需要自己写一个小程序来实现发送邮件的目的,本文将会介绍如何使用 acl 里的 smtp_client 模块快速实现一个邮件发送的客户端程序。下面是发送邮件一个简单流程:

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

ehlo localhost

250-inc365.com

250-PIPELINING

250-SIZE 51200000

250-VRFY

250-ETRN

250-AUTH LOGIN

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
#endifstatic 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);elsesnprintf(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;}elseacl_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;}elseacl_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;}elseacl_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;}elseacl_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;}elseacl_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;}elseacl_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;}elseacl_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;}elseacl_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;}elseacl_printf(">smtpd: %s\r\n", conn->buf);smtp_close(conn);return 0;
}int main(void)
{int   ret;#ifdef WIN32acl_init();
#endifwhile (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 WIN32acl_vstream_printf("enter any key to exit\r\n");acl_vstream_getc(ACL_VSTREAM_IN);
#endifreturn 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

转载于:https://my.oschina.net/u/568966/blog/309543

使用 acl 库编写发送邮件的客户端程序相关推荐

  1. 2023-05-29 用 fltk gui库编写一个打字练习程序

    用 fltk gui库编写一个打字练习程序 前言 一.FLTK GUI 库 二.使用步骤 1.引入库 2.使用代码 总结 前言 给孩子练习键盘打字, 发现终端还是欠点意思, 研究了一下gui, 最终用 ...

  2. 使用PyInstaller将Pygame库编写的小游戏程序打包为exe文件

    目录 一.安装Pyinstaller 1.直接安装 2.Anaconda环境下安装 二.使用Pyinstaller打包生成exe文件 三.运行生成的exe文件 四.exe文件运行中的问题 一.安装Py ...

  3. python-基于yagmail库开发自动邮件发送程序

    目录: 前言: 一,准备工作; 1.安装第三方库 2.邮箱设置步骤 二,编写程序 1.发送单个邮件的程序 2.发送单个邮件的进阶程序 前言: 很多时候,我们发送网络邮件都需要进入邮箱网站或App,可能 ...

  4. 18. 编写FTP客户端程序

    在实际应用中可能经常访问FTP服务器来上传或下载文件,Python也可以替我们做这些. [示例 1]下面请看一个例子(ftpclient). 运行的结果如下: FTP客户端程序的编写还可以参照官方文档 ...

  5. 0729------Linux网络编程----------使用 select 、poll 和 epoll 模型 编写客户端程序

    1.select 模型 1.1 select 函数原型如下,其中 nfds 表示的描述符的最大值加1(因为这里是左闭右开区间),中间三个参数分别表示要监听的不同类型描述符的集合,timeout用来表示 ...

  6. python socket recv超时_python使用多线程编写tcp客户端程序,你还没掌握吗?

    这篇文章主要为大家详细介绍了python使用多线程编写tcp客户端程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 在网上浏览的时候发现很多关于此题目的程序都只能接收数据,所以随便找了个程序研究 ...

  7. php编写TCP服务端和客户端程序

    From: http://blog.csdn.net/anda0109/article/details/46655301 1.修改php.ini,打开extension=php_sockets.dll ...

  8. EMAIL的客户端程序

    今天小编给大家讲解EMAIL的客户端程序  ,这个程序也是小编我在东北大学读研究生期间学习的,今天分享给大家. 1. 实验目的 随着互联网的发展,越来越多的人使用电子邮件交流工作,电子邮件已经成为生活 ...

  9. 引用:编写高性能Web应用程序的10个技巧(一)

    在网上看到这样一篇文章:编写高性能Web应用程序的10个技巧 感觉很不错就引用到我的blog里面,和大家一起学习,同时也谢谢作者本人 数据层性能 技巧 1 - 返回多个结果集 技巧 2 - 分页的数据 ...

最新文章

  1. iview-admin webpack打包总结的几个问题
  2. 使用office生成PDF文件
  3. 深度学习和目标检测系列教程 9-300:TorchVision和Albumentation性能对比,如何使用Albumentation对图片数据做数据增强
  4. openwrt 挂载摄像头
  5. 描述项目的典型用户与场景
  6. [html] 在HTML5中,用于获得用户的当前位置是哪个方法?
  7. linux脚本多个条件比较大小,shell脚本-比较两个整数大小
  8. docker上传镜像到仓库
  9. pandas之dataframe去掉冗余行以及左连接合并dataframe
  10. MySQL常见的几种数据类型盘点
  11. 【MM32F5270开发板试用】基于MindSDK对接雨滴传感器
  12. 单职业传奇私脱机辅助制作视频教程
  13. 物联网智能家居ppt
  14. RGB 颜色格式转换
  15. 面对众多客户/用户需求,每个都紧急,怎么排期?
  16. You hasn‘t joined this enterprise!
  17. 锐文科技智能网卡xNIC-200/400在国产飞腾FT2000测试性能报告
  18. 金水智能汽车5G-V2X车路协同安全测试认证基地项目招标文件
  19. Rasa 3.x 学习系列-Rasa X 社区版(免费版) 更改
  20. 安卓火狐导出书签_firefox火狐浏览器中怎么导出书签备份收藏夹?

热门文章

  1. 大数据的两面性_大数据,多大的数据才是大数据?
  2. python 三分钟入门_Cython 三分钟入门教程
  3. python dos攻击_利用SMB漏洞DoS攻击任何Windows系统
  4. 【基础知识】截长图的方法以及防止截图时下拉框自动收回的方法
  5. 【matlab】第二章基本使用方法
  6. Angular 文件上传与下载
  7. 终端主题_再见 XShell 和 ITerm 2,是时候拥抱全平台高颜值终端工具 Hyper 了!
  8. promise 和 async await区别
  9. 高可用架构篇 MySQL源码编译安装(CentOS-6.6+MySQL-5.6)
  10. 那些对混合云开发和应用程序环境的错误认识