• Java与邮件系统交互之使用Socket验证邮箱是否存在


      最近遇到一个需求:需要验证用户填写的邮箱地址是否真实存在,是否可达。和普通的正则表达式不同,他要求尝试链接目标邮箱服务器并请求校验目标邮箱是否存在。

    先来了解


      DNS之MX记录

      对于DNS不了解的,请移步百度搜索。

      DNS中除了A记录(域名-IP映射)之外,还有MX记录(邮件交换记录),CNAME记录(别名,咱不管)。

      MX记录就是为了在发送邮件时使用友好域名规则,比如我们发送到QQ邮箱xxx@qq.com。我们填写地址是到“qq.com”,但实际上可能服务器地址千奇百怪/而且许有多个。在设置DNS时可以顺带设置MX记录。

      说白了,“qq.com”只是域名,做HTTP请求响应地址,你邮件能发到Tomcat上吗?那我们发到“qq.com”上面的邮件哪里去了,我们把自己想象成一个邮件服务器,你的用户让你给xxx@qq.com发一封信,你如何操作?找mx记录是必要的。

      SMTP之纯Socket访问

      对于SMTP不了解或Java Socket不了解的,请移步百度搜索。

      邮件协议是匿名协议,我们通过SMTP协议可以让邮件服务器来验证目标地址是否真实存在。

    代码实现


      由以上介绍可知:通过DNS中MX记录可以找到邮件服务器地址,通过SMTP协议可以让邮件服务器验证目标邮箱地址的真实性。

      那么我们就来进行编码实现。

      首先需要查询DNS,这个需要用到一个Java查询DNS的组件dnsjava(下载),自己写太麻烦。

     1 // 查找mx记录
     2             Record[] mxRecords = new Lookup(host, Type.MX).run();
     3             if(ArrayUtils.isEmpty(mxRecords)) return false;
     4             // 邮件服务器地址
     5             String mxHost = ((MXRecord)mxRecords[0]).getTarget().toString();
     6             if(mxRecords.length > 1) { // 优先级排序
     7                 List<Record> arrRecords = new ArrayList<Record>();
     8                 Collections.addAll(arrRecords, mxRecords);
     9                 Collections.sort(arrRecords, new Comparator<Record>() {
    10                     
    11                     public int compare(Record o1, Record o2) {
    12                         return new CompareToBuilder().append(((MXRecord)o1).getPriority(), ((MXRecord)o2).getPriority()).toComparison();
    13                     }
    14                     
    15                 });
    16                 mxHost = ((MXRecord)arrRecords.get(0)).getTarget().toString();
    17             }
    mx

      (上面代码中的生僻类型就是来自dnsjava,我使用apache-commons组件来判断空值和构建排序,return false是在查询失败时。)

      接下来通过优先级排序(mx记录有这个属性)取第一个邮件服务器地址来链接。

      这里的主要代码是通过SMTP发送RCPT TO指令来指定邮件接收方,如果这个地址存在则服务器返回成功状态,如果没有的话则返回错误指令。

      1 import java.io.BufferedInputStream;
      2 import java.io.BufferedReader;
      3 import java.io.BufferedWriter;
      4 import java.io.IOException;
      5 import java.io.InputStreamReader;
      6 import java.io.OutputStreamWriter;
      7 import java.net.InetSocketAddress;
      8 import java.net.Socket;
      9 import java.util.ArrayList;
     10 import java.util.Collections;
     11 import java.util.Comparator;
     12 import java.util.List;
     13 
     14 import org.apache.commons.lang.ArrayUtils;
     15 import org.apache.commons.lang.StringUtils;
     16 import org.apache.commons.lang.builder.CompareToBuilder;
     17 import org.xbill.DNS.Lookup;
     18 import org.xbill.DNS.MXRecord;
     19 import org.xbill.DNS.Record;
     20 import org.xbill.DNS.TextParseException;
     21 import org.xbill.DNS.Type;
     22 
     23 
     24 public class MailValid {
     25 
     26     public static void main(String[] args) {
     27         System.out.println(new MailValid().valid("100582783@qq.com", "jootmir.org"));
     28     }
     29     
     30     /**
     31      * 验证邮箱是否存在
     32      * <br>
     33      * 由于要读取IO,会造成线程阻塞
     34      * 
     35      * @param toMail
     36      *         要验证的邮箱
     37      * @param domain
     38      *         发出验证请求的域名(是当前站点的域名,可以任意指定)
     39      * @return
     40      *         邮箱是否可达
     41      */
     42     public boolean valid(String toMail, String domain) {
     43         if(StringUtils.isBlank(toMail) || StringUtils.isBlank(domain)) return false;
     44         if(!StringUtils.contains(toMail, '@')) return false;
     45         String host = toMail.substring(toMail.indexOf('@') + 1);
     46         if(host.equals(domain)) return false;
     47         Socket socket = new Socket();
     48         try {
     49             // 查找mx记录
     50             Record[] mxRecords = new Lookup(host, Type.MX).run();
     51             if(ArrayUtils.isEmpty(mxRecords)) return false;
     52             // 邮件服务器地址
     53             String mxHost = ((MXRecord)mxRecords[0]).getTarget().toString();
     54             if(mxRecords.length > 1) { // 优先级排序
     55                 List<Record> arrRecords = new ArrayList<Record>();
     56                 Collections.addAll(arrRecords, mxRecords);
     57                 Collections.sort(arrRecords, new Comparator<Record>() {
     58                     
     59                     public int compare(Record o1, Record o2) {
     60                         return new CompareToBuilder().append(((MXRecord)o1).getPriority(), ((MXRecord)o2).getPriority()).toComparison();
     61                     }
     62                     
     63                 });
     64                 mxHost = ((MXRecord)arrRecords.get(0)).getTarget().toString();
     65             }
     66             // 开始smtp
     67             socket.connect(new InetSocketAddress(mxHost, 25));
     68             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(socket.getInputStream())));
     69             BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
     70             // 超时时间(毫秒)
     71             long timeout = 6000;
     72             // 睡眠时间片段(50毫秒)
     73             int sleepSect = 50;
     74             
     75             // 连接(服务器是否就绪)
     76             if(getResponseCode(timeout, sleepSect, bufferedReader) != 220) {
     77                 return false;
     78             }
     79             
     80             // 握手
     81             bufferedWriter.write("HELO " + domain + "
    ");
     82             bufferedWriter.flush();
     83             if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
     84                 return false;
     85             }
     86             // 身份
     87             bufferedWriter.write("MAIL FROM: <check@" + domain + ">
    ");
     88             bufferedWriter.flush();
     89             if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
     90                 return false;
     91             }
     92             // 验证
     93             bufferedWriter.write("RCPT TO: <" + toMail + ">
    ");
     94             bufferedWriter.flush();
     95             if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
     96                 return false;
     97             }
     98             // 断开
     99             bufferedWriter.write("QUIT
    ");
    100             bufferedWriter.flush();
    101             return true;
    102         } catch (NumberFormatException e) {
    103         } catch (TextParseException e) {
    104         } catch (IOException e) {
    105         } catch (InterruptedException e) {
    106         } finally {
    107             try {
    108                 socket.close();
    109             } catch (IOException e) {
    110             }
    111         }
    112         return false;
    113     }
    114     
    115     private int getResponseCode(long timeout, int sleepSect, BufferedReader bufferedReader) throws InterruptedException, NumberFormatException, IOException {
    116         int code = 0;
    117         for(long i = sleepSect; i < timeout; i += sleepSect) {
    118             Thread.sleep(sleepSect);
    119             if(bufferedReader.ready()) {
    120                 String outline = bufferedReader.readLine();
    121                 // FIXME 读完……
    122                 while(bufferedReader.ready())
    123                     /*System.out.println(*/bufferedReader.readLine()/*)*/;
    124                 /*System.out.println(outline);*/
    125                 code = Integer.parseInt(outline.substring(0, 3));
    126                 break;
    127             }
    128         }
    129         return code;
    130     }
    131 }

      (解锁和输出123、124行数据可以让你更加清晰SMTP协议)

      对于企业邮箱,可能无法正常验证,这个是因为服务器问题。另外,dnsjava查询DNS是有缓存的。

      现在工作越来越紧张,对于技术的理解也较以前深刻。现在写出的代码可能较为精简(这意味着如果你是新手可能不容易理解)。

    联系我,一起交流


     欢迎您移步我们的交流群,无聊的时候大家一起打发时间:Programmer Union

     

    或者通过QQ与我联系:点击这里给我发消息

     (最后编辑时间2015-04-29 10:27:44)

  • 相关阅读:
    GKB版本与UTF-8版本有什么区别(转载)
    如何与项目开发方对接
    php查询数据集的几种方式(mysql_unbuffered_query()与mysql_query()的区别)
    怎么去执行文件读写 ?
    Python中(集合、元祖、字典)等词汇基本语法
    Python入门~list 相关语法
    Python入门-基础语法实践
    newman + postman 环境搭建
    萌新驾到,请多多关照!
    切片、非空即真
  • 原文地址:https://www.cnblogs.com/Johness/p/javaemail_usesockettocheckemailaddressvalid.html
Copyright © 2020-2023  润新知