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

先来了解


  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(下载),自己写太麻烦。

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

mx

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

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

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

 import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.MXRecord;
import org.xbill.DNS.Record;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type; public class MailValid { public static void main(String[] args) {
System.out.println(new MailValid().valid("100582783@qq.com", "jootmir.org"));
} /**
* 验证邮箱是否存在
* <br>
* 由于要读取IO,会造成线程阻塞
*
* @param toMail
* 要验证的邮箱
* @param domain
* 发出验证请求的域名(是当前站点的域名,可以任意指定)
* @return
* 邮箱是否可达
*/
public boolean valid(String toMail, String domain) {
if(StringUtils.isBlank(toMail) || StringUtils.isBlank(domain)) return false;
if(!StringUtils.contains(toMail, '@')) return false;
String host = toMail.substring(toMail.indexOf('@') + 1);
if(host.equals(domain)) return false;
Socket socket = new Socket();
try {
// 查找mx记录
Record[] mxRecords = new Lookup(host, Type.MX).run();
if(ArrayUtils.isEmpty(mxRecords)) return false;
// 邮件服务器地址
String mxHost = ((MXRecord)mxRecords[0]).getTarget().toString();
if(mxRecords.length > 1) { // 优先级排序
List<Record> arrRecords = new ArrayList<Record>();
Collections.addAll(arrRecords, mxRecords);
Collections.sort(arrRecords, new Comparator<Record>() { public int compare(Record o1, Record o2) {
return new CompareToBuilder().append(((MXRecord)o1).getPriority(), ((MXRecord)o2).getPriority()).toComparison();
} });
mxHost = ((MXRecord)arrRecords.get(0)).getTarget().toString();
}
// 开始smtp
socket.connect(new InetSocketAddress(mxHost, 25));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(socket.getInputStream())));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
// 超时时间(毫秒)
long timeout = 6000;
// 睡眠时间片段(50毫秒)
int sleepSect = 50; // 连接(服务器是否就绪)
if(getResponseCode(timeout, sleepSect, bufferedReader) != 220) {
return false;
} // 握手
bufferedWriter.write("HELO " + domain + "\r\n");
bufferedWriter.flush();
if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
return false;
}
// 身份
bufferedWriter.write("MAIL FROM: <check@" + domain + ">\r\n");
bufferedWriter.flush();
if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
return false;
}
// 验证
bufferedWriter.write("RCPT TO: <" + toMail + ">\r\n");
bufferedWriter.flush();
if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
return false;
}
// 断开
bufferedWriter.write("QUIT\r\n");
bufferedWriter.flush();
return true;
} catch (NumberFormatException e) {
} catch (TextParseException e) {
} catch (IOException e) {
} catch (InterruptedException e) {
} finally {
try {
socket.close();
} catch (IOException e) {
}
}
return false;
} private int getResponseCode(long timeout, int sleepSect, BufferedReader bufferedReader) throws InterruptedException, NumberFormatException, IOException {
int code = 0;
for(long i = sleepSect; i < timeout; i += sleepSect) {
Thread.sleep(sleepSect);
if(bufferedReader.ready()) {
String outline = bufferedReader.readLine();
// FIXME 读完……
while(bufferedReader.ready())
/*System.out.println(*/bufferedReader.readLine()/*)*/;
/*System.out.println(outline);*/
code = Integer.parseInt(outline.substring(0, 3));
break;
}
}
return code;
}
}

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

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

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

联系我,一起交流


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

或者通过QQ与我联系:

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

