电子邮件服务原理与搭建

引言

最近在为一个海事项目内网环境搭建邮件服务器,对邮件服务的原理和流程有了更深的认识与梳理。

说起电子邮件,最容易想到的就是使用B/S架构的gmail、163、QQ邮箱收发邮件了。但对于一个企业内部而言,往往不会采用这些第三方公司开放的邮箱应用和服务,这主要是出于对信息保密、灵活定制、维护方便、存储空间等方面的考虑,因此越来越多的企业都开始自己搭建电子邮件服务,使得内部员工可以在同一个内网下使用。

本文首先简单介绍下邮件服务器的工作原理与相关协议,再实际记录下采用开源项目Apache James(Java Apache Mail Enterprise Server)搭建邮件服务器的过程与关键点。

工作原理

邮件服务器其实是若干构件的一个统称,具体细化后其实是由用户代理、发送服务器、接收服务器、邮件发送协议(SMTP)、邮件接收协议(POP3)组成的,如下图所示。

email1

发件流程

  1. 发件人借助用户代理(客户端软件,如outlook、网页版QQ邮箱)起草邮件,点击发送邮件
  2. 用户代理与发送服务器建立TCP可靠连接
  3. 用户代理(充当STMP客户)以STMP协议承载传输至发送服务器(充当STMP服务器)
  4. 发送服务器收到邮件后将他们暂存到一个邮件缓冲队列中,保证后续传输到达顺序的正确性
  5. 发送服务器与目的接收服务器建立TCP可靠连接
  6. 发送服务器(这次充当STMP客户)依次摘取邮件缓冲队列队首邮件,再同样以STMP协议经网络传输至接收方IP下的接收服务器(充当STMP服务器)

收件流程

  1. 收件人打开用户代理(客户端软件)并建立与接收服务器的TCP可靠连接
  2. 用户代理使用POP3(IMAP)协议从接收服务器中读取邮件数据
  3. 将读取到的邮件数据呈现到客户端界面上

当然如果要求更完备一些的话,比如若要允许收发外域邮件,那么还要涉及DNS服务器进行域名解析,若要保证在客户端对邮件的操作结果都时刻同步至服务器,那么还要涉及IMAP交互式邮件存取协议等等。

搭建流程

由上已经知道,邮件服务器的核心是用户代理、发送/接收服务器及它们之间通信的协议,因此在搭建时我采用了一个开源的B/S架构的Web客户端工程作为用户代理,还有一个开源的Apache James基于Spring3.x而写的邮件服务工程作为邮件服务器,他们都是基于Java语言编写的。本文的搭建都是在CentOS 6.3系统上进行的,搭建过程涉及JDK安装、Tomcat安装、MySQL安装、Web服务部署、James服务部署。下面依次记录这几个过程及遇到的问题。

1 JDK安装

Web端的Java环境采用的是jdk1.8.0_131版本,通过从官网下载该版本的tar.gz包,解压到lib目录并配置环境变量,具体命令如下:

#解压jdk
cd /usr/lib/java
tar -zvxf jdk1.8.0_131.tar.gz #事先将压缩包放于此目录
#配置环境变量
vi /etc/profile
export JAVA_HOME=/usr/share/jdk1.8.0_131 
export PATH=$JAVA_HOME/bin:$PATH 
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

2 Tomcat安装

Tomcat作为Web服务的容器应该已经是约定俗成的传统了,虽然有更轻量级的Jetty,但其启动命令很长让我觉得很不优雅所以不怎么爱用。搭建很简单,也是从官网下载Tomcat8(与jdk版本保持一致)的tar.gz包,然后解压到/usr/local目录,具体命令如下:

#解压tomcat
cd /usr/local
tar -zvxf apache-tomcat-8.5.16.tar.gz #事先将压缩包放于此目录
#重命名
mv apache-tomcat-8.5.16 tomcat
#修改tomcat的java环境
cd /usr/local/tomcat/bin
vi catalina.sh
export JAVA_HOME=/usr/lib/java/jdk1.8.0_131
vi setclasspath.sh
export JAVA_HOME=/usr/lib/java/jdk1.8.0_131
#启动
cd /usr/local/tomcat/bin 
./startup.sh
#停止
./shutdown.sh

3 MySQL安装

数据库采用MySQL,安装过程采用yum,先下载安装包源并安装源,然后安装MySQL,命令如下:

# 下载mysql源安装包
shell> wget http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm
# 安装mysql源
shell> yum localinstall mysql57-community-release-el7-8.noarch.rpm
#检查mysql源是否安装成功
yum repolist enabled | grep "mysql.*-community.*"
#安装MySQL
yum install mysql-community-server
#启动
service mysqld start
#停止
service mysqld stop

root用户的初始密码自动生成再了/var/log/mysqld.log下,我们对该密码进行更改方便后续使用,命令如下:

#抓取root初始密码
grep 'temporary password' /var/log/mysqld.log
#修改密码
mysql -uroot -p
set password for 'root'@'localhost'=password('Grenade2017~');

但是之后搭建好Web服务和James服务后,我们发现了两个问题。第一个是插入汉字的时候在数据库中会变成???,这个问题比较明确,解决办法是在/etc/my.cnf文件中的[mysqld]与[client]下加上配置,命令如下:

vi /etc/my.cnf
#[mysqld]下加入
character-set-server=utf8
#[client]下加入
default-character-set=utf8

若是采用jdbc连接的数据库,也可以在jdbc的配置文件对应数据库的那行加上编码配置:

jdbc:mysql://localhost:3306/email?useUnicode=true&characterEncoding=UTF-8

另一个问题比较隐秘但也跟踪出来了,就是Web端在访问表名的时候随意采用大小写,一会儿大写,一会儿小写,一会儿驼峰,部署在Windows系统上时没有问题,但一旦拿到linux下就出表名相关的问题。这是因为Windows平台上的MySQL数据库对表名大小写不敏感,而Linux系统对数据库表名大小写敏感。解决办法很简单,同样是在/etc/my.cnf文件的[mysqld]下加入配置,命令如下:

#[mysqld]下加入
lower_case_table_names = 1 # 0/默认:敏感 1:不敏感

4 Web服务部署

Web工程是采用SSH(Spring-Struts-Hibernate)编写的,需要的jar包都已经引入了,所以在部署的时候我们只需要打好war包放在tomcat的webapps目录下即可。但在打war包之前记得将数据库配置文件jdbc.properties中的连接信息替换成自己的:

jdbc.url=jdbc\:mysql\://localhost\:3306/email
jdbc.username=root
jdbc.password=123456

5 James服务部署

Apache James(Java Apache Mail Enterprise Server)是Apache组织的子项目之一,完全采用纯Java技术开发,实现了SMTP、POP3与NNTP等多种邮件相关协议。James也是一个邮件应用平台,可以通过Mailet扩充其功能,如Mail2SMS、Mail2Fax等。James提供了比较完善的配置方案,尤其是关于邮件内容存储和用户信息存储部分,可以选择在文件、数据库或其他介质中保存。更多详情可参见Apache James Project本文仅给出James服务工程的配置与部署过程,包括如下几个步骤。

1) 数据库修改

James工程的conf目录下有一个database.properties配置文件,可对其中的数据库信息进行配置以适应部署时的生产环境,如下所示:

database.driverClassName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/email
database.username=root
database.password=123456
vendorAdapter.database=MYSQL
openjpa.streaming=false
2) 不依赖外网

如果不做任何修改,在默认情况下启动James服务,会报无法访问外网的错误(在spring-beans.xml文件中)。这个问题是因为James基于Spring3.x,会采用XML文件进行一些beans的配置,而XML文件格式、语法、结构等方面正确性的验证又依赖于XSD文件(XML Schemas Definition)。而spring-beans.xml中配置的XSD文件是通过HTTP协议访问外网服务器上的资源得到的,我们需要把访问路径改为本地XSD文件所在的路径(刚好包含于对应的jar包中)。修改如下所示:

<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:camel="http://camel.apache.org/schema/spring"
       xmlns:amq="http://activemq.apache.org/schema/core" 
       xsi:schemaLocation="
          http://www.springframework.org/schema/beans classpath:/org/springframework/beans/factory/xml/spring-beans-3.0.xsd
          http://camel.apache.org/schema/spring classpath:/camel-spring.xsd
          http://activemq.apache.org/schema/core classpath:/activemq.xsd">
3) 后台启动

James服务的启动很简单,进入bin目录,输入下面命令:

cd /usr/local/james/bin
./run.sh

但这样做存在一个问题:因为通常我们采用ssh远程访问服务器,当启动服务后只要关闭终端,James服务对应的进程也会退出。因此我们需要在命令末尾加上&保证是后台启动,不占用终端界面进程:

./run.sh &

但这样做以后会发现退出终端后进程还是断了,网上查了一番后才发现,原来真正的后台启动需要采用nohup命令,它可以忽略挂起、退出等信号,如下所示:

nohup ./run.sh &

结语

本文仅是对电子邮件服务的基本原理和搭建给出了一个大概的整理,但仍然缺少对邮件服务器更底层原理性东西的阐述,如SMTP协议、POP3协议、服务器推技术等,若感兴趣可以查阅相关资料进行阅读。

关于本文用到的两个工程的代码可以在我的GitHub中的email仓库下载,其中文件夹james-server-container-spring-3.0-M2就是James工程,剩余部分都是Web端工程的内容,可导入到idea/eclipse打成war包部署。