公司邮箱目前使用的是Zimbra,该邮件服务器目前不甚稳定,经常出现重发、漏发问题。经测试,每100封邮件仅可成功发送98封左右,以下是测试数据:
测试用例1:100封,总用时约:16min;实收97封,失败3次,3次错误信息均为:javax.mail.MessagingException: Could not connect to SMTP host
测试用例2:100封,总用时约:16min;实收100封,失败2次,错误同上。加失败重发机制,失败后等待10s重发,最多重发3次;
测试用例3:每发一封,停留10s,总用时32min;实收100封,失败1次,错误同上;重发机制同用例2.
关于MessagingException的问题,可以参考:
javax.mail.MessagingException: Could not connect to SMTP host
我看了一下几种解释:1.网络;2.防火墙;3.服务器的自我保护,比如防止大批量发送时挂掉或者垃圾邮件,我觉得第三种解释靠谱一些。
针对这种问题,我增加了邮件重发,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
if
(sendHtmlMail_(mail)){
return
true
;
}
else
{
int
i =
0
;
//包含群组邮件,失败不重发
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
;
}
|
但这种机制又产生了新的问题,因邮件服务器不稳定导致在仅发送一次的情况下也会向邮件收件人发送邮件,且同一封邮件的收件人(包括抄送、密送)可能部分收到邮件、部分收不到邮件。
针对以上的问题,我们将重发机制去除,仅针对不合法邮件(即服务器上不存在的邮件地址)进行剔除,剔除后再进行发送。而对其他原因导致的邮件发送失败不做重发(该问题将通过邮件服务器运维部门向厂商反映)。
下面是判断邮件是否合法的逻辑:
1.SMTP是工作在两种情况下:一是电子邮件从客户机传输到服务器;二是从某一个服务器传输到另一个服务器
2.SMTP是个请求/响应协议,命令和响应都是基于ASCII文本,并以CR和LF符结束。响应包括一个表示返回状态的三位数字代码
3.SMTP在TCP协议25号端口监听连接请求
4.连接和发送过程
SMTP协议说复杂也不复杂,说简单如果你懂得Socket。不过现在只是我们利用的就是第一条中说的,从客户机传输到服务器,当我们向一台服务器发送邮件时,邮件服务器会首先验证邮件发送地址是否真的存在于本服务器上。
5 操作的步骤如下:
连接服务器的25端口(如果没有邮件服务,连了也是白连)
发送helo问候
发送mail from命令,如果返回250表示正确可以,连接本服务器,否则则表示服务器需要发送人验证。
发送rcpt to命令,如果返回250表示则Email存在
发送quit命令,退出连接
基于上面这个逻辑,我们封装邮件服务器形成Socket,发送命令,根据返回值来判断邮件地址是否合法:
具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|
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
;
}
}
|
以上是判断邮件地址是否合法的逻辑,如果邮件地址不合法,则将邮件地址从收件人列表中剔除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
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;
}
|
相关文章供参考:
本文转自 gaochaojs 51CTO博客,原文链接:http://blog.51cto.com/jncumter/1674178,如需转载请自行联系原作者