用java开发Email工具之发送邮件 (1)作者:冯睿
作者:冯睿 来源:赛迪网
本文介绍了如何利用Java的网络API来实现一个电子邮件工具程序。通常Email工具都 是使用SMTP(简单邮件传输协议, Simple Mail Transfer Protocol)来发送邮件,使用POP3协议来接受电子邮件。在本文中只对这两个协议作简单介绍。如果有兴趣的读者可以参考以下站点:
POP3: ftp://ftp.isi.edu/in-notes/rfc1939.txt
SMTP: ftp://ftp.isi.edu/in-notes/rfc2821.txt
Java中虽然提供了JavaMail API,但是由于在这篇文章中我将从底层来探讨电子邮件软件是如何工作的,因此不会使用JavaMail API。本文中的例子是在J2SE 1.4下开发的。
电子邮件的格式
在开发Email软件之前,你需要了解电子邮件的格式。根据RFC 2882(http://www.faqs.org/rfcs/rfc2822.html)的规定,电子邮件由很多行组成,每行由<CRLF>(ASCII代码13和ASCII代码10)结束。每行的最大长度为998个字符。其中有些行提供了收发电子邮件所必需的信息,这些行被称为头(Header),所有的头构成了头域(Header Field)。其他的行用于保存邮件的具体内容。
头域提供了很多信息,其中包括邮件的来源;邮件的目的地和邮件的主题等。每个头由名 称和冒号加上相应的值构成。例如From:、Send:和Reply-To:中记录了邮件的来源。在From:中记录的是邮件的作者;在Sender:中 指定了发送邮件的代理(可以是邮件地址,也可以是机器名称);Reply-To:中指定了接受回信的邮箱地址。
一封邮件可能有多个作者,因此From:中可以指定一个或多个邮箱地址。下面给出了一个个From:的例子:
From: Ray Feng <rayfeng@yahoo.com.cn>, bogus@yahoo.com.cn |
在一封电子邮件中只能有一个Sender。因此Sender:的值只能包含一个邮箱 地址。如果在From:中只有一个作者,而且Sender:的值和From:的值相同,则Sender:就不会出现在电子邮件中,否则会出现信息冗余;反 之Sender:则应该出现在邮件中。下面是一个Sender:的例子:
Sender: Ray Feng rayfeng@yahoo.com.cn |
在电子邮件中可以指定将回信发送到多个邮箱地址中。因此Reply-To:中可以包 含一个或多个邮箱地址,每个地址之间用逗号隔开。如果邮件中有Reply-To:,回信会被发送到罗列在Reply-To:中的所有地址;如果邮件中没有 Reply-To:,则回信会被发送到罗列在From:中的地址。那么谁会收到邮件呢?To:和Cc:中保存了接受邮件的邮箱地址。两者的值都可以包含多 个邮箱地址。
除了邮件的来源和接受者,RFC 2882中还定义了其他一些头,例如Subject:中包含了电子邮件的主题。下面是一个电子邮件头域的例子:
From: Ray Feng <rayfeng@yahoo.com.cn>To: bogus <bogus@yahoo.com.cn>Cc: John <John@yahoo.com.cn>Subject: Test Email |
附件
在MIME中允许在电子邮件中添加二进制文件,被添加的文件叫做附件。附件的内容可 以作为邮件的一部分进行传输。MIME是如果实现这个功能的呢?在MIME中引入了很多头,其中和附件相关的最重要的就是Content-Type:和 Content-Tracnsfer-Encoding:。为了在一封电子邮件中区分不同的部分,MIME要求在Content-Type: multipart/mixed头中包含一个边界参数。边界参数的值是一个在双引号中的字符串。通过这个字符串,程序就可以区分电子邮件的不同部分。在传 输电子邮件的内容前,程序先传输一个<CRLF>,两个连字符和边界参数。当完成Email内容的传输后,程序会在最后传输边界参数和两个连字符。
下面的电子邮件中包含了两个部分,一个部分是由iso-8859-1字符组成的文本,一部分是名为file.txt的附件。这里没有包含Content-Transfer-Encoding:头,表明使用缺省的7位ASCII字符。
Content-Type: multipart/mixed; boundary="***"--***Content-Type: text/plain; charset="iso-8859-1"This message has an attachment.--***Content-Type: text/plain; name="file.txt"Attachment text.--***-- |
发送电子邮件
基于互联网的电子邮件通常是利用SMTP网络协议进行传输的。根据SMTP,当电子邮件程序 需要发送电子邮件时,该程序首先同一个SMTP服务程序建立起双向的通讯通道(通常是通过套接字建立这种通道的)。这个基本的SMTP服务程序或许是这份 电子邮件的最终目的地,也可能只是通向另一个SMTP服务程序的跳板。总而言之,当电子邮件程序同SMTP服务程序建立起双相的传输通道后,电子邮件程序 会向SMTP服务程序发送一系列基于ASCII字符的命令,而SMTP服务程序会对这些命令产生相应的回应来表明相应的操作是成功还是失败了。
让我们假设所有的操作都成功了,那么电子邮件程序将把邮件发送到SMTP服务程 序,如果电子邮件的接收地址正好是该SMTP服务程序运行的服务器,那么SMTP服务程序就会将邮件加入邮件数据库中,否则SMTP服务程序将把邮件转发 到在其他SMTP服务器上的SMTP服务程序,直到到达目的地为止。图二通过图示说明了这一点。
SMTP可以识别很多电子邮件用来与SMTP服务程序通讯的命令。某些命令需要参数,某些命令则不需要。但是每个命令后必须跟一个<CRLF>。最常用的六个命令是HELO,MAIL,RCPT,DATA,RSET和QUIT。
按照上面的顺序给出这六个命令并非偶然。除了RSET外,其他的命令必须按照特定的顺序发送,这是因为SMTP服务程序是基于状态的。对于每一个建立了双向通讯通道的电子邮件程序,SMTP服务程序都会保存当前的通讯状态。
当一个电子邮件程序和SMTP服务程序建立联系后,SMTP服务程序将向电子邮件程 序发送初始化消息。该消息包含了一个三位回应码,这个回应码是用来标识SMTP服务程序的。除此之外,在SMTP服务程序发送给电子邮件程序的消息的头部 也带有回应码,它们被用来表示操作成功或者失败。电子邮件程序接收到这些回应码后,可以根据其中包含的信息完成相应的工作。而消息的文本部分是给人看的, 电子邮件程序可以忽略文本部分。
在收到初始化消息后,电子邮件程序通过发送HELO命令来开始传输邮件。HELO 命令有一个参数,该参数标志了SMTP服务程序所在服务器的域名。它将在SMTP服务程序中标识出SMTP服务程序。作为回应,SMTP服务程序进行一些 初始化工作,将自己设定到初始状态以接收电子邮件。当这些工作成功完成后,它发送回一条成功的回应消息给电子邮件程序,该回应消息以回应码250开头。
在HELO命令之后,电子邮件程序会发送MAIL命令。MAIL命令将在SMTP 服务程序中标识出发送者,它有两个参数:FROM:和一个电子邮件地址。如果SMTP服务程序能够成功地解析电子邮件地址的话,通常它将返回以250开头 的回应消息;否者将发送回表示操作失败的回应消息。
在MAIL之后是RCPT命令。RCPT命令在SMTP服务程序中标识出一个邮件的接收者,它也有两个参数:TO:和一个电子邮件地址。如果邮件由多个接收者,则程序需要多次发送RCPT命令。
RCPT命令之后,程序需要发送电子邮件本身了。程序先发送一个DATA命令,当接 收到表示成功的回应消息后,将电子邮件逐行发送给SMTP服务程序,当所有的行都发送完毕后,程序发送一行由句号组成的行。在此之后,电子邮件程序等待 SMTP服务程序的回应消息,以确定邮件被SMTP服务程序正常接收了。这一切都成功后,程序可以发送RSET命令来退出邮件传输过程。最后,当要断开和 SMPT服务程序建立的连接时,程序发送QUIT命令。主要提醒的一点是,虽然上面的命令都是大写的,但是在实际的协议对大小写不敏感。
现在也许你关心的问题是回应码的格式是怎样的。最左边的一位数字代表操作是否成功,1代表收 到命令,2代表操作成功完成,3代表等待后续命令,4代表操作临时未能完成(电子邮件程序可以在当前的邮件传输过程中重新发送命令),5代表操作不能完成 (电子邮件程序不能在当前的邮件传输过程中重新发送命令)。第二位数字代表回应的领域,0代表语法错误,1代表消息请求,2代表传输通道,3和4没有指 定,5代表与邮件系统相关。最有一位数字对第二位数字做补充说明,这里就不再详述。根据上面的信息,我们可以看出250代表请求的命令已经成功完成; 220代表SMTP服务程序正在等待HELO命令;而503代表命令顺序错误。有兴趣的朋友可以参见RFC 2821。
下面提供了一个基于命令行的例子SMTPDemo,这个例子可以帮助你理解基于SMTP的邮件传输机制。这个程序将利用标准端口25连接到一个SMTP服务程序上。为了使程序能够运行,你需要将home更改为你使用的邮件服务器的地址。
// SMTPDemo.javaimport java.io.*;import java.net.*;class SMTPDemo{ public static void main (String [] args) { String SMTPServer = "home int SMTPPort = 25; Socket client = null; try { // 向SMTP服务程序建立一个套接字连接。 client = new Socket (SMTPServer, SMTPPort); // 创建一个BufferedReader对象,以便从命令行读取用户输入。 BufferedReader stdin; stdin = new BufferedReader (new InputStreamReader (System.in)); // 创建一个BufferedReader对象,以便从套接字读取输出。 InputStream is = client.getInputStream (); BufferedReader sockin; sockin = new BufferedReader (new InputStreamReader (is)); // 创建一个PrintWriter对象,以便向套接字写入内容。 OutputStream os = client.getOutputStream (); PrintWriter sockout; sockout = new PrintWriter (os, true); // 显示同SMTP服务程序的握手过程。 System.out.println ("S:" + sockin.readLine ()); while (true) { System.out.print ("C:"); // 读取用户输入。 String cmd = stdin.readLine (); // 将用户输入的命令发送到SMTP服务程序。 sockout.println (cmd); // 从套接字读取SMTP服务程序的回应消息并显示在屏幕上。 String reply = sockin.readLine (); System.out.println ("S:" + reply); // 如果发送了DATA命令并且获得成功的回应消息,从输入设备读取行,// 直到读取到完全由句号组成的行时停止, 这些行构成了电子邮件。 if (cmd.toLowerCase ().startsWith ("data") && reply.substring (0, 3).equals ("354")) { do { cmd = stdin.readLine (); if (cmd != null && cmd.length () > 1 && cmd.charAt (0) == '.') cmd = "."; sockout.println (cmd); if (cmd.equals (".")) break; } while (true); // 从SMTP服务程序中读取回应消息并显示。 reply = sockin.readLine (); System.out.println ("S:" + reply); continue; } // 如果用户输入QUIT命令,则退出程序。 if (cmd.toLowerCase ().startsWith ("quit")) break; } } catch (IOException e) { System.out.println (e.toString ()); } finally { try { if (client != null) client.close (); } catch (IOException e) { } } }} |
当运行SMTPDemo时,你将会看到下面的输出。其中C:后面是用户的输入,S:后面是SMTP服务程序返回的信息。
S:220 home.digital.com Microsoft ESMTP MAIL Service, Version: 4.0.2195.2966 ready at Fri, 13 Dec 2002 15:06:58 +0800 |
当运行SMTPDemo后,邮件服务程序返回了初始化信息。
C:helo digital.comS:250 home.digital.com Hello [23.2.254.53] |
通过发送helo digital.com命令开始邮件传输过程。digital.com是邮件服务器所在域的域名。然后邮件服务程序返回了以250开头的欢迎信息。
C:mail from: rayfeng@digital.comS:250 2.1.0 rayfeng@digital.com....Sender OK |
接下来是输入邮件发送者的信息mail from:。邮件服务程序返回了成功信息。
C:rcpt to: rayfeng@digital.comS:250 2.1.5 rayfeng@digital.com |
然后是通过rcpt to:指定邮件的接收者。
C:dataS:354 Start mail input; end with <CRLF>.<CRLF>Subject: Test EmailThis is the test Email..S:250 2.6.0 HOMEOulkEZ00VNuHKDy00000002@home.digital.com Queued mail for delivery |
接下来是输入邮件的内容。发送DATA命令后,等待服务器发送回命令被成功接收的回应消息。当接收到以354开头的回应消息时,就可以输入电子邮件的内容了。完成后以<CRLF>.<CRLF>结束。
C:quitS:221 2.0.0 home.digital.com closing connection |
最后退出发送电子邮件的过程。请注意回应码221,最左边的2代表操作成功,中间的2表示传输通道,1表示连接关闭。
前面我曾讨论过关于附件的问题。通过SMTPDemo也可以发送附件。通过向邮件服务程序发送下面的命令,就可以在邮件中加入file.txt作为附件。
helo digital.netmail from: rayfeng@digital.comrcpt to: rayfeng@digital.comdataSubject: Attachment DemoContent-Type: multipart/mixed; boundary="***"--***Content-Type: text/plain; charset="iso-8859-1"This message has an attachment.--***Content-Type: text/plain; name="file.txt"Attachment text.--***--quit |
到此为止,我们介绍了如何用Java实现Email工具的发送功能,并从地层分析了邮件发送的机制,不知您是否已经掌握了这些内容。在下一篇文章中,我们将一起来研究Email工具的接收功能。
http://developer.ccidnet.com/art/322/20021218/33989_1.html
用java开发Email工具之发送邮件 (1)作者:冯睿相关推荐
- java 开发人员工具_每个Java开发人员都应该知道的10个基本工具
java 开发人员工具 大家好,我们已经到了2019年的第二个月,我相信你们所有人都已经制定了关于2019年学习以及如何实现这些目标的目标. 我一直在撰写一系列文章,为您提供一些知识,使您可以学习和改 ...
- java 开发人员工具_Java开发人员应该知道的5种错误跟踪工具
java 开发人员工具 随着Java生态系统的发展,可满足不断增长的请求和用户对高性能需求的Web应用程序成为了新型的现代开发工具. 具有快速新部署的快速节奏环境需要跟踪错误,并以传统方法无法维持的水 ...
- java 开发人员工具_Java开发人员应该知道的7种新工具
java 开发人员工具 通过快速浏览一些最新的,创新的工具,准备好锁定和加载. 万一您错过了它,RebelLabs最近发布了Java工具和技术前景的全球调查结果 . 除了著名的工具和成熟的工具之外,市 ...
- java 开发人员工具_Java开发人员的5种工具
java 开发人员工具 改善我们编写的Java代码的一种方法是使用最好的工具. 因此,让我们看看IDR Solutions建议使用的5种最常用的工具来帮助Java开发人员编写更好的代码. 查找错误 F ...
- java 操作vss,java开发常用工具总结,java开发常用工具
java开发常用工具总结,java开发常用工具 1.editplus editplus 是我使用最频繁的工具,不管是java程序还是其他的语言的程序,本人都使用它,方便好用,速度快.如果配置好的话,可 ...
- linux java 构建工具有哪些,Linux ant --强大的Java开发构建工具
用途说明 ant严格说来,ant其实并非原生的Linux命令,但它是一个使用广泛.功能强大的跨平台构建工具程序,尤其是进行Java开发时,许多开源的Java项目都使用ant作为构建工具.ant命令一般 ...
- java开发测试工具
JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework).Junit测试是程序员测试,即所谓白盒测试,因为程序 ...
- java开发常用工具、github加速工具、idea插件
java常用工具 开源终端工具:Tabby 非开源的终端工具:xshell 开源数据库连接工具:DBeaver 开发工具:idea 社区版 文本工具:notepad++ 脑图:xmind 在线脑图:p ...
- java 开发屏幕截图工具_Java屏幕截图工具 捕获屏幕
下面是编程之家 jb51.cc 通过网络收集整理的代码片段. 编程之家小编现在分享给大家,也给大家做个参考. import java.awt.BorderLayout; import java.awt ...
最新文章
- V4L2获取usb视频流测试代码
- 剑指offer:和为S的连续正数序列
- 看 nova-scheduler 如何选择计算节点 - 每天5分钟玩转 OpenStack(27)
- python中的几种copy用法_Python3中copy模块常用功能及其他几种copy方式比较
- Kafka跨集群迁移方案MirrorMaker原理、使用以及性能调优实践
- 7月15号day7总结
- android 搜索工具栏,Android记事本在菜单栏添加搜索按钮方法
- 黑洞内外,时空弯曲的漩涡
- shell从小做起:将100以内整除3的数列出来
- latex 常用环境(environment)
- 钉钉直播html,钉钉直播功能介绍,钉钉直播步骤详情
- 鲁棒控制(棒棒控制原理)
- Oracle 执行计划详解(预估 + 真实)
- 看图识物_App Store 上的“认识动物-看图识物大全、学英语”
- lis =[2,3,'k',['qwe',20,['k1',['tt',3,'1']],89],'ab','adv'] 将列表lis中的'tt'变成大写(用两种方式)。...
- TIA博途中CPU固件版本和实际PLC不同时如何进行程序的下载?
- linux基础及应用教程第二版课后答案,Linux基础及应用复习题(附加参考答案)
- 从“被动挖光缆”到“主动剪网线”,蚂蚁金服异地多活的微服务体系
- IoT物联网——各大厂质量保障实践分享汇总(智能语音视频篇)
- 自己动手做聊天机器人 二十九-重磅:近1GB的三千万聊天语料供出