`
zsxxsz
  • 浏览: 451146 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

阅读更多

    邮件做为最早和最广的互联应网用之一,已经与人们的生活息息相关。我们虽然经常使用 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
#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/

 github 地址:https://github.com/acl-dev/acl

 个人微博:http://weibo.com/zsxxsz

 

1
8
分享到:
评论

相关推荐

    Lotus必考习题

    数据库设计包括数据模型的规划,而代理可以实现自动化任务,例如定时发送邮件或执行数据更新。页面则是用户交互的界面,需要考虑用户体验和布局设计。 程序设计在Lotus Domino中通常涉及LotusScript和Formula ...

    计算机网络课程设计.doc

    2、 简单的网络通信程序(包括服务器端程序,客户端程序,能实现客户端到服务器的主动连接,发送信息,服务器能否对信息做一次回应)。 3、 编写一个程序,能够根据输入的原始数据,及所选择的多项式,自动生成CRC...

    Domino管理培训资料

    - **代理**:自动执行任务的程序,如定时发送邮件或更新数据库。 - **设计元素**:包括表单、视图、字段、按钮等,用于构建应用程序界面和逻辑。 2. **Domino管理员角色** - **系统管理员**:负责安装、配置和...

    Domino基本概念

    代理是Domino中的自动化脚本,可以执行一系列操作,如发送邮件、更新文档、执行计算等。它们可以根据预定的触发条件自动运行,如定时任务或特定事件,提高了工作效率。 7. **访问控制列表(ACL)** 访问控制列表...

    louts notes编程学习笔记

    6. **Integration with Other Systems**: Lotus Notes可以与其他系统集成,如通过Web服务接口(SOAP或REST)与其他应用程序通信,或者使用Lotus Notes Connector与ERP、CRM系统集成。 7. **RDBMS Interoperability*...

    IBM Lotus Domino 7系统基础管理

    Lotus Domino是IBM提供的一种强大的应用程序服务器,它集成了电子邮件、日历、任务管理、即时通讯等多种功能,是企业信息化建设的重要工具。以下是基于这个主题的详细知识点: 1. **Lotus Domino架构**:了解Lotus ...

    IIS6.0 IIS,互联网信息服务

    除了匿名访问用户(Anonymous)外,IIS中的FTP将使用Windows 2000自带的用户库(可在“开始→程序→管理工具→计算机管理”中找到“用户”一项来进行用户库的管理)。 最后,关键一步还有就是将你的电脑变为网络中的...

    ASP源码—Totres ASP通用实验教学管理系统 v3.0.zip

    10. **集成其他服务**:可能与其他服务集成,如邮件服务发送通知,或者使用OAuth进行第三方登录。 在 Totres ASP通用实验教学管理系统 v3.0 的源码中,我们可以深入学习如何利用ASP技术实现上述功能,理解服务器端...

    Suse 基本教學使用手冊

    - **邮件服务器**:配置Postfix发送和接收邮件。 - **FTP 服务器**:提供文件传输服务。 - **SSH 服务器**:安全的远程登录服务。 - **VNC 远程桌面**:实现远程图形界面控制。 #### PAM 认证模块 - **PAM 模块简介...

    lotus命令集lotus命令集lotus命令集lotus命令集

    2. `Agent`:可编程的任务执行器,可以定期运行以自动执行各种操作,如发送邮件、更新数据等。 总结来说,Lotus Notes命令集是管理员和开发人员的有力工具,涵盖了从数据库管理、用户权限、脚本编写到服务器控制等...

    domino基础开发

    "Agents"在Domino中指的是自动执行任务的脚本,如定时发送邮件通知、处理数据更新等。这部分可能深入解释如何创建和配置这些代理,以及如何根据需要触发它们。"Security"则关乎数据的保护,包括用户权限管理、角色...

    Mastering SaltStack

    状态文件通常使用YAML格式编写,并存储在`/srv/salt/`目录下。 ##### 3.2 自动化任务 SaltStack提供了丰富的API和命令行工具,支持用户自定义脚本或模块来实现自动化任务。例如,可以通过编写Python脚本来执行复杂...

    cakephp3-user-management

    这需要发送确认邮件的功能,可以使用`CakeEmail`类来实现。 8. **错误处理与表单验证**:在处理用户输入时,应确保数据的有效性。CakePHP的`FormHelper`和`Validator`类可以帮助我们在客户端和服务器端进行表单验证...

Global site tag (gtag.js) - Google Analytics