Java与邮件系统交互之使用Socket验证邮箱是否存在
最近遇到一個(gè)需求:需要驗(yàn)證用戶填寫的郵箱地址是否真實(shí)存在,是否可達(dá)。和普通的正則表達(dá)式不同,他要求嘗試鏈接目標(biāo)郵箱服務(wù)器并請(qǐng)求校驗(yàn)?zāi)繕?biāo)郵箱是否存在。
先來(lái)了解
?
DNS之MX記錄
對(duì)于DNS不了解的,請(qǐng)移步百度搜索。
DNS中除了A記錄(域名-IP映射)之外,還有MX記錄(郵件交換記錄),CNAME記錄(別名,咱不管)。
MX記錄就是為了在發(fā)送郵件時(shí)使用友好域名規(guī)則,比如我們發(fā)送到QQ郵箱xxx@qq.com。我們填寫地址是到“qq.com”,但實(shí)際上可能服務(wù)器地址千奇百怪/而且許有多個(gè)。在設(shè)置DNS時(shí)可以順帶設(shè)置MX記錄。
說(shuō)白了,“qq.com”只是域名,做HTTP請(qǐng)求響應(yīng)地址,你郵件能發(fā)到Tomcat上嗎?那我們發(fā)到“qq.com”上面的郵件哪里去了,我們把自己想象成一個(gè)郵件服務(wù)器,你的用戶讓你給xxx@qq.com發(fā)一封信,你如何操作?找mx記錄是必要的。
SMTP之純Socket訪問
對(duì)于SMTP不了解或Java Socket不了解的,請(qǐng)移步百度搜索。
郵件協(xié)議是匿名協(xié)議,我們通過SMTP協(xié)議可以讓郵件服務(wù)器來(lái)驗(yàn)證目標(biāo)地址是否真實(shí)存在。
代碼實(shí)現(xiàn)
?
由以上介紹可知:通過DNS中MX記錄可以找到郵件服務(wù)器地址,通過SMTP協(xié)議可以讓郵件服務(wù)器驗(yàn)證目標(biāo)郵箱地址的真實(shí)性。
那么我們就來(lái)進(jìn)行編碼實(shí)現(xiàn)。
首先需要查詢DNS,這個(gè)需要用到一個(gè)Java查詢DNS的組件dnsjava(下載),自己寫太麻煩。
1 // 查找mx記錄 2 Record[] mxRecords = new Lookup(host, Type.MX).run(); 3 if(ArrayUtils.isEmpty(mxRecords)) return false; 4 // 郵件服務(wù)器地址 5 String mxHost = ((MXRecord)mxRecords[0]).getTarget().toString(); 6 if(mxRecords.length > 1) { // 優(yōu)先級(jí)排序 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(上面代碼中的生僻類型就是來(lái)自dnsjava,我使用apache-commons組件來(lái)判斷空值和構(gòu)建排序,return false是在查詢失敗時(shí)。)
接下來(lái)通過優(yōu)先級(jí)排序(mx記錄有這個(gè)屬性)取第一個(gè)郵件服務(wù)器地址來(lái)鏈接。
這里的主要代碼是通過SMTP發(fā)送RCPT TO指令來(lái)指定郵件接收方,如果這個(gè)地址存在則服務(wù)器返回成功狀態(tài),如果沒有的話則返回錯(cuò)誤指令。
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 * 驗(yàn)證郵箱是否存在 32 * <br> 33 * 由于要讀取IO,會(huì)造成線程阻塞 34 * 35 * @param toMail 36 * 要驗(yàn)證的郵箱 37 * @param domain 38 * 發(fā)出驗(yàn)證請(qǐng)求的域名(是當(dāng)前站點(diǎn)的域名,可以任意指定) 39 * @return 40 * 郵箱是否可達(dá) 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 // 郵件服務(wù)器地址 53 String mxHost = ((MXRecord)mxRecords[0]).getTarget().toString(); 54 if(mxRecords.length > 1) { // 優(yōu)先級(jí)排序 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 // 超時(shí)時(shí)間(毫秒) 71 long timeout = 6000; 72 // 睡眠時(shí)間片段(50毫秒) 73 int sleepSect = 50; 74 75 // 連接(服務(wù)器是否就緒) 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 // 驗(yàn)證 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行數(shù)據(jù)可以讓你更加清晰SMTP協(xié)議)
對(duì)于企業(yè)郵箱,可能無(wú)法正常驗(yàn)證,這個(gè)是因?yàn)榉?wù)器問題。另外,dnsjava查詢DNS是有緩存的。
現(xiàn)在工作越來(lái)越緊張,對(duì)于技術(shù)的理解也較以前深刻。現(xiàn)在寫出的代碼可能較為精簡(jiǎn)(這意味著如果你是新手可能不容易理解)。
聯(lián)系我,一起交流
?
?歡迎您移步我們的交流群,無(wú)聊的時(shí)候大家一起打發(fā)時(shí)間:
?
?
或者通過QQ與我聯(lián)系:
?(最后編輯時(shí)間2015-04-29?10:27:44)
?
總結(jié)
以上是生活随笔為你收集整理的Java与邮件系统交互之使用Socket验证邮箱是否存在的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA String 相加编译器发生了
- 下一篇: Windows 10 安装