Java與郵件系統交互之使用Socket驗證郵箱是否存在

  最近遇到一個需求:需要驗證用戶填寫的郵箱地址是否真實存在,是否可達。和普通的正則表達式不同,他要求嘗試鏈接目標郵箱服務器並請求校驗目標郵箱是否存在。

先來了解


 

  DNS之MX記錄

  對於DNS不瞭解的,請移步百度搜索。

  DNS中除了A記錄(域名-IP映射)之外,還有MX記錄(郵件交換記錄),CNAME記錄(別名,咱不管)。

  MX記錄就是爲了在發送郵件時使用友好域名規則,比如我們發送到QQ郵箱[email protected]。我們填寫地址是到“qq.com”,但實際上可能服務器地址千奇百怪/而且許有多個。在設置DNS時可以順帶設置MX記錄。

  說白了,“qq.com”只是域名,做HTTP請求響應地址,你郵件能發到Tomcat上嗎?那我們發到“qq.com”上面的郵件哪裏去了,我們把自己想象成一個郵件服務器,你的用戶讓你給[email protected]發一封信,你如何操作?找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("[email protected]", "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是有緩存的。

  現在工作越來越緊張,對於技術的理解也較以前深刻。現在寫出的代碼可能較爲精簡(這意味着如果你是新手可能不容易理解)。

聯繫我,一起交流


 

 歡迎您移步我們的交流羣,無聊的時候大家一起打發時間:Programmer Union

 

 

或者通過QQ與我聯繫:點擊這裏給我發消息

 (最後編輯時間2015-04-29 10:27:44)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章