【Java邮件开发】7.邮件的基本格式与编码

时间:2022-01-16 01:00:35


之前我们学会了如何发送一封邮件,那么下面我们来学习如何生成一封有良好格式的邮件。



原来我们发邮件的时候基本上没有添加任何格式,但是一封正规的邮件是需要良好的格式与


排版的,这样邮件处理程序才能从中分析和提取出发件人、收件人、主题和附件等信息。邮


件内容的基本格式和具体细节分别由RFC822文档和MIME协议定义。



我们回顾一下之前我们发出去的那个邮件:


【Java邮件开发】7.邮件的基本格式与编码


我们点击“查看信头”,可以看到我们邮件的原始内容:


【Java邮件开发】7.邮件的基本格式与编码


邮件的原始内容如下:


【Java邮件开发】7.邮件的基本格式与编码


该内容的相关信息解释如下:


(1)“Received”是该邮件的路由信息,即是该邮件经过了多少路由转发,就好像是我们寄信一样,每过一个邮局,就会盖一个邮戳,表示经手该地。


(2)“X-Sender”是发送邮件的主地址。


(3)“X-Auth-ID”是该邮件事发送方的授权ID。


(4)“X-SMAIL-MID”是


(5)“From”和“To”代表邮件的发送地址和接收地址。


(6)“Message-ID”是邮件信息的唯一ID。


(7)“subject”是邮件的主题,内容被编译成相应编码格式(GBK/UTF-8)的代码。


(8)“MIME-Version”是用来声明MIME版本。


(9)“Content-Type”是用来声明邮件内容格式,包含了application/xxxxx、


audio/xxxxx、image/xxxxx、text/xxxxx等。


(10)“Content-Transfer-Encoding”是编码类型,包含了Base64, 7bit, 8bit, 


binaray等类型。


(11)“Authentication-Results”是该邮件发件者的授权反馈信息。


(12)“Date”是邮件的发送日期信息。


(13)最下面的则是我们的新建正文,我们使用了Html格式编码了此字体,即一个红色


字体的“哈哈哈”。



上面是一个比较基础的邮件报文格式。我们会发现我们的中文信息被转换成另外一种


格式了,不再是我们的中文信息,这里发生了什么呢?



我们可以看到上面的邮件源代码信息,可以发现有“Content-Transfer-Encoding”字段,该字段就是指定我们邮件内容


在传输过程中的编码格式。那为什么我们的中文信息需要转码呢?因为在邮件传输过程中,我们的特殊文本信息中往往


在底层中不能正确的被解析(如多国语言字符)。需要将特殊文本转换成一种界定的、统一的、可翻译的底层信息代号,


才可以进行网络传输。还有一种情况,传输的文本信息中有可能含有与计算机指令相同的数据,有时会被当成指令数据,


造成软件系统的判断错误,所以作为正文的信息一般要经过转码才能发送。



这里要说明的是,我们在早期的邮件传输中SMTP协议只能传输ASCII码的数据。这是因为每个ASCII码字符只占用一个字节(8个bit位),


且最高bit位总为0,即ASCII码字符中的有真正意义的信息只是后面的7个低bit位,而传统的SMTP协议又是基于ASCII码字符设计的,因


此一些基于传统SMTP协议设计的SMTP服务器在处理邮件内容时只处理每个字节的7个低bit位进行处理,而将最高bit位忽略不计,此时


的SMTP服务器在处理含有非ASCII码字符的邮件内容时,会出现严重问题。这就限制了在早期邮件中只能出现英文的ASCII码字符,


而不能出现中文字符或二进制数据。



这里解释一下为什么原始SMTP协议不能解析中文,我们通过上面的协议规定可以知道,


SMTP原来只能解析ASCII码,而ASCII码一般一个只占用8bit,而我们的一个汉字是两个


字节(byte),而1字节(byte)等于8位(bit),也就是说一个汉字要占用2*8=16bit,


所以原始的SMTP只可能会将一个汉字解析成两个英文字符,这个时候显然是不正确的。



不过后期为了传输中文、图像以及声音等非ASCII码的数据,人们想到了采用某种编码方式,将非ASCII字符转换成可打印的ASCII字符后再发送,


邮件阅读程序则按照相应的编码方式从邮件中还原原始数据即可。再后来SMTP协议允许直接在邮件中传递二进制数据,而不用对它们进行编码。



所以,我们在信息传输之前,会将正文信息转换成编码对应的格式,当传送到接收方之后,接收方会按照约定的编码格式转换成原文。



