1、引言
PDA 即个人数字助理, 是集数字、文字和图像于一体的高科技产品;是一种用于处理个人事务的数字设备。其产品功能主要表现在记录和处理个人信息如通讯录、名片册、计算器、日程安排、约会信息提示等。随着互联网越来越深入到人们的生活中,用户对PDA设备的要求也越来越高。用户希望PDA能通过接入Internet来收发电子邮件,还可以通过电子邮件方式获取各种增值服务。如通过电子邮件接收财经(股票、金融)、旅游、气象等等信息。要使PDA成为一个实用的信息终端,电子邮件系统是其不可缺少的重要组成部分。
2、PDA电子邮件收发系统
2.1 系统功能
PDA电子邮件系统的主体是一组基于TCP/IP协议的电子邮件收发程序。运行在PDA上的邮件收发程序通过调制解调器拨号连接到ISP,然后接入Internet。通过Internet,PDA可以和邮件接收服务器建立连接,使用POP3协议来接收邮件;也可以和邮件发送服务器建立连接,使用SMTP协议来发送邮件。
2.2 硬件平台
我们所使用的PDA硬件平台是基于ADZ80芯片的,具有50MHZ的运行速度,它除了输入输出设备、CPU、存储器等基本配置外,还包括了自带的LCD控制屏、触摸屏控制器、时钟、中断控制器等外设,因此它是一款性能优异、价格低廉的PDA设备。
PDA是一个特殊的设备,它所具有的RAM资源相对PC而言实在太少,由于系统结构的限制,代码与数据块的大小超过64K时必须进行段切换。由于PDA电子邮件系统是利用ANSI C来开发的,而用C语言来实现段切换又相当困难,所以必须把电子邮件系统的最终代码和数据分别安排在两个不同的段中,且每个段不能超过64K。另外,PDA设备是一个没有文件管理功能和内存管理功能的系统,因而不能根据邮件的大小来动态地申请邮件存储空间,只能给单一邮件分配一个固定大小的8K缓冲区。当邮件超过8K时,就放弃该邮件的接收。
3、电子邮件识别与封装模块的设计与实现
3.1、识别与封装模块的功能
PDA电子邮件收发系统的邮件识别功能是指收发程序能识别从邮件服务器中收取的邮件并从中抽取出用户所需要的邮件内容,再交给PDA主控程序进行显示;邮件封装的功能是指在PDA调用SMTP协议发送邮件之前,把用户希望发送的信息封装成RFC822、RFC1521所定义的MIME结构。
3.2、接收邮件大小的控制。
PDA原型机只提供8K的邮件接收缓冲区,当邮件超过8K的时候,如果要接收该邮件,则会使缓冲区溢出,从而导致系统异常。所以在接收整个邮件内容之前,必须判断该邮件的大小是否超过8K,如果超过8K的邮件,就放弃该邮件的接收。我们的解决方案是首先利用POP3协议的LIST命令,该命令返回用户邮箱中的邮件目录及每个邮件的大小,然后利用其扩展命令TOP取得单一邮件的摘要信息。将这些邮件目录内容返回给主控程序,PDA主控程序把它显示在PDA屏幕上,供用户选择其中的部分或全部进行接收。当用户选择了超过8K的邮件时,接收模块跳过该邮件转而接收下一个。
3.3、识别与生成模块与PDA主控程序的数据接口
PDA原型机没有操作系统,只有一个控制系统运行的主控程序。对于电子邮件系统而言,该主控程序将用户输入后需要发送的邮件交给电子邮件系统;并从邮件系统中输入接收到的邮件,显示在PDA屏幕上,供用户浏览。为了完成与PDA主控程序的交互,电子邮件系统定义了如下的邮件数据交换接口:
序号\0发信人\0收信人\0发信日期\0主题\0邮件大小\0有否附件\0正文大小\0正文内容\0附件大小\0附件内容\0\0xFF(如果存在附件)。
该结构有如下优点:(1)节省空间。在这一结构中仅保留了最需要的邮件内容,省略了大多数不需要的或邮件代理自行添加的邮件头信息。前面讲过数据块的内容最好放在一个段中,所以定义这样的结构可以为系统节省一定的空间。(2)易于扩展。该结构只定义了一个附件,如果邮件存在多个附件,只需在结构末尾继续添加附件大小\0附件内容\0,并在最后用\0xFF来结束即可。(3)便于处理。用字符\0来分隔各数据域,很容易使用各种字符串库函数,用ANSI C来开发非常方便。
为了保存接收到的邮件和方便同PDA主控程序的交互,系统定义了二个大小为8K的缓冲区。为了叙述方便,分别称为缓冲区A和缓冲区B。邮件缓冲区A用来存放从邮件服务器中收取的按MIME格式定义的邮件内容,称为邮件源码;邮件缓冲区B用来存放经过识别后的邮件内容,称为已识别邮件,邮件收发程序使用该缓冲区同主控程序进行数据交换。
对缓冲区A中的邮件源码进行识别后的邮件按照预先定义的数据结构写入邮件缓冲区B,但是在写入的过程中,必须不断地判断邮件缓冲区B是否超过8K,以免发生系统异常。封装邮件同样也存在着缓冲区溢出的问题,用户如果需要发送一封电子邮件,用户在PDA界面上写好邮件内容,主控程序按照预先定义的数据结构保存在缓冲区B中,邮件封装模块将B中的内容封装成邮件源码,写入发送缓冲区A中,在写入发送缓冲区的时候,同样需要判断是否超过8K。鉴于上述情况,该模块定义了一个全局变量WriteBufferSize,用来保存已经写入到邮件接收或发送缓冲区的大小,当WriteBufferSize超过8K的时候,停止向邮件缓冲区写入内容,并返回一个错误编号,用以指示邮件缓冲区已溢出。
3.4、邮件的结构。
在介绍邮件识别之前,先简要地介绍一下RFC所定义的MIME邮件结构。一封邮件是由邮件头、邮件体组成。邮件头、邮件体是一系列的文本行,每一行以回车换行(CRLF)符结束。在邮件头之后是一个空行,用来分隔邮件头与邮件体。邮件头是由若干字段组成,每个字段由一行或多行组成。对于跨越多行的字段,非首行必须以空格键(Backspace)或表格键(Tab)开始,称为续行(Continuation Line)。每个邮件头字段由以下几部分组成:字段名称、可选的空格、一个冒号、可选的注解空格和一个可选的字段体,但通常在字段名称与冒号之间没有空格。
邮件体是用户所需要传输的实际内容。目前的电子邮件不仅能够包含文本,还可以附加图像、视频、声音等多媒体信息。MIME规定用字段Content-Type来指明邮件体的类型。该协议规定了7种主要类型,分别为text、image、audio、video、application、message、multipart,每种类型又划分成若干种子类型。因此类型说明是由主类型/子类型组合而成。
分析目前的电子邮件,存在较多的主要有两种类型text、multipart。Text类型说明该邮件仅仅包含正文,它主要有plain、html、xml三种子类型,并且有个重要参数charset,用来指明文本内容所采用的编码字符集。Multipart把多种类型的数据组合到一个邮件中,这就是通常所指的包含附件的邮件,它有个重要参数Boundary,该参数定义了一个边界分隔字符串。该字符串与两个前导横线结合起来,组成边界行,用来分隔组成该邮件的各种子类型。在最后一个子类型后,有一个结束边界行,它由常规的边界字符串后跟两个横线组成,用以表示整个邮件体的结束。组成multipart的每种类型,格式上同主邮件相似,同样存在邮件头与邮件体,邮件头与邮件体之间以空行分隔,这种邮件称为嵌套邮件。
尽管可能存在个别的邮件为四层嵌套(主要是在发送非常大的邮件时可能出现的情况),但PDA原型平台仅能处理8K以内的文本和BMP图像,因此我们可以假定电子邮件最多有三层嵌套关系,超过三层嵌套关系的邮件不予识别。因此我们考虑五种情况:(1)邮件内容是纯文本或超文本中的一种;(2)邮件内容可以同时包含纯文本和超文本;(3)邮件内容中含有附件,但只能包含纯文本或超文本中的一种;(4)、(5)邮件内容中含有附件,既可以包含纯文本,也可以包含超文本。
以下用森林数据结构来直观地表示这五种电子邮件的嵌套结构:
注 :
(1)上述的类型是Content-Type字段定义的类型,不仅仅可以为这些类型,还可以为其他类型,如message/rfc822等等。
(2)上图中仅列出了二个附件,实际邮件中可能存在多个附件。
3.5、邮件头的识别
无论收取邮件摘要还是整个邮件,都必须对邮件头进行处理,因为邮件头中不仅包含了邮件的发送者、邮件的接收者、发送日期和邮件主题等邮件的基本信息,还包含了许多与邮件体有关的重要信息。所以对邮件头的识别是该模块不可缺少的一部分。
针对MIME的邮件结构,邮件头的识别算法如下:扫描邮件源码,当扫描到CRLF字符时,表明邮件头字段一行的结束,再判断下一行是否以BACKSPACE 或者 TAB键开始,如果是,继续扫描;直到一个邮件头字段的结束,并从中取出字段名称,判断该字段是否需要,若需要,则保存该字段值;否则继续扫描邮件源码,直至遇到两个连续的CRLF,这时才表示邮件头结束。
3.6、 邮件体的识别
原型PDA由于平台限制,不能处理所有类型的数据,只能处理纯文本和BMP类型的图像附件。本模块定义了一个指针数组来存放纯文本和BMP图像的开始位置。由于只考虑邮件嵌套层次最多为三层的情况,因此算法中定义了二个变量,用来保存嵌套邮件的边界字符串,对应于算法中的Boundary参数和SubBoundary参数。如果把3.4中所描述的树结构的根结点作为第一层,则Boundary参数保存的是最外层的邮件边界字符串,而SubBoundary参数保存的是第二层的邮件边界字符串。
针对3.4的五种情况和MIME格式,我们发现在邮件体中找出纯文本和BMP图像的过程,实际上就是在图3.4的五棵树中找到一个匹配树,并从相应的叶结点中抽取出我们所需要的邮件内容。为了叙述方便,用当前结点来表示正在被处理的子邮件体,识别算法如下:
(1) 程序首先识别邮件头,再分析邮件体的类型,并与森林中的五棵树的根结点进行匹配,如果没有一棵树的根结点与邮件体的类型匹配,则返回出错信息,表明该邮件体不能被识别,从而结束程序;如果找到匹配,则转到下一步处理。
(2) 设置当前结点为该树的根结点。
(3) 判断当前结点是否是叶子结点,如果不是,则取得Boundary边界字符串,并转到步骤(4)处理;否则,判断当前结点的Content-Type字段的值是否为text/plain,如果是,保存该内容的起始位置和传输编码格式,程序结束,否则,程序返回不能识别的错误。
(4) 判断以Boundary字符串为边界的邮件体是否已经被识别,如果是,程序结束返回;否则,取出以Boundary参数值为边界的下一个子邮件体,并把它赋给当前结点,转到下一步处理。
(5) 判断当前结点是否是叶子节点,如果不是,则取出SubBoundary字符串,再转到下一步处理;如果是,转到步骤(7)处理。
(6) 判断以SubBoundary字符串为边界的邮件体是否已经被识别,如果是,转到步骤(4)处理;否则取出以SubBoundary字符串为边界的下一个子邮件体,并把它赋给当前结点,转到下一步处理。
(7) 判断当前结点的Content-Type字段的值是否为text/plain或者image/bmp,如果是,则保存该内容的起始位置和传输编码格式,再判断当前结点的边界是否等于Boundary字符串,如果是,转到步骤(4)处理,否则,转到步骤(6)处理。
当查找程序返回后,判断保存Text与BMP图像起始位置的指针数组是否为空,若为空,返回错误编号,表示该邮件中没有我们所能识别的内容;否则,按照3.3所定义的数据结构先将邮件头写入邮件接收缓冲区,再将邮件内容按照传输编码格式进行相应的解码,并把它写入到接收缓冲区中。
3.7、邮件的封装
邮件封装的功能是当用户书写好一封电子邮件后,必须通过邮件编码,把邮件内容编码成一定的格式,再按MIME规定的结构封装成邮件源码,然后调用SMTP协议发送到相应的邮件发送服务器。
对于邮件封装,需要考虑二种情况:(1)邮件体内容仅有纯文本组成;(2)邮件体有纯文本和BMP图像附件组成。
对于邮件头,只需按照邮件的字段结构,给字段内容加上相应的字段名称,并根据邮件内容,定义Content-Type字段:当邮件内容仅为纯文本时,这时Content-Type为text/plain ;当邮件内容由纯文本和BMP图像组成时,Content-Type采用multipart/mixed,下设两个子类型,一个为text/plain,另一个为image/bmp。纯文本与附件内容均采用base64编码。
4、结束语
本文介绍了PDA电子邮件收发系统邮件识别与封装模块的设计与实现,重点讲述了如何在存储资源有限、缺乏系统支持的环境下,对邮件进行识别与封装,以满足特定的应用需求。本模块已经成功地运行在一款PDA产品上。但是本模块只能接收小于8K的邮件,而在实际情况中,有大量文本或带有附件的电子邮件一般都超过8K。因此我们正在着手进行两个方面的工作,一方面是扩充邮件缓冲区的大小至64K,从而可以接收64K以内的邮件,这在一定程度上缓解了这一矛盾;另一方面,我们也在开发一个与本模块相对应的智能邮件接收代理,PDA用户可通过代理来接收邮件,该代理可以代表用户从真正的邮件服务器上收取邮件,并对邮件进行过滤、转换,去除PDA无法接收的内容,将大邮件分割成PDA能收取的小邮件,从而为PDA的邮件接收提供更好的条件。
本文地址:http://com.8s8s.com/it/it26421.htm