最近遇到一个需求:需要验证用户填写的邮箱地址是否真实存在,是否可达。和普通的正则表达式不同,他要求尝试链接目标邮箱服务器并请求校验目标邮箱是否存在。
先来了解
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 = newLookup(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 arrRecords = new ArrayList();8 Collections.addAll(arrRecords, mxRecords);9 Collections.sort(arrRecords, new Comparator() {10
11 public intcompare(Record o1, Record o2) {12 return newCompareToBuilder().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 importjava.io.BufferedInputStream;2 importjava.io.BufferedReader;3 importjava.io.BufferedWriter;4 importjava.io.IOException;5 importjava.io.InputStreamReader;6 importjava.io.OutputStreamWriter;7 importjava.net.InetSocketAddress;8 importjava.net.Socket;9 importjava.util.ArrayList;10 importjava.util.Collections;11 importjava.util.Comparator;12 importjava.util.List;13
14 importorg.apache.commons.lang.ArrayUtils;15 importorg.apache.commons.lang.StringUtils;16 importorg.apache.commons.lang.builder.CompareToBuilder;17 importorg.xbill.DNS.Lookup;18 importorg.xbill.DNS.MXRecord;19 importorg.xbill.DNS.Record;20 importorg.xbill.DNS.TextParseException;21 importorg.xbill.DNS.Type;22
23
24 public classMailValid {25
26 public static voidmain(String[] args) {27 System.out.println(new MailValid().valid("100582783@qq.com", "jootmir.org"));28 }29
30 /**
31 * 验证邮箱是否存在32 *
33 * 由于要读取IO,会造成线程阻塞34 *35 *@paramtoMail36 * 要验证的邮箱37 *@paramdomain38 * 发出验证请求的域名(是当前站点的域名,可以任意指定)39 *@return
40 * 邮箱是否可达41 */
42 public booleanvalid(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 = newSocket();48 try{49 //查找mx记录
50 Record[] mxRecords = newLookup(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 arrRecords = new ArrayList();56 Collections.addAll(arrRecords, mxRecords);57 Collections.sort(arrRecords, new Comparator() {58
59 public intcompare(Record o1, Record o2) {60 return newCompareToBuilder().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(newBufferedInputStream(socket.getInputStream())));69 BufferedWriter bufferedWriter = new BufferedWriter(newOutputStreamWriter(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 + "\r\n");82 bufferedWriter.flush();83 if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {84 return false;85 }86 //身份
87 bufferedWriter.write("MAIL FROM: \r\n");88 bufferedWriter.flush();89 if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {90 return false;91 }92 //验证
93 bufferedWriter.write("RCPT TO: \r\n");94 bufferedWriter.flush();95 if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {96 return false;97 }98 //断开
99 bufferedWriter.write("QUIT\r\n");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) throwsInterruptedException, 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 returncode;130 }131 }
(解锁和输出123、124行数据可以让你更加清晰SMTP协议)
对于企业邮箱,可能无法正常验证,这个是因为服务器问题。另外,dnsjava查询DNS是有缓存的。
现在工作越来越紧张,对于技术的理解也较以前深刻。现在写出的代码可能较为精简(这意味着如果你是新手可能不容易理解)。
联系我,一起交流
欢迎您移步我们的交流群,无聊的时候大家一起打发时间:
或者通过QQ与我联系:
(最后编辑时间2015-04-29 10:27:44)