Java与邮件系统交互之使用Socket验证邮箱是否存在的更多相关文章

  1. Android 中Java和JavaScript交互入门

    如何实现JavaScript 和java 交互 实现Java和js交互十分便捷.通常只需要以下几步. WebView开启JavaScript脚本执行 WebView设置供JavaScript调用的交互 ...

  2. Android中Java和JavaScript交互

    Android提供了一个很强大的WebView控件用来处理Web网页,而在网页中,JavaScript又是一个很举足轻重的脚本.本文将介绍如何实现Java代码和Javascript代码的相互调用. 如 ...

  3. Java与WCF交互(二):WCF客户端调用Java web service【转】

    原文:http://www.cnblogs.com/downmoon/archive/2010/08/25/1807982.html 在上篇< Java与WCF交互(一):Java客户端调用WC ...

  4. 转载——Java与WCF交互(二):WCF客户端调用Java Web Service

    在上篇< Java与WCF交互(一):Java客户端调用WCF服务>中,我介绍了自己如何使用axis2生成java客户端的悲惨经历.有同学问起使用什么协议,经初步验证,发现只有wsHttp ...

  5. Android WebView加载本地html并实现Java与JS交互

    最近做的一个项目中,用到自定义地图,将自定义地图转换成html页面,现在需要做的是如何将本地的html加载到android中,并可以实现交互. 相关讲解: 其实webview加载资源的速度并不慢,但是 ...

  6. java的nio之:java的bio流下实现的socket服务器同步阻塞模型和socket的伪异步的socket服务器的通信模型

    同步I/O模型的弊端===>每一个线程的创建都会消耗服务端内存,当大量请求进来,会耗尽内存,导致服务宕机 伪异步I/O的弊端分析===>当对Socket的输入流进行读取操作的时候,它会一直 ...

  7. Java与WCF交互(一)补充:用WSImport生成WSDL的Java客户端代码

    在<Java与WCF交互(一):Java客户端调用WCF服务>一 文中,我描述了用axis2的一个Eclipse控件生成WCF的Java客户端代理类,后来有朋友建议用Xfire.CXF,一 ...

  8. Java与WCF交互(一):Java客户端调用WCF服务

    最近开始了解WCF,写了个最简单的Helloworld,想通过java客户端实现通信.没想到以我的基础,居然花了整整两天(当然是工作以外的时间,呵呵),整个过程大费周折,特写下此文,以供有需要的朋友参 ...

  9. java程序连接oracle12c报:java.sql.SQLException: ORA-28040: 没有匹配的验证协议。

    报错信息: 2017-09-22 15:17:37,204 WARN [org.hibernate.cfg.SettingsFactory] - Could not obtain connection ...

随机推荐

  1. 转载:APP的上线和推广——线上推广渠道

    本文版权归个人所有,如需转载请注明出处http://www.cnblogs.com/PengLee/p/4637080.html 目录 应用商店 互联网开放平台 软件下载中心 媒体社交平台 刷榜推广 ...

  2. AngularJS快速入门指南03:表达式

    AngularJS通过表达式将数据绑定到HTML. AngularJS表达式 AngularJS表达式写在双大括号中:{{ 表达式语句 }}. AngularJS表达式绑定数据到HTML的方式与ng- ...

  3. win2003 Enterprise Edition sp2 企业版序列号

    HXCRB-TQW9R-42JK8-TQ7X2-PJRDY Windows Server 2003 R2, x32 EDDVB4Y-KF6GK-MT3XX-FW3HC-VXTB6

  4. webApp 阅读器项目实践

    这是一个webApp 阅读器的项目,是慕课网的老师讲授的一个实战,先给出项目源码在GitHub的地址:https://github.com/yulifromchina/MobileWebReader. ...

  5. podspec文件介绍

    podspec文件是cocopods引入的第三方代码库的配置索引文件 它的创建命令是:$pod spec create TestFile podspec文件的常用配置字段介绍 Pod::Spec.ne ...

  6. paip.提升效率---提升绑定层次--form绑定取代field绑定

    paip.提升效率---提升绑定层次--form绑定取代field绑定 =================== 编辑form中,常常需要,绑定一个对象到个form..   传统上要绑定field开始. ...

  7. javaweb学习总结(二十七)——jsp简单标签开发案例和打包

    一.开发标签库 1.1.开发防盗链标签 1.编写标签处理器类:RefererTag.java 1 package me.gacl.web.simpletag; 2 3 import java.io.I ...

  8. Leetcode 130 Surrounded Regions DFS

    将内部的O点变成X input X X X XX O O X X X O XX O X X output X X X XX X X XX X X XX O X X DFS的基本框架是 void dfs ...

  9. WPS 文字排版 标题回车后标题号自动增加

    飞思卡来互联网提供全球性的 看了看 风格的呵呵 合格否d合格否的 secure embedded嵌入式解 个国家和地区,注册了成千上万项专利,产品面向 物联网,汽车电子,消费电子,工业及网络设备等市 ...

  10. Advacned Puppet: Puppet Master性能调优

    本文是Advanced Puppet系列的第一篇:Puppet master性能调优,谈一谈如何优化和提高C/S架构下master端的性能. 故事情节往往惊人地类似:你是一名使用Puppet管理线上业 ...