如何準(zhǔn)確判斷郵件地址是否存在
我總結(jié)了幾種郵件出現(xiàn)重發(fā)、漏發(fā)的解釋:1.網(wǎng)絡(luò);2.防火墻;3.服務(wù)器的自我保護(hù),比如防止大批量發(fā)送時掛掉或者垃圾郵件,我覺得第三種解釋靠譜一些,對于遇到的這些問題在下面的文章中給出了補救措施。
公司郵箱目前使用的是Zimbra,該郵件服務(wù)器目前不甚穩(wěn)定,經(jīng)常出現(xiàn)重發(fā)、漏發(fā)問題。經(jīng)測試,每100封郵件僅可成功發(fā)送98封左右,以下是測試數(shù)據(jù):
測試用例1:100封,總用時約:16min;實收97封,失敗3次,3次錯誤信息均為:javax.mail.MessagingException: Could not connect to SMTP host
測試用例2:100封,總用時約:16min;實收100封,失敗2次,錯誤同上。加失敗重發(fā)機(jī)制,失敗后等待10s重發(fā),最多重發(fā)3次;
測試用例3:每發(fā)一封,停留10s,總用時32min;實收100封,失敗1次,錯誤同上;重發(fā)機(jī)制同用例2.
關(guān)于MessagingException的問題,可以參考:
javax.mail.MessagingException: Could not connect to SMTP host
針對這種問題,我增加了郵件重發(fā),
if(sendHtmlMail_(mail)){ return true; } else{ int i = 0; //包含群組郵件,失敗不重發(fā) boolean isNeedRe = isNeedRe(mail); while(!sendHtmlMail_(mail) && isNeedRe && i < 10){ try { i++; Thread.sleep(1000*60); } catch (InterruptedException e) { LOGGER.error("resend mail error", e); } } return true; }
但這種機(jī)制又產(chǎn)生了新的問題,因郵件服務(wù)器不穩(wěn)定導(dǎo)致在僅發(fā)送一次的情況下也會向郵件收件人發(fā)送郵件,且同一封郵件的收件人(包括抄送、密送)可能部分收到郵件、部分收不到郵件。
針對以上的問題,我們將重發(fā)機(jī)制去除,僅針對不合法郵件(即服務(wù)器上不存在的郵件地址)進(jìn)行剔除,剔除后再進(jìn)行發(fā)送。而對其他原因?qū)е碌泥]件發(fā)送失敗不做重發(fā)(該問題將通過郵件服務(wù)器運維部門向廠商反映)。
下面是判斷郵件是否合法的邏輯:
1.SMTP是工作在兩種情況下:一是電子郵件從客戶機(jī)傳輸?shù)椒?wù)器;二是從某一個服務(wù)器傳輸?shù)搅硪粋€服務(wù)器
2.SMTP是個請求/響應(yīng)協(xié)議,命令和響應(yīng)都是基于ASCII文本,并以CR和LF符結(jié)束。響應(yīng)包括一個表示返回狀態(tài)的三位數(shù)字代碼
3.SMTP在TCP協(xié)議25號端口監(jiān)聽連接請求
4.連接和發(fā)送過程
SMTP協(xié)議說復(fù)雜也不復(fù)雜,說簡單如果你懂得Socket。不過現(xiàn)在只是我們利用的就是第一條中說的,從客戶機(jī)傳輸?shù)椒?wù)器,當(dāng)我們向一臺服務(wù)器發(fā)送郵件時,郵件服務(wù)器會首先驗證郵件發(fā)送地址是否真的存在于本服務(wù)器上。
5 操作的步驟如下:
連接服務(wù)器的25端口(如果沒有郵件服務(wù),連了也是白連)
發(fā)送helo問候
發(fā)送mail from命令,如果返回250表示正確可以,連接本服務(wù)器,否則則表示服務(wù)器需要發(fā)送人驗證。
發(fā)送rcpt to命令,如果返回250表示則Email存在
發(fā)送quit命令,退出連接
基于上面這個邏輯,我們封裝郵件服務(wù)器形成Socket,發(fā)送命令,根據(jù)返回值來判斷郵件地址是否合法:
具體代碼如下:
import java.io.*; import java.net.*; import java.util.*; import javax.naming.*; import javax.naming.directory.*; public class SMTPMXLookup { private static int hear( BufferedReader in ) throws IOException { String line = null; int res = 0; while ( (line = in.readLine()) != null ) { String pfx = line.substring( 0, 3 ); try { res = Integer.parseInt( pfx ); } catch (Exception ex) { res = -1; } if ( line.charAt( 3 ) != '-' ) break; } return res; } private static void say( BufferedWriter wr, String text ) throws IOException { wr.write( text + "\r\n" ); wr.flush(); return; } private static ArrayList getMX( String hostName ) throws NamingException { // Perform a DNS lookup for MX records in the domain Hashtable env = new Hashtable(); env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); DirContext ictx = new InitialDirContext( env ); Attributes attrs = ictx.getAttributes ( hostName, new String[] { "MX" }); Attribute attr = attrs.get( "MX" ); // if we don't have an MX record, try the machine itself if (( attr == null ) || ( attr.size() == 0 )) { attrs = ictx.getAttributes( hostName, new String[] { "A" }); attr = attrs.get( "A" ); if( attr == null ) throw new NamingException ( "No match for name '" + hostName + "'" ); } // Huzzah! we have machines to try. Return them as an array list // NOTE: We SHOULD take the preference into account to be absolutely // correct. This is left as an exercise for anyone who cares. ArrayList res = new ArrayList(); NamingEnumeration en = attr.getAll(); while ( en.hasMore() ) { String mailhost; String x = (String) en.next(); String f[] = x.split( " " ); // THE fix ************* if (f.length == 1) mailhost = f[0]; else if ( f[1].endsWith( "." ) ) mailhost = f[1].substring( 0, (f[1].length() - 1)); else mailhost = f[1]; // THE fix ************* res.add( mailhost ); } return res; } public static boolean isAddressValid( String address ) { // Find the separator for the domain name int pos = address.indexOf( '@' ); // If the address does not contain an '@', it's not valid if ( pos == -1 ) return false; // Isolate the domain/machine name and get a list of mail exchangers String domain = address.substring( ++pos ); ArrayList mxList = null; try { mxList = getMX( domain ); } catch (NamingException ex) { return false; } // Just because we can send mail to the domain, doesn't mean that the // address is valid, but if we can't, it's a sure sign that it isn't if ( mxList.size() == 0 ) return false; // Now, do the SMTP validation, try each mail exchanger until we get // a positive acceptance. It *MAY* be possible for one MX to allow // a message [store and forwarder for example] and another [like // the actual mail server] to reject it. This is why we REALLY ought // to take the preference into account. for ( int mx = 0 ; mx < mxList.size() ; mx++ ) { boolean valid = false; try { int res; // Socket skt = new Socket( (String) mxList.get( mx ), 25 ); BufferedReader rdr = new BufferedReader ( new InputStreamReader( skt.getInputStream() ) ); BufferedWriter wtr = new BufferedWriter ( new OutputStreamWriter( skt.getOutputStream() ) ); res = hear( rdr ); if ( res != 220 ) throw new Exception( "Invalid header" ); say( wtr, "EHLO rgagnon.com" ); res = hear( rdr ); if ( res != 250 ) throw new Exception( "Not ESMTP" ); // validate the sender address say( wtr, "MAIL FROM: <tim@orbaker.com>" ); res = hear( rdr ); if ( res != 250 ) throw new Exception( "Sender rejected" ); say( wtr, "RCPT TO: <" + address + ">" ); res = hear( rdr ); // be polite say( wtr, "RSET" ); hear( rdr ); say( wtr, "QUIT" ); hear( rdr ); if ( res != 250 ) throw new Exception( "Address is not valid!" ); valid = true; rdr.close(); wtr.close(); skt.close(); } catch (Exception ex) { // Do nothing but try next host ex.printStackTrace(); } finally { if ( valid ) return true; } } return false; } public static void main( String args[] ) { String testData[] = { "real@rgagnon.com", "you@acquisto.net", "fail.me@nowhere.spam", // Invalid domain name "arkham@bigmeanogre.net", // Invalid address "nosuchaddress@yahoo.com" // Failure of this method }; for ( int ctr = 0 ; ctr < testData.length ; ctr++ ) { System.out.println( testData[ ctr ] + " is valid? " + isAddressValid( testData[ ctr ] ) ); } return; } }
以上是判斷郵件地址是否合法的邏輯,如果郵件地址不合法,則將郵件地址從收件人列表中剔除。
private static String[] removeInvalidateAddress(String[] addresses, String mailFrom) { ArrayList<String> validateAddresses = new ArrayList<String>(); String normalAddress = null; int code; SMTPTransport smptTrans = null; if(StringUtils.isEmpty(mailFrom) || null == addresses) { return new String[0]; } String sendCmd = "MAIL FROM:" + normalizeAddress(mailFrom); try { smptTrans = (SMTPTransport)sendSession.getTransport("smtp"); smptTrans.connect(); code = smptTrans.simpleCommand(sendCmd); if(code != 250 && code != 251) { logger.error("send from invalidate" + mailFrom); } else { for(String address : addresses) { normalAddress = normalizeAddress(address); String cmd = "RCPT TO:" + normalAddress; code = smptTrans.simpleCommand(cmd); if(code == 250 || code == 251) { validateAddresses.add(address); } } } } catch(MessagingException e) { logger.error("Validate mail address error. send from " + mailFrom, e); } String[] result = validateAddresses.toArray(new String[validateAddresses.size()]); return result; } private static String normalizeAddress(String addr) { if ((!addr.startsWith("<")) && (!addr.endsWith(">"))) return "<" + addr + ">"; else return addr; }
以上是本文的全部內(nèi)容,希望大家能夠理解,對大家有所幫助。
相關(guān)文章
spring?boot?使用Mybatis-plus查詢方法解析
這篇文章主要介紹了spring?boot?使用Mybatis-plus查詢方法解析,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09SpringCloud?Feign使用ApacheHttpClient代替默認(rèn)client方式
這篇文章主要介紹了SpringCloud?Feign使用ApacheHttpClient代替默認(rèn)client方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03在安卓系統(tǒng)中插入表情到光標(biāo)位置的代碼詳解
這篇文章主要介紹了在安卓系統(tǒng)中插入表情到光標(biāo)位置的代碼詳解,利用Java代碼在EditText控件中實現(xiàn),需要的朋友可以參考下2015-07-07