新项目,有需要提前“判断电子邮件地址是否真实存在”。
首先想到这是一个标准化问题,网上肯定有参考答案了。
大概思路是,发一封邮件给这个账户,或者通过SMTP等协议进行通信。
邮箱几十亿,不可能有简单的API,直接判断是否有效,不然全网等可用邮箱都被你给拿到了。
简单做个汇总,备忘。
实践结论:QQ、163等标准化邮箱,可以判断是否存在。部分企业邮箱,如果使用的是腾讯企业邮箱,也可以。
部分企业邮箱,京东等,不太行。
微软Hotmail邮箱,返回超时。
在线检测工具,比如 https://verify-email.org/ 感觉不靠谱。
从了解到放弃。
实际测试的邮箱,已做修改,仅供参考。
第1种实践过的,最佳代码:
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 MailValidKit {
public static void main(String[] args) {
System.out.println("应该为true:"+new MailValidKit().valid("100582783@qq.com", "jootmir.org"));
System.out.println("应该为true:"+new MailValidKit().valid("fasuion@qq.com", "jootmir.org"));
System.out.println("应该为false:"+new MailValidKit().valid("fansnion@qq.com", "jootmir.org"));
System.out.println("应该为true:"+new MailValidKit().valid("lewenans@163.com", "jootmir.org"));
System.out.println("应该为true:"+new MailValidKit().valid("wen.lei@10cdit.com", "jootmir.org"));
System.out.println("应该为true:"+new MailValidKit().valid("ein1@jd.com", "jootmir.org"));
System.out.println("应该为true:"+new MailValidKit().valid("wngongao@jd.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;
}
}
代码主要参考:
第2种,还可以,163邮箱无法识别
import java.io.IOException;
import org.apache.commons.net.smtp.SMTPClient;
import org.apache.commons.net.smtp.SMTPReply;
import org.apache.log4j.Logger;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.Type;
/**
*
* 校验邮箱:1、格式是否正确 2、是否真实有效的邮箱地址
* 步骤: 1、从dns缓存服务器上查询邮箱域名对应的SMTP服务器地址 2、尝试建立Socket连接
* 3、尝试发送一条消息给SMTP服务器 4、设置邮件发送者 5、设置邮件接收者 6、检查响应码是否为250(为250则说明这个邮箱地址是真实有效的)
*
* @author Michael Ran
*
*/
// 总体靠谱
public class CheckEmailValidityUtil {
private static final Logger logger = Logger.getLogger(CheckEmailValidityUtil.class);
/**
* @param email
* 待校验的邮箱地址
* @return
*/
public static boolean isEmailValid(String email) {
if (!email.matches("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+")) {
logger.error("邮箱(" + email + ")校验未通过,格式不对!");
return false;
}
String host = "";
String hostName = email.split("@")[1];
// Record: A generic DNS resource record. The specific record types
// extend this class. A record contains a name, type, class, ttl, and
// rdata.
Record[] result = null;
SMTPClient client = new SMTPClient();
try {
// 查找DNS缓存服务器上为MX类型的缓存域名信息
Lookup lookup = new Lookup(hostName, Type.MX);
lookup.run();
if (lookup.getResult() != Lookup.SUCCESSFUL) {// 查找失败
logger.error("邮箱(" + email + ")校验未通过,未找到对应的MX记录!");
return false;
} else {// 查找成功
result = lookup.getAnswers();
}
// 尝试和SMTP邮箱服务器建立Socket连接
for (int i = 0; i < result.length; i++) {
host = result[i].getAdditionalName().toString();
logger.info("SMTPClient try connect to host:" + host);
// 此connect()方法来自SMTPClient的父类:org.apache.commons.net.SocketClient
// 继承关系结构:org.apache.commons.net.smtp.SMTPClient-->org.apache.commons.net.smtp.SMTP-->org.apache.commons.net.SocketClient
// Opens a Socket connected to a remote host at the current
// default port and
// originating from the current host at a system assigned port.
// Before returning,
// _connectAction_() is called to perform connection
// initialization actions.
// 尝试Socket连接到SMTP服务器
client.connect(host);
// Determine if a reply code is a positive completion
// response(查看响应码是否正常).
// All codes beginning with a 2 are positive completion
// responses(所有以2开头的响应码都是正常的响应).
// The SMTP server will send a positive completion response on
// the final successful completion of a command.
if (!SMTPReply.isPositiveCompletion(client.getReplyCode())) {
// 断开socket连接
client.disconnect();
continue;
} else {
logger.info("找到MX记录:" + hostName);
logger.info("建立链接成功:" + hostName);
break;
}
}
logger.info("SMTPClient ReplyString:" + client.getReplyString());
//网易163,发现是自己等,有能力进行检测,所以最好用真实的
String emailPrefix = "leiwenfans";//leiwenfans222,可以随便写?
String emailSuffix = "163.com";
//emailPrefix="fansunion";
//emailSuffix="qq.com";
String fromEmail = emailPrefix + "@" + emailSuffix;
// Login to the SMTP server by sending the HELO command with the
// given hostname as an argument.
// Before performing any mail commands, you must first login.
// 尝试和SMTP服务器建立连接,发送一条消息给SMTP服务器
client.login(emailPrefix);
logger.info("SMTPClient login:" + emailPrefix + "...");
logger.info("SMTPClient ReplyString:" + client.getReplyString());
// Set the sender of a message using the SMTP MAIL command,
// specifying a reverse relay path.
// The sender must be set first before any recipients may be
// specified,
// otherwise the mail server will reject your commands.
// 设置发送者,在设置接受者之前必须要先设置发送者
client.setSender(fromEmail);
logger.info("设置发送者 :" + fromEmail);
logger.info("SMTPClient ReplyString:" + client.getReplyString());
// Add a recipient for a message using the SMTP RCPT command,
// specifying a forward relay path. The sender must be set first
// before any recipients may be specified,
// otherwise the mail server will reject your commands.
// 设置接收者,在设置接受者必须先设置发送者,否则SMTP服务器会拒绝你的命令
client.addRecipient(email);
logger.info("设置接收者:" + email);
logger.info("SMTPClient ReplyString:" + client.getReplyString());
logger.info("SMTPClient ReplyCode:" + client.getReplyCode() + "(250表示正常)");
if (250 == client.getReplyCode()) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
client.disconnect();
} catch (IOException e) {
}
}
return false;
}
//建立连接可能会非常慢,几十秒
public static void main(String[] args) {
//test163();
testJd();
//testBrng();
//testQq();
testHotmail();
}
//SMTPClient login:leiwenfans...
//SMTPClient ReplyString:250 DB5EUR03FT043.mail.protection.outlook.com Hello [36.110.19.90]
//没下文了
private static void testHotmail() {
System.out.println("合法hotmail邮箱:"+isEmailValid("fasnion@otmail.com"));
System.out.println("合法hotmail邮箱:"+isEmailValid("huingans@homail.com"));
}
//550
//设置接收者:liwe@jd.com
//SMTPClient ReplyString:550 5.1.1 Recipient address rejected: User unknown
private static void testJd() {
System.out.println("jd企业邮箱:"+isEmailValid("eien1@jd.com"));
}
private static void test163() {
//163总是false,550、503等异常状态,而不是250
System.out.println("合法163邮箱:"+isEmailValid("liwens@163.com"));
}
private static void testQq() {
//非法格式
System.out.println("格式不正确:"+isEmailValid("90310930qq.com"));
System.out.println("格式不正确2:"+isEmailValid("903109360@qqcom"));
//普通随机邮箱
System.out.println("普通随机邮箱:"+isEmailValid("903109360@qq.com"));//原作者自己测试等邮箱
System.out.println("普通随机邮箱,瞎填的:"+isEmailValid("903109360222@qq.com"));
//我的邮箱,qq
System.out.println("合法qq数字邮箱:"+isEmailValid("2437088@qq.com"));
System.out.println("合法qq自定义邮箱:"+isEmailValid("fasunon@qq.com"));
}
private static void testBaing() {
//baron企业邮箱ok
System.out.println("企业邮箱:"+isEmailValid("wn.li@10edit.com"));
System.out.println("企业邮箱2:"+isEmailValid("cheong.yu@1dit.com"));
System.out.println("企业邮箱3:"+isEmailValid("yonao.niu@10edit.com"));
System.out.println("企业邮箱4:"+isEmailValid("yonghao.@.com"));
System.out.println("企业邮箱:"+isEmailValid("wen.i123@100crdi"));
}
}
第3种,不太靠谱,io卡住,源代码中得正则表达式还有问题
import java.io.*;
import java.net.*;
import org.xbill.DNS.*;
// 不太靠谱
public class CheckEmail
{
public static void main(String[] args) {
System.out.println("企业邮箱:"+CheckEmail.check("wen.lei@100ct.com"));//check方式有问题
System.out.println("qq邮箱:"+CheckEmail.check("2403818@qq.com"));//check方式有问题
}
public static boolean check(String mailTo)
{
//正则不对
/* if (!mailTo.matches("w+([-_.]w+)*@w+([-.]w+)*.w+([-.]w+)*")) { //判断格式
return false;
}*/
if (!mailTo.matches("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+")) {
System.out.println("邮箱(" + mailTo + ")校验未通过,格式不对!");
return false;
}
String hostName = mailTo.split("@")[1]; //获得如163.com
String host = null; // MX记录
try {
Lookup lookup = new Lookup(hostName, Type.MX);
lookup.run();
if (lookup.getResult() != Lookup.SUCCESSFUL) {
return false;
}
else {
Record[] result = lookup.getAnswers();
host = result[0].getAdditionalName().toString();
}
Socket socket = new Socket(host, 25);
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
in.readLine();
out.write("HELO Sender ");
out.flush();
in.readLine();
out.write("MAIL FROM:<xx@xxx.xx> ");
out.flush();
in.readLine();
out.write("RCPT TO:<" + mailTo + "> ");
out.flush();
String r = in.readLine();
out.write("QUIT ");
out.flush();
out.close();
in.close();
socket.close();
if (!r.startsWith("250")) {
return false;
}
else {
return true;
}
} catch (Exception e) {
return false;
}
}
}
----------------------以下内容纯copy,备用------------------------------------
1.Java与邮件系统交互之使用Socket验证邮箱是否存在
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(下载),自己写太麻烦。
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 + "\r\n");
82 bufferedWriter.flush();
83 if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
84 return false;
85 }
86 // 身份
87 bufferedWriter.write("MAIL FROM: <check@" + domain + ">\r\n");
88 bufferedWriter.flush();
89 if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
90 return false;
91 }
92 // 验证
93 bufferedWriter.write("RCPT TO: <" + toMail + ">\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) 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是有缓存的。
2、Java验证邮箱有效性和真实性
Java验证邮箱是否真实存在有效
要检测邮箱是否真实存在,必须了解两方面知识:
1. MX记录,winodws的nslookup命令。查看学习
2. SMTP协议,如何通过telnet发送邮件。查看学习
有个网站可以校验,http://verify-email.org/, 不过一小时只允许验证10次。
代码如下(补充了一些注释):
1. import java.io.IOException;
2.
3. import org.apache.commons.net.smtp.SMTPClient;
4. import org.apache.commons.net.smtp.SMTPReply;
5. import org.apache.log4j.Logger;
6. import org.xbill.DNS.Lookup;
7. import org.xbill.DNS.Record;
8. import org.xbill.DNS.Type;
9.
10. /**
11. *
12. * 校验邮箱:1、格式是否正确 2、是否真实有效的邮箱地址
13. * 步骤:
14. * 1、从dns缓存服务器上查询邮箱域名对应的SMTP服务器地址
15. * 2、尝试建立Socket连接
16. * 3、尝试发送一条消息给SMTP服务器
17. * 4、设置邮件发送者
18. * 5、设置邮件接收者
19. * 6、检查响应码是否为250(为250则说明这个邮箱地址是真实有效的)
20. * @author Michael Ran
21. *
22. */
23. public class CheckEmailValidityUtil {
24. private static final Logger logger = Logger
25. .getLogger(CheckEmailValidityUtil.class);
26. /**
27. * @param email 待校验的邮箱地址
28. * @return
29. */
30. public static boolean isEmailValid(String email) {
31. if (!email.matches("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+")) {
32. logger.error("邮箱("+email+")校验未通过,格式不对!");
33. return false;
34. }
35. String host = "";
36. String hostName = email.split("@")[1];
37. //Record: A generic DNS resource record. The specific record types
38. //extend this class. A record contains a name, type, class, ttl, and rdata.
39. Record[] result = null;
40. SMTPClient client = new SMTPClient();
41. try {
42. // 查找DNS缓存服务器上为MX类型的缓存域名信息
43. Lookup lookup = new Lookup(hostName, Type.MX);
44. lookup.run();
45. if (lookup.getResult() != Lookup.SUCCESSFUL) {//查找失败
46. logger.error("邮箱("+email+")校验未通过,未找到对应的MX记录!");
47. return false;
48. } else {//查找成功
49. result = lookup.getAnswers();
50. }
51. //尝试和SMTP邮箱服务器建立Socket连接
52. for (int i = 0; i < result.length; i++) {
53. host = result[i].getAdditionalName().toString();
54. logger.info("SMTPClient try connect to host:"+host);
55.
56. //此connect()方法来自SMTPClient的父类:org.apache.commons.net.SocketClient
57. //继承关系结构:org.apache.commons.net.smtp.SMTPClient-->org.apache.commons.net.smtp.SMTP-->org.apache.commons.net.SocketClient
58. //Opens a Socket connected to a remote host at the current default port and
59. //originating from the current host at a system assigned port. Before returning,
60. //_connectAction_() is called to perform connection initialization actions.
61. //尝试Socket连接到SMTP服务器
62. client.connect(host);
63. //Determine if a reply code is a positive completion response(查看响应码是否正常).
64. //All codes beginning with a 2 are positive completion responses(所有以2开头的响应码都是正常的响应).
65. //The SMTP server will send a positive completion response on the final successful completion of a command.
66. if (!SMTPReply.isPositiveCompletion(client.getReplyCode())) {
67. //断开socket连接
68. client.disconnect();
69. continue;
70. } else {
71. logger.info("找到MX记录:"+hostName);
72. logger.info("建立链接成功:"+hostName);
73. break;
74. }
75. }
76. logger.info("SMTPClient ReplyString:"+client.getReplyString());
77. String emailSuffix="163.com";
78. String emailPrefix="ranguisheng";
79. String fromEmail = emailPrefix+"@"+emailSuffix;
80. //Login to the SMTP server by sending the HELO command with the given hostname as an argument.
81. //Before performing any mail commands, you must first login.
82. //尝试和SMTP服务器建立连接,发送一条消息给SMTP服务器
83. client.login(emailPrefix);
84. logger.info("SMTPClient login:"+emailPrefix+"...");
85. logger.info("SMTPClient ReplyString:"+client.getReplyString());
86.
87. //Set the sender of a message using the SMTP MAIL command,
88. //specifying a reverse relay path.
89. //The sender must be set first before any recipients may be specified,
90. //otherwise the mail server will reject your commands.
91. //设置发送者,在设置接受者之前必须要先设置发送者
92. client.setSender(fromEmail);
93. logger.info("设置发送者 :"+fromEmail);
94. logger.info("SMTPClient ReplyString:"+client.getReplyString());
95.
96. //Add a recipient for a message using the SMTP RCPT command,
97. //specifying a forward relay path. The sender must be set first before any recipients may be specified,
98. //otherwise the mail server will reject your commands.
99. //设置接收者,在设置接受者必须先设置发送者,否则SMTP服务器会拒绝你的命令
100. client.addRecipient(email);
101. logger.info("设置接收者:"+email);
102. logger.info("SMTPClient ReplyString:"+client.getReplyString());
103. logger.info("SMTPClient ReplyCode:"+client.getReplyCode()+"(250表示正常)");
104. if (250 == client.getReplyCode()) {
105. return true;
106. }
107. } catch (Exception e) {
108. e.printStackTrace();
109. } finally {
110. try {
111. client.disconnect();
112. } catch (IOException e) {
113. }
114. }
115. return false;
116. }
117. public static void main(String[] args) {
118. System.out.println(isEmailValid("903109360@qq.com"));
119. }
120. }
执行结果:
MX record about qq.com exists.
Connection succeeded to mx3.qq.com.
220 newmx21.qq.com MX QQ Mail Server
>HELO 163.com
=250 newmx21.qq.com >MAIL FROM:
=250 Ok
=250 Ok
Outcome: true
如果将被验证的邮箱换为:903109360@qq.con,就会验证失败:
找不到MX记录
Outcome: false
值得注意的是犹豫校验的第一步是从DNS服务器查询MX记录 所以必须联网 否则校验会失效 因为找不到MX记录会导致真实的有效地址也校验为无效 这点要特别注意。
此代码需要两个jar包:
1、Apache Commons Net
maven地址:http://mvnrepository.com/artifact/commons-net/commons-net/
2、dnsjava
maven地址:http://mvnrepository.com/artifact/dnsjava/dnsjava/
PS:目前还没发验证部分企业邮箱,后面想办法解决这个问题之后更新此文章。
相关资源下载>>>:
参考文档:
3.检验电子邮件地址是否真实
同样用到了dnsjava(下载地址:http://www.dnsjava.org/)来获取MX记录。考虑到效率问题可将获取的MX记录保存到xml或数据库,下次使用时先检索本地数据,若不存在再获取其MX记录。
CheckEmail.java
import java.io.*;
import java.net.*;
import org.xbill.DNS.*;public class CheckEmail
{
public static boolean check(String mailTo)
{
if (!mailTo.matches("w+([-_.]w+)*@w+([-.]w+)*.w+([-.]w+)*")) { //判断格式
return false;
}
String hostName = mailTo.split("@")[1]; //获得如163.com
String host = null; // MX记录
try {
Lookup lookup = new Lookup(hostName, Type.MX);
lookup.run();
if (lookup.getResult() != Lookup.SUCCESSFUL) {
return false;
}
else {
Record[] result = lookup.getAnswers();
host = result[0].getAdditionalName().toString();
}
Socket socket = new Socket(host, 25);
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
in.readLine();
out.write("HELO Sender ");
out.flush();
in.readLine();
out.write("MAIL FROM:<xx@xxx.xx> ");
out.flush();
in.readLine();
out.write("RCPT TO:<" + mailTo + "> ");
out.flush();
String r = in.readLine();
out.write("QUIT ");
out.flush();
out.close();
in.close();
socket.close();
if (!r.startsWith("250")) {
return false;
}
else {
return true;
}
} catch (Exception e) {
return false;
}
}
}
考虑到效率&资源问题,不推荐这样验证电子邮件的真实性。
4.使用JavaMail发送邮件时如何判断收件人邮箱是否有效的解决方案
昨天公司老总提了个需求,要求给用户提供的邮箱发送邮件后,希望能收到反馈,判断是否发送成功。公司的项目是使用JavaMail来给用户发送邮件,但是调用Transport类的sendMessage方法发送邮件是没有返回值的。于是去百度,搜寻了好长时间,终于找到了两篇博客,以为能够解决这个问题,然后就去试了,可结果还是不行。
博客截图如下:
附上具体的地址:
具体的代码参考我找到的另一篇博客:
但是,不管我使用的是否是注册的还是未注册的邮箱,返回的结果都是: MESSAGE_DELIVERED,看了一下评论区的人,发现很多人情况和我都一看,心中充满了郁闷,因为找博客,找完整代码,整合到自己项目中来测试花了很多时间了,但是问题依旧没有解决。不过干咱们这行的,发牢骚、逃避是解决不了问题的,该干的活还是得干,于是调整了心情,我继续想办法。
我分析到:既然是要检验这个邮箱是否是有效的,那么给这个邮箱发一封邮件判断对方是否收到是一种解决方案,但却不是唯一的解决方案,我就想是不是有什么方法可以校验这个邮箱是否真实存在的呢?带着这个疑惑,我更换了在百度上用于搜索的关键字,果然,让我找到了一篇名为《Java与邮件系统交互之使用Socket验证邮箱是否存在》的博文,具体地址是:
我直接将这篇博客上的代码拷贝到我的项目上,测试了几个有效的邮箱,包括163邮箱,qq邮箱,腾讯企业邮箱,发现打印的结果都是true,然后又测试了几个不存在的邮箱,返回的结果都是false。此刻内心对这个作者充满了感激与膜拜......
为了表达对这段代码的喜爱,我特地复制上来跟各位一起欣赏:
package sy.util.sendemail;
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("a111aaaaaaaaaaa@163.com", "jootmir.org"));
}
/**
* 验证邮箱是否存在
* <br>
* 由于要读取IO,会造成线程阻塞
*
* @param toMail
* 要验证的邮箱
* @param domain
* 发出验证请求的域名(是当前站点的域名,可以任意指定)
* @return
* 邮箱是否可达
*/
public static 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 static 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;
}
}
原博客上的jar包下载链接找不到,附上在Maven Repository中的下载地址:
http://mvnrepository.com/artifact/dnsjava/dnsjava/2.1.1
要是该链接失效了,可以自己到Maven 仓库官网:http://mvnrepository.com/artifact/opensymphony/quartz-all ,输入关键字:dnsjava 搜索,注意选择 dnsjava包下的资源下载,如图:
对找jar包有过非常痛苦的经历,所以稍微啰嗦了一下,哈哈。言归正传,
接着,测试了邮箱有效之后,就可以给这个邮箱发邮件了,如果正常发送,说明发送成功了,因为邮箱是有效的,至于是不是用户本人的,那就只有用户自己知道了,如果发送过程中抛了异常,那就说明发送失败。
但是,这其实并非真正的解决方案,真正的解决方案是:
(1)如果这个邮箱对用户和对网站自身来说很重要的话,那么在注册的时候就应该强制用户验证邮箱,这样以后发邮箱时就能保证该邮箱是有效的。
(2)对于企业邮箱,有可能用户会因为离职等原因,导致原来的邮箱不可用,或者对于那些用户主动关闭邮箱的情况,就需要调用工具类定时检验邮箱是否可用的。但是我们知道,IO是非常耗费计算机资源的,所以有必要降低IO的频率,同时避免在网站使用的高峰期进行大量的IO操作。
至此,问题勉强解决了。如果有更好的方法,欢迎指教。
解决方法一直都在,只是没有人将javamail与Socket校验这两个词关联起来,我今天做的,就是关联这两个词,仅此而已。
5.检验Email是否有效
前段时间自己做一个检验Email是否有效的工具,通过几天时间的查资料和学习,终于搞定了。主要就是登陆邮箱的smtp服务器,查找邮箱是否存在,在网上很多转载了检验Email有效性的文章,那里就是通过smtp检验的,首先说一下使用telnet登陆smtp服务器发送邮件的过程:
1、连接smtp服务器:telnet smtp.126.com 25
2、发送hello命令: helo 126.com
3、发送mail from命令,表示发件人邮箱:mail from:<test@test.com>
4、发送rcpt to命令,表示收件人邮箱,可以多次使用此命令:rcpt to:<test@126.com>
5、发送data命令,接着就是信件的内容,以“.”结束
6、发送quit命令,结束连接。
上面的每个命令都以/r/n结束,这是smtp协议。但是如今大部分邮件服务器都采用esmtp协议,防止乱发邮件,esmtp也就比smtp多了验证的步骤,在第2步与第3步之间加入验证:
发送auth login命令:auth login
接着发送用户名和密码,用户名和密码都是base64编码的,确认了用户名和密码后就可以发送mail from了。
这样我们就可以通过rcpt to返回的信息判断用户是否存在了。
后来我查了一下,有些在线检测邮箱有效性的网站,它们的log中显示的并不用用户名和密码登陆,也就是可以直接利用smtp协议,然后查找到它们是利用MX服务器检测的。
我们可以利用nslookup命令查找到邮箱服务器的MX服务器地址,如nslookup -type=mx 126.com
这样就会显示出126邮箱的MX服务器“126.com MX preference = 50, mail exchanger = 126.mxmail.netease.com”其中126.mxmail.netease.com就是地址了。下面是交互的过程
$ telnet 126.mxmail.netease.com 25
Trying 220.181.15.200...
Connected to 126.mxmail.netease.com.
S:220 126.com Anti-spam GT for Coremail System (126com[20081010])
C:helo localhost
S:250 OK
C:mail fro
S:250 Mail OK //250,表示此邮箱存在
C:rcpt to:<
S:550 User no //550,用户不存在
C:quit
S:221 Bye
Connection closed by foreign host.
这样如果我们要检测某个邮箱是否有效,我们只要登陆此邮箱的MX服务器,然后检测就OK了。
如果要编程实现的话,只需建立socket连接,然后发送相应的命令,检查接受到的是不是250信息就可以判断邮箱的有效性,这里要注意每次命令都要加上/r/n结束,另外还有一个重点就是要获取MX地址,这个就根据各个开发语言和工具,仁者见仁智者见智,不同的工具实现也不一样的。我用VC和C#实现过,就是运行控制台命令,然后获得命令的标准输出。
6.
如何验证一个邮箱是否正确
https://waimaoquan.alibaba.com/bbs/read-htm-tid-2006059-fid-0.html
有时我们给一些潜在客户发了第一封邮件,邮件很快被退回来了,也就是说发送失败了,邮件发送失败的原因有很多,最可能的一种就是邮箱地址有误,根本不存在。
如果我们发邮件失败后,我们可以去判断那个邮箱地址是不是存在的,如果不存在,我们可以把这个邮箱从我们的邮箱库中删除,这样就能提高以后的工作效率。另外,如果我们发的很多邮件被退回来,事实上我们的邮箱是会被减分的,那我们邮箱的等级也会相应降低一些,严重的可能会影响我们跟正常合作的客户的邮件沟通。
判断一个邮箱是否存在的工具很多,这里介绍几种我经常用的,效果比较好的。
第一个工具:
http://verify-email.org/
进入这个网站,有个地方有个方框,我们把要验证的邮箱地址粘贴进去,然后点击“Verify",过一会儿,下面会显示出结果,如果是Result: Ok,说明邮箱是存在的,如果显示是Result: bad,那邮箱地址一般存在问题
这个工具每小时好像最多只能验证5个邮箱
第二个工具:
http://mailtester.com/
使用方法很简单,这里不说了。请注意,如果系统说“Server doesn't allow e-mail address verification”,这种情况,就是无法判断的意思,这种情况邮箱一般也是没有问题的。
第三个工具:
http://tools.email-checker.com/ ,这个网站一般验证不出企业邮箱。
请注意,我们去验证的时候,最好不要拿企业邮箱去验证,企业邮箱的后缀一个公司的域名后缀,如果你把这个企业邮箱放在谷歌里搜一下,能搜到一个公司的网站,那一般邮箱就没问题。另外,一般人也不会去虚构企业邮箱,因为没什么意义。
以上的验证工具我们主要是用它们来验证gmail,yahoo email,hotmail之类免费邮箱,另外,那些只是工具,准确率一般就95%左右,不是100%准确,就是一些付费的邮箱验证工具,准确率也不太可能达到100%。