这种编码方式我们日常生活中也是常见的,如URL编码。我们在百度上搜索“上海”两个


字,我们会得到搜索结果,但是我们在URL上是看不到“上海”的中文字的,原因是“上


海”两个字已经被编码成其他格式的数据:


【Java邮件开发】7.邮件的基本格式与编码


搜索引擎拿到这些数据后,会按照界定格式转换成原文,然后加载出查询结果。这里所谓的Url编码,通常也被称为百分号编码,是因为它的


编码方式非常简单,使用%百分号加上两位的字符“0123456789ABCDEF”,代表一个字节的十六进制形式。Url编码默认使用的字符集是US-ASCII。


例如a在US-ASCII码中对应的字节是0x61,那么Url编码之后得到的就是%61,我们在地址栏上输入http://g.cn/search?q=%61%62%63,实际


上就等同于在google上搜索abc了。又如@符号在ASCII字符集中对应的字节为0x40,经过Url编码之后得到的是%40。



我们在邮件中常用的两种编码格式为“base64”和“quote-printable”。上面一封邮件


我们选择默认的编码格式为“quote-printable”。



我们给大家简单介绍一下“base64”和“quote-printable”编码:


(1)base64

base64是将二进制数据转换成可打印的ASCII字符的一种最常见的编码方式,它的基本原理


是将一组连续的字节按照6个bit位进行分组,然后对每组数据用一个ASCII字符来表示。


6个bit为最多能表示2的6次方也就是64个数值,因此可以使用64个ASCII字符来对应这64个


数值,这64个ASCII字符为:


ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/


其中每个字符便是的数值就是该字符在上面的排列中的索引号,索引号从0开始编号。假设


在内存中有如下三个连续的字节数组:


[0110,0001][0110,0010][0110,0011]


将它们按照6个bit位进行分组后的形式如下:


[0110,00][01,0110,][0010,01][10,0011]


分组后得到了四组数据,每组数据对应的十进制数值分别为24、22、9、35,它们分别对应


Y、W、J、j这四个字符。所以,对[0110,0001][0110,0010][0110,0011]这三个字节的数据


进行base64编码的最后结果是“YWJj”。


base64编码要求把3个8位字节(即24个bit)的数据转换为4个6位字节(也是24个bit)的


数据,如果原来的8位字节数据个数不能被3整除,其余数只能是1或2,那么如何对余下的1


个或2个8位字节数据进行处理呢?对于这种情况,仍然按照6个bit位对应剩余的字节进行


分组,在最后不够6个bit位的内容后面添加几个为0的bit位来凑成6个bit位。例如,如果


最后剩下的一个8位字节的内容如下:


[0110,0001]


对它进行分组之后的结果如下:


[0110,00][01,0000]


其中最后面四个的0位填充的bit位,所以,最后剩下的这个字节的base64编码结果为“YQ”。


base64编码还有规定,如果编码后的整个结果文本的字符个数不是4的整数倍,那么需要在最


后填充“=”字符来凑成4的倍数,所以,在最后这个字节编码的结果后面还要添加两个“=”字


符,即“YQ==”。显然,如果最后剩下两个8位字节的内容,它可以被编码成3个字符,最后还


需要添加一个“=”字符。对一大段数据进行base64编码时,可以在编码结果中的适当位置加入


回车换行,MIME规范建议base64编码结果中的每行最多76个字符。



(2)Quoted-printable

Quoted-printable也是一种将二进制数据转换成可打印的ASCII字符的编码方式,它对ASCII字符不


进行转换,只对非ASCII字符的数据进行编码转化。每个非ASCII字符的字节数据,都被转换成一个


"="号后跟这个字节的十六进制数据,例如,“ab中国”的Quoted-printable编码结果为“ab=d6=d0=b9=fa”。


显然,由于"="号在Quoted-printable编码中具有的特殊意义,所以,原始数据中的"="号字符也需要进行编码转换,


用“=3d”表示。



对一大段数据进行Quoted-printable编码时,可以在编码结果中的适当位置加入回车换行,在回车换


行前需要额外再加入一个“=”字符,以表示后面的换行是因编码而造成的软回车,而非原始数据中原


有的回车换行。例如,对于下面一段Quoted-printable编码后的数据:



=D5=E2=CA=C7=CD=A8=D0=C5=B5=C4=B3=CC=D0=


=F2, =C7=EB=D6=B8=BD=CC!


在第一行末尾的“=”字符和换行,都是由于编码后生成的。