最近在 Spring Boot 项目中需要添加邮件功能,在完成应用开发后对邮件服务还存在些许迷雾。因此顺便回顾下电子邮件系统的工作原理以及 SMTP、MIME、POP3 和 IMAP 协议,做一次从基本原理到应用的知识点扫盲。
一、电子邮件
邮件服务,我们常见有普通的邮件服务(即线下邮局服务)和电子邮件服务。电子邮件服务是因特网在初期就发展起来的产物,该服务能够利用网络的便捷性实现和扩展传统邮件业务,如快速发送、多用户分发和价格低廉。现代电子邮件更是具备传输包含附件、超链接、HTML格式文本和图片的功能特性。
传统邮件服务我们都知道,下面就来认识一下电子邮件服务系统及原理。(参考《计算机网络·自顶向下方法》)
1.0 电子邮件系统
一个最基本的电子邮件系统如上图所示,主要包含三个部分:用户代理、邮件服务器和 SMTP协议。用户代理为一个邮件编写发送和查看删除的客户端;邮件服务器为该系统核心,每一个邮件服务器下含开放了多个“用户邮箱”,用于存储该服务器接收到的对应的用户的邮件;SMTP协议为 TCP/IP 网络中的应用层协议,能够实现单向的邮件 Push 传输功能。
如上图所示(Alice 和 Bob 为系统中任意两个用户),邮件系统的工作原理为:
- Alice 在其代理客户端中编写好邮件,通过 SMTP 协议发送到她的邮件服务器的外出报文队列中;
- Alice 的邮件服务器根据 Alice 邮件中写入的接收方 Bob 的邮箱地址,将该邮件通过 SMTP 协议发送到 Bob 的邮件服务器。(例如:从谷歌的 Gmail邮箱服务器 发送到网易的 163邮箱服务器)
- Bob 的邮件服务器接收到邮件后,根据邮件中的写入的接收方 Bob 的邮箱地址前缀(“@”的前面部分)定位到该服务器下的 Bob 的用户邮箱,并将该邮件存入该邮箱中。
- Bob 可以选择直接在他的邮件服务器中查看(即如果他有该服务器的登录权限,则可以直接进入服务器查看邮件,此时不需要后面的用户代理)。也可以选择使用用户代理的方式(常见方式),在自己的用户代理中通过 POP3/IMAP/HTTP 协议从邮件服务器中 Pull 他的邮件到其代理客户端进行查看等操作。
- 一次邮件系统工作完成。
要点补充
- 外出报文队列:顾名思义,为邮件服务器向其他邮件服务器发送邮件报文(即邮件内容)的队列。所有的服务器用户将邮件报文发送到队列中集中排队向外发送。如果发送失败则进行重复尝试(连接不到目的邮件服务器等问题),间隔指定时间尝试指定次数仍然失败后,邮件服务器(将会删除该邮件报文)以邮件的形式通知该用户他的邮件发送失败。
- 邮件服务器间直连:SMTP 一般不使用中间邮件服务器发送邮件。即两个用户的邮件服务器直接进行 TCP 连接后通过 SMTP 协议进行邮件报文传输。
1.1 SMTP
SMTP 是邮件系统的重要组成部分之一,是 Internet 电子邮件的核心。上面已经说明和体现了 SMTP 的大部分功能和特性,这里再介绍和总结一下 SMTP协议。
SMTP协议,即 Simple Mail Transfer Protocol 简单邮件传输协议,是一个作用类似于 HTTP 协议的互联网协议标准(但比 HTTP 早)。SMTP协议 与 HTTP 使用报文的方式不同,SMTP 没有类似的传输严格格式化的报文格式,而是通过命令事务来完成协议的实现。
1.1.1 SMTP交互模型
如上图SMTP模型所示,为 RFC 5321 SMTP 中的SMTP工作模型图。SMTP 实现为 C/S 架构,即含客户端和服务端。由客户端向服务端发送命令,服务端接收到命令后执行命令并反馈信息到客户端(即传统的C/S命令交互)。通过一次邮件事务的命令发送和执行完成邮件的发送。即SMTP交流模型:
当用户需要发邮件时候,邮件发送者(Client-SMTP)建立一个与邮件接收者(Server-SMTP)通信的通道,发送者发送SMTP命令给接收者,接收者收到后对命令做回复响应。
基本事务:通信通道被建立后,发送者发送 MAIL
命令来指定发送者的邮件,如果接受者接收这个邮件,就回复 OK
;接着发送者发送 RCPT
命令来指定接收者的邮箱,如果被接收同样回复OK
,如果不接受则拒绝(不会终止整个通话)。接收者邮箱确定后,发送者用DATA
命令指示要发送数据,并用一个.
结束发送。如果数据被接收,会收到OK
,然后用QUIT
结束会话。
一个SMTP邮件发送例子
1 | S: MAIL FROM:<Smith .ARPA> # 向服务器说明邮件发送者 |
进一步的简单了解请参考 SMTP协议详解,官方详细了解请参考 RFC 5321 SMTP。
1.1.2 邮件报文
SMTP协议将互联网邮件报文封装在邮件对象中。SMTP协议的邮件对象由两个部分组成:信封和内容。
- 信封实际上是SMTP命令。
- 邮件报文是邮件对象中的内容,包含首部和主体两个部分。
RFC 文档的对报文格式的要求:
- 所有报文都是由 ASCII 码组成;
- 报文由报文行组成,各行之间用回车(CR)、换行(LF)符分隔;
- 报文的长度不能超过 998 个字符;
- 报文行的长度 ≤78 个字符之内(不包括回车换行符);
- 报文中可包括多个首部字段和首部内容;
- 报文可包括一个主体,主体必须用一个空行与其首部分隔;
- 除非需要使用回车与换行符,否则报文中不使用回车与换行符。
1.1.3 SMTP 的扩展协议:MIME
上面的邮件报文格式要求中有说到:所有的报文都是由 ASCII 码组成,那么一些非英语字符消息和二进制文件、图像、声音等非文字消息就都不能在电子邮件中传输。在互联网初期仅传输 ASCII码 还能满足需求,但到了互联网快速发展的图像视频时代就存在局限性了。因此,需要一个辅助性协议帮忙传输报文,它就是MIME。
WiKi:多用途互联网邮件扩展(Multipurpose Internet Mail Extensions,MIME)是一个互联网标准,它扩展了电子邮件标准,使其能够支持:
- 非ASCII字符文本;
- 非文本格式附件(二进位制、声音、图片等);
- 由多部分(multiple parts)组成的消息体;
- 包含非ASCII字元的标头资讯(Header information)。
MIME是通过标准化电子邮件报文的头部的附加域(fields)而实现的。这些头部的附加域,描述新的报文类型的内容和组织形式。主要的附加域有三条:
1 | MIME-Version: 1.0 # MIME版本 |
一个SMTP邮件发送例子
一封MIMI邮件的源码如下(借阮大佬图片一用):
进一步了解可参考 阮一峰-MIME笔记 和 WiKi-MIME。
1.2 POP3
如上 图2-16 所示,SMTP 实现了邮件发送到邮件服务器的 Push 传输,而 POP协议 主要用于支持客户端远程下载和管理在服务器上的电子邮件。
WiKi:邮局协议(Post Office Protocol,POP)是TCP/IP协议族中的一员,主要用于支持使用客户端远程管理在服务器上的电子邮件。最新版本为POP3,全名“Post Office Protocol - Version 3”,而提供了 SSL 加密的 POP3协议 被称为 POP3S。
POP协议的远程管理是电子邮件客户端调连接邮件服务器,并下载所有未阅读的电子邮件。这种离线访问模式是一种存储转发服务,将邮件从邮件服务器端送到个人终端机器上。一旦邮件下载到个人终端上,邮件服务器上的邮件将会被删除。但目前的POP3邮件服务器大都可以“只下载邮件,服务器端并不删除”。
POP3协议 也是通过C/S架构的命令模式完成邮件管理事务:
- 客户端先连接到邮件服务器,建立双方的 POP3 连接;
- 客户端发送命令执行对邮件服务器中的邮件管理。
常用命令参考如下:
1.3 IMAP
因特网信息访问协议(Internet Message Access Protocol,IMAP;以前称作交互邮件访问协议)与 POP协议一样,都是客户端对邮件服务器中邮件的管理,但 IMAP 提供了更加丰富的功能。其主要优点如下:
- 使用IMAP4可以获得更快的响应时间。
- 使用IMAP4可支持多个设备,同时连接到一个邮箱。
- IMAP4支持获取部分或全部 MIME 格式的电子邮件。
- IMAP4支持服务器查看当前的信息状态。
- IMAP4支持在服务器访问多个邮箱。
- IMAP4支持在服务器端搜索电子邮件。
- IMAP4支持一个定义良好的扩展机制。
1.4 其他应用要点
使用邮件发送,邮箱中必须开启 SMTP服务。以 QQ 为例:
相关服务器端口:
为了安全,邮件服务器都要求必须支持 SSL。QQ 邮箱收发邮件使用说明。
二、Java中的邮件服务
2.1 Jakarta Mail
Jakarta Mail(以前称为 JavaMail)是一个Jakarta EEAPI,用于通过SMTP、POP3和IMAP发送和接收电子邮件。Jakarta Mail 内置于Java EE平台中,但也提供了用于Java SE的可选包。—— WiKi 摘取。
WiKi百科介绍及使用Demo,官网详细解析和应用文档, Github 中开源。
Maven 坐标:
1 | <dependency> |
2.2 Spring Boot Mail
Spring Boot Mail 是基于 JavaMail 封装起来的便于使用的邮件依赖,使得用户能够避免接触底层细节,更快更方便的使用邮件服务。—— Spring官方使用介绍
Maven 坐标:
1 | <dependency> |
简单来说,Spring 封装了一个JavaMailService 接口
及其丰富了的具体实现 JavaMailServiceImpl类
。通过JavaMailServiceImpl类
的send()
方法能够执行发送邮件的功能。其中,简单邮件可以通过SimpleMailMessage
来发送邮件,而复杂的邮件(如添加附件)可以借助MimeMessageHelper
来构建MimeMessage
发送邮件。
此外,Spring Boot Mail 还支持发送HTML邮件、图片、模板邮件,具体可简单参考。
简单看一眼源码
JavaMailService接口
JavaMailServiceImpl类
三、Spring Boot 中应用 Mail
3.1 引入和配置
引入和配置
- pom 引入依赖
1 | <dependency> |
- application 配置文件中配置,QQ邮箱为例(参考+修改)
1 | # 字符集编码 默认 UTF-8 |
3.2 简单使用示例
在上述配置完成的基础上,编码如下简单使用的服务示例。引例参考
简单使用
1 |
|
3.3 封装使用示例
封装使用目的是为了方便业务调用,形成单独的发送邮件业务。
- 封装一个邮件类
**MailDO.java**
封装数据对象
1 | public class MailDO { |
- 编写一个服务
**MailService.java**
邮件服务接口
1 | public interface MailService { |
- 编写服务实现类
**MailServiceImpl.java**
接口实现
1 |
|
- 测试
测试
1 | // @Test |