Home > Backend Development > PHP Tutorial > JAVA+PHP+阿里云组件纯手工实现POP、SMTP、IMAP开发邮件服务器(2)

JAVA+PHP+阿里云组件纯手工实现POP、SMTP、IMAP开发邮件服务器(2)

WBOY
Release: 2016-06-13 12:27:52
Original
1258 people have browsed it

JAVA+PHP+阿里云组件纯手工实现POP、SMTP、IMAP开发邮件服务器(二)

 java开发邮件服务器的接收模块

    用java建立socket服务端,监听端口25,实现SMTP协议。即可完成邮件服务器的接收模块。

    这里要注意的是,SMTP协议其实可以分为两种。一种是你用手机、PC等客户端发邮件到邮件服务商的服务器的时候用的SMTP协议,这一类是需要登录验证的。一种是邮件服务商之间传递邮件的SMTP协议,此类协议是不需要登录的。比如你用Foxmail上你的QQ邮箱发送了一封邮件到163的邮箱。过程是这样的:

  1. 邮件从Foxmail通过SMTP协议发送到QQ邮箱的服务器。

  2. QQ邮箱的服务器通过SMTP协议将邮件投递到163的邮件服务器。

  3. 对方通过IMAP或者POP协议从163邮箱服务器拿到这封邮件。

 

    本文将实现的是不需要登录的SMTP协议。下面展示该协议:

C:telnet smtp.126.com 25   /* 以telnet方式连接126邮件服务器 */S:220 126.com Anti-spam GT for Coremail System (126com[071018]) /* 220为响应数字,其后的为欢迎信息,会应服务器不同而不同*/C:HELO smtp.126.com /* HELO 后用来填写返回域名(具体含义请参阅RFC821),但该命令并不检查后面的参数*/S:250 OKC: MAIL FROM: [email protected] /* 发送者邮箱 */S:250 … ./* “…”代表省略了一些可读信息 */C:RCPT TO: [email protected]/* 接收者邮箱 */S:250 … ./* “…”代表省略了一些可读信息 */C:DATA  /* 请求发送数据 */S:354 Enter mail, end with "." on a line by itselfC:Enjoy Protocol StudingC:.S:250 Message sentC:QUIT /* 退出连接 */S:221 Bye
Copy after login

所以,我们建立Socket服务端,并监听25端口后,只要检测客户端发来的信息,并给出相应的回复,即可完成邮件的接收。具体java实现socket可以百度。下面解释几个重要的语法用法:

  1. MAIL FROM:这里后面跟的参数是发送者的邮箱。当你接收到邮件的正文(eml后缀的文件),并解析后,里面还有一个标签标记的是邮件的发件人。这两个参数理论上应该是一样的。当不一样的时候,QQ邮箱等会显示“该邮件由***代发”。所以我们接收邮件的时候,也要检测一下这两个邮件地址是否相同,避免有人恶意欺骗。另外,最重要的是SPF检查,我们下一篇单独介绍。SPF在反垃圾邮件方面的作用很大。比如MAIL FROM:[email protected] ,我们就会去查cliyun.com的SPF记录,看看里面有没有包含当前连接我们socket服务端的客户端的IP地址,如果不包含,说明该客户端是欺骗我们的。

  2. RCPT TO:这里标记的是收件人邮箱。当你完成邮件服务器的时候,你会发现网络上很多恶意的程序在大肆发送垃圾邮件,会给本来不存在的用户邮箱所在的服务器发送垃圾邮件。比如你的域名是cliyun.com,但是你的邮箱用户里,[email protected]??[email protected]?,应该把该邮件发送者的IP加入敏感列表,利用算法进行过滤。

  3. DATA:DATA之后,就是邮件EML正文,知道遇到单独一行的 . 结束。通常我们在解析这份EML文件之前,会把它本地保存一下,方便解析出问题后(比如有文字乱码),可以在调整代码后再次解析。

邮件接收器的重点是EML文件的解析。通常使用javamail模块来解析,网上教程很多,但是很多都是不全的。因为一个完整的eml文件,含有很多信息。比如标题、正文、发件人、收件人、抄送、回复地址、附件等等。下面贴一段比较好的解析类:

import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.InputStream;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Properties;import javax.mail.BodyPart;import javax.mail.Flags;import javax.mail.Folder;import javax.mail.Message;import javax.mail.MessagingException;import javax.mail.Multipart;import javax.mail.Part;import javax.mail.Session;import javax.mail.Store;import javax.mail.internet.InternetAddress;import javax.mail.internet.MimeMessage;import javax.mail.internet.MimeUtility; /** * @author yh *  */public class ShowMail {     private MimeMessage mimeMessage = null;    private String saveAttachPath = ""; // 附件下载后的存放目录    private StringBuffer bodyText = new StringBuffer(); // 存放邮件内容的StringBuffer对象    private String dateFormat = "yy-MM-dd HH:mm"; // 默认的日前显示格式     /**     * 构造函数,初始化一个MimeMessage对象     */    public ShowMail() {    }     public ShowMail(MimeMessage mimeMessage) {        this.mimeMessage = mimeMessage;        System.out.println("创建一个ReceiveEmail对象....");    }     public void setMimeMessage(MimeMessage mimeMessage) {        this.mimeMessage = mimeMessage;        System.out.println("设置一个MimeMessage对象...");    }     /**     *  * 获得发件人的地址和姓名       */    public String getFrom() throws Exception {        InternetAddress address[] = (InternetAddress[]) mimeMessage.getFrom();        String from = address[0].getAddress();        if (from == null) {            from = "";            System.out.println("无法知道发送者.");        }        String personal = address[0].getPersonal();         if (personal == null) {            personal = "";            System.out.println("无法知道发送者的姓名.");        }         String fromAddr = null;        if (personal != null || from != null) {            fromAddr = personal + "<" + from + ">";            System.out.println("发送者是:" + fromAddr);        } else {            System.out.println("无法获得发送者信息.");        }        return fromAddr;    }     /**     *  * 获得邮件的收件人,抄送,和密送的地址和姓名,根据所传递的参数的不同     *  * "to"----收件人 "cc"---抄送人地址 "bcc"---密送人地址       */    public String getMailAddress(String type) throws Exception {        String mailAddr = "";        String addType = type.toUpperCase();         InternetAddress[] address = null;        if (addType.equals("TO") || addType.equals("CC")                || addType.equals("BCC")) {             if (addType.equals("TO")) {                address = (InternetAddress[]) mimeMessage                        .getRecipients(Message.RecipientType.TO);            } else if (addType.equals("CC")) {                address = (InternetAddress[]) mimeMessage                        .getRecipients(Message.RecipientType.CC);            } else {                address = (InternetAddress[]) mimeMessage                        .getRecipients(Message.RecipientType.BCC);            }             if (address != null) {                for (int i = 0; i < address.length; i++) {                    String emailAddr = address[i].getAddress();                    if (emailAddr == null) {                        emailAddr = "";                    } else {                        System.out.println("转换之前的emailAddr: " + emailAddr);                        emailAddr = MimeUtility.decodeText(emailAddr);                        System.out.println("转换之后的emailAddr: " + emailAddr);                    }                    String personal = address[i].getPersonal();                    if (personal == null) {                        personal = "";                    } else {                        System.out.println("转换之前的personal: " + personal);                        personal = MimeUtility.decodeText(personal);                        System.out.println("转换之后的personal: " + personal);                    }                    String compositeto = personal + "<" + emailAddr + ">";                    System.out.println("完整的邮件地址:" + compositeto);                    mailAddr += "," + compositeto;                }                mailAddr = mailAddr.substring(1);            }        } else {            throw new Exception("错误的电子邮件类型!");        }        return mailAddr;    }     /**     *  * 获得邮件主题       */    public String getSubject() throws MessagingException {        String subject = "";        try {            System.out.println("转换前的subject:" + mimeMessage.getSubject());            subject = MimeUtility.decodeText(mimeMessage.getSubject());            System.out.println("转换后的subject: " + mimeMessage.getSubject());            if (subject == null) {                subject = "";            }        } catch (Exception exce) {            exce.printStackTrace();        }        return subject;    }     /**     *  * 获得邮件发送日期       */    public String getSentDate() throws Exception {        Date sentDate = mimeMessage.getSentDate();        System.out.println("发送日期 原始类型: " + dateFormat);        SimpleDateFormat format = new SimpleDateFormat(dateFormat);        String strSentDate = format.format(sentDate);        System.out.println("发送日期 可读类型: " + strSentDate);        return strSentDate;    }     /**     *  * 获得邮件正文内容       */    public String getBodyText() {        return bodyText.toString();    }     /**     *   * 解析邮件,把得到的邮件内容保存到一个StringBuffer对象中,解析邮件     *   * 主要是根据MimeType类型的不同执行不同的操作,一步一步的解析        */     public void getMailContent(Part part) throws Exception {         String contentType = part.getContentType();        // 获得邮件的MimeType类型        System.out.println("邮件的MimeType类型: " + contentType);         int nameIndex = contentType.indexOf("name");         boolean conName = false;         if (nameIndex != -1) {            conName = true;        }         System.out.println("邮件内容的类型: " + contentType);         if (part.isMimeType("text/plain") && conName == false) {            // text/plain 类型            bodyText.append((String) part.getContent());        } else if (part.isMimeType("text/html") && conName == false) {            // text/html 类型            bodyText.append((String) part.getContent());        } else if (part.isMimeType("multipart/*")) {            // multipart/*            Multipart multipart = (Multipart) part.getContent();            int counts = multipart.getCount();            for (int i = 0; i < counts; i++) {                getMailContent(multipart.getBodyPart(i));            }        } else if (part.isMimeType("message/rfc822")) {            // message/rfc822            getMailContent((Part) part.getContent());        } else {         }    }     /**     *   * 判断此邮件是否需要回执,如果需要回执返回"true",否则返回"false"       */    public boolean getReplySign() throws MessagingException {         boolean replySign = false;         String needReply[] = mimeMessage                .getHeader("Disposition-Notification-To");         if (needReply != null) {            replySign = true;        }        if (replySign) {            System.out.println("该邮件需要回复");        } else {            System.out.println("该邮件不需要回复");        }        return replySign;    }     /**     * 获得此邮件的Message-ID        */    public String getMessageId() throws MessagingException {        String messageID = mimeMessage.getMessageID();        System.out.println("邮件ID: " + messageID);        return messageID;    }     /**     * 判断此邮件是否已读,如果未读返回false,反之返回true     */    public boolean isNew() throws MessagingException {        boolean isNew = false;        Flags flags = ((Message) mimeMessage).getFlags();        Flags.Flag[] flag = flags.getSystemFlags();        System.out.println("flags的长度: " + flag.length);        for (int i = 0; i < flag.length; i++) {            if (flag[i] == Flags.Flag.SEEN) {                isNew = true;                System.out.println("seen email...");                // break;            }        }        return isNew;    }     /**     * 判断此邮件是否包含附件     */    public boolean isContainAttach(Part part) throws Exception {        boolean attachFlag = false;        // String contentType = part.getContentType();        if (part.isMimeType("multipart/*")) {            Multipart mp = (Multipart) part.getContent();            for (int i = 0; i < mp.getCount(); i++) {                BodyPart mPart = mp.getBodyPart(i);                String disposition = mPart.getDisposition();                if ((disposition != null)                        && ((disposition.equals(Part.ATTACHMENT)) || (disposition                                .equals(Part.INLINE))))                    attachFlag = true;                else if (mPart.isMimeType("multipart/*")) {                    attachFlag = isContainAttach((Part) mPart);                } else {                    String conType = mPart.getContentType();                     if (conType.toLowerCase().indexOf("application") != -1)                        attachFlag = true;                    if (conType.toLowerCase().indexOf("name") != -1)                        attachFlag = true;                }            }        } else if (part.isMimeType("message/rfc822")) {            attachFlag = isContainAttach((Part) part.getContent());        }        return attachFlag;    }     /**     *  * 保存附件       */     public void saveAttachMent(Part part) throws Exception {        String fileName = "";        if (part.isMimeType("multipart/*")) {            Multipart mp = (Multipart) part.getContent();            for (int i = 0; i < mp.getCount(); i++) {                BodyPart mPart = mp.getBodyPart(i);                String disposition = mPart.getDisposition();                if ((disposition != null)                        && ((disposition.equals(Part.ATTACHMENT)) || (disposition                                .equals(Part.INLINE)))) {                    fileName = mPart.getFileName();                    if (fileName.toLowerCase().indexOf("gb2312") != -1) {                        fileName = MimeUtility.decodeText(fileName);                    }                    saveFile(fileName, mPart.getInputStream());                } else if (mPart.isMimeType("multipart/*")) {                    saveAttachMent(mPart);                } else {                    fileName = mPart.getFileName();                    if ((fileName != null)                            && (fileName.toLowerCase().indexOf("GB2312") != -1)) {                        fileName = MimeUtility.decodeText(fileName);                        saveFile(fileName, mPart.getInputStream());                    }                }            }        } else if (part.isMimeType("message/rfc822")) {            saveAttachMent((Part) part.getContent());        }    }     /**     * 设置附件存放路径     */    public void setAttachPath(String attachPath) {        this.saveAttachPath = attachPath;    }     /**     *  * 设置日期显示格式       */    public void setDateFormat(String format) throws Exception {        this.dateFormat = format;    }     /**     *  * 获得附件存放路径       */    public String getAttachPath() {        return saveAttachPath;    }     /**     *  * 真正的保存附件到指定目录里       */    private void saveFile(String fileName, InputStream in) throws Exception {        String osName = System.getProperty("os.name");        String storeDir = getAttachPath();        String separator = "";        if (osName == null) {            osName = "";        }        if (osName.toLowerCase().indexOf("win") != -1) {            separator = "\\";            if (storeDir == null || storeDir.equals(""))                storeDir = "c:\\tmp";        } else {            separator = "/";            storeDir = "/tmp";        }        File storeFile = new File(storeDir + separator + fileName);        System.out.println("附件的保存地址: " + storeFile.toString());        // for(int i=0;storefile.exists();i++){        // storefile = new File(storedir+separator+fileName+i);        // }        BufferedOutputStream bos = null;        BufferedInputStream bis = null;         try {            bos = new BufferedOutputStream(new FileOutputStream(storeFile));            bis = new BufferedInputStream(in);            int c;            while ((c = bis.read()) != -1) {                bos.write(c);                bos.flush();            }        } catch (Exception exception) {            exception.printStackTrace();            throw new Exception("文件保存失败!");        } finally {            bos.close();            bis.close();        }    }     /**     * ReceiveEmail类测试     */    public static void main(String args[]) throws Exception {        String host = "pop.sina.com";        String username = "***";        String password = "***";         Properties props = new Properties();        Session session = Session.getDefaultInstance(props, null);         Store store = session.getStore("pop3");        store.connect(host, username, password);         Folder folder = store.getFolder("INBOX");        folder.open(Folder.READ_ONLY);        Message message[] = folder.getMessages();        System.out.println("邮件数量: " + message.length);        ShowMail re = null;         for (int i = 0; i < message.length; i++) {            re = new ShowMail((MimeMessage) message[i]);            System.out.println("邮件 " + i + " 主题: " + re.getSubject());            System.out.println("邮件 " + i + " 发送时间: " + re.getSentDate());            System.out.println("邮件 " + i + " 是否需要回复: " + re.getReplySign());            System.out.println("邮件 " + i + " 是否已读: " + re.isNew());            System.out.println("邮件 " + i + " 是否包含附件: "                    + re.isContainAttach((Part) message[i]));            System.out.println("邮件 " + i + " 发送人地址: " + re.getFrom());            System.out                    .println("邮件 " + i + " 收信人地址: " + re.getMailAddress("to"));            System.out.println("邮件 " + i + " 抄送: " + re.getMailAddress("cc"));            System.out.println("邮件 " + i + " 暗抄: " + re.getMailAddress("bcc"));            re.setDateFormat("yy年MM月dd日 HH:mm");            System.out.println("邮件 " + i + " 发送时间: " + re.getSentDate());            System.out.println("邮件 " + i + " 邮件ID: " + re.getMessageId());            re.getMailContent((Part) message[i]);            System.out.println("邮件 " + i + " 正文内容: \r\n" + re.getBodyText());            re.setAttachPath("e:\\");            re.saveAttachMent((Part) message[i]);        }    }}
Copy after login

 

    解析完EML文件,将信息存入数据库后,邮件服务器的接收器就完成工作了。实际生产环境中,为了项目的健壮性,接收器会有很多个,比如颗粒云邮箱的邮件接收器有15台,一封邮件发过来,怎么知道该投递到哪台服务器呢?这是取决于域名的MX记录的。比如qq.com的MX记录里有很多台邮件接收服务器,他们每个的优先级不同。一般都是根据优先级由高到低投递邮件。当一台投递失败后,就切换到另一台。如何通过命令行来查看一个域名的MX记录呢?

QQ截图20151127195216.jpg

这部分是邮件服务器的发件器需要做的工作。我们会在后面的博文中,介绍如何用JAVA检测邮件地址的MX记录。

原文地址:JAVA+PHP+阿里云组件纯手工实现POP、SMTP、IMAP开发邮件服务器(二)
Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template