使用SMTP协议通过IPv6地址发送邮件失败的解决办法
问题概述
使用smtp.mail.ru作为SMTP服务器时,该域名解析出多个IP地址(包含IPv6地址),手动遍历这些IP发送邮件时,遇到IPv6地址会抛出连接异常:
org.springframework.mail.MailSendException: Mail server connection failed; nested exception is com.sun.mail.util.MailConnectException: Couldn't connect to host, port: 2a00:1148:db00:0:0:0:0:8, 465; timeout -1; nested exception is: java.net.SocketException: Network unreachable. Failed messages: com.sun.mail.util.MailConnectException: Couldn't connect to host, port: 2a00:1148:db00:0:0:0:0:8, 465; timeout -1; nested exception is: java.net.SocketException: Network unreachable
当前获取IP的代码:
InetAddress[] addresses = InetAddress.getAllByName("smtp.mail.ru"); log.warn("Read addresses {}", Arrays.asList(addresses));
执行结果显示包含IPv4和IPv6地址:
2023-06-22 16:37:09.216 WARN 1 --- [ main] r.v.u.e.s.MyClasss : Read addresses [smtp.mail.ru/94.100.180.160, smtp.mail.ru/217.69.139.160, smtp.mail.ru/2a00:1148:db00:0:0:0:0:8]
邮件发送逻辑为手动切换JavaMailSenderImpl的host为遍历到的IP地址,再执行发送操作。
解决办法
1. 过滤IPv6,仅使用IPv4地址
既然IPv6连接失败,直接在获取IP阶段筛选出仅IPv4的地址,避免无效尝试。修改getNextAddress方法:
private String getNextAddress(){ InetAddress[] all = InetAddress.getAllByName(hostName); // 筛选所有IPv4地址 List<InetAddress> ipv4List = Arrays.stream(all) .filter(addr -> addr instanceof Inet4Address) .collect(Collectors.toList()); if (ipv4List.isEmpty()) { throw new IllegalStateException(hostName + " 未找到可用IPv4地址"); } // 循环获取下一个地址,防止索引越界 index = (index + 1) % ipv4List.size(); return ipv4List.get(index).getHostAddress(); }
2. 连接失败自动重试下一个IP
不想过滤IPv6的话,添加重试逻辑:连接当前IP失败时,自动切换到下一个地址重试,直到发送成功或所有IP尝试完毕。调整发送代码:
public void foo() { int tryTimes = 0; InetAddress[] allAddrs = InetAddress.getAllByName(hostName); boolean sendSuccess = false; while (!sendSuccess && tryTimes < allAddrs.length) { try { sender.setHost(getNextAddress()); sender.send(message); sendSuccess = true; } catch (MailSendException e) { tryTimes++; log.error("通过{}发送邮件失败,进行第{}次重试", sender.getHost(), tryTimes, e); // 所有IP尝试完毕仍失败,抛出异常 if (tryTimes >= allAddrs.length) { throw e; } } } }
对应的getNextAddress需要保证循环取地址:
private int index = 0; private String getNextAddress(){ InetAddress[] all = InetAddress.getAllByName(hostName); index = (index + 1) % all.length; return all[index].getHostAddress(); }
3. 全局设置JVM优先使用IPv4
如果整个应用环境都无法使用IPv6,可让JVM默认优先使用IPv4。两种方式:
- 在JVM启动参数中添加:
-Djava.net.preferIPv4Stack=true - 在代码的网络操作前设置系统属性:
System.setProperty("java.net.preferIPv4Stack", "true");
设置后InetAddress.getAllByName会优先返回IPv4地址,后续网络操作默认走IPv4。
4. 直接使用域名(最省心)
JavaMailSenderImpl本身支持直接用域名作为host,JavaMail底层会自动处理域名解析后的IP遍历和重试逻辑,无需手动切换IP。修改代码如下:
@Autowired private JavaMailSenderImpl sender; // 初始化邮件发送配置 @PostConstruct public void initMailSender() { sender.setHost("smtp.mail.ru"); sender.setPort(465); // smtp.mail.ru的SSL端口为465 sender.setUsername("你的邮箱@mail.ru"); sender.setPassword("你的邮箱密码"); // 启用SSL加密 Properties props = sender.getJavaMailProperties(); props.put("mail.smtp.ssl.enable", "true"); } public void foo() { sender.send(message); }
这种方式下,若某个IP连接失败,底层会自动切换至下一个IP重试,无需手动实现遍历逻辑。
内容的提问来源于stack exchange,提问作者gstackoverflow
相关产品推荐
相关产品推荐

