当前位置: 首页 > 工具软件 > dnsjava > 使用案例 >

JAVA 模拟邮箱服务器与主流邮箱传输邮件,使用(netty,javax.mail,dnsjava)

经清野
2023-12-01

实现效果

    1、接收QQ邮箱发过来的邮件
    
    2、发送邮件到QQ邮箱

基础概念

    邮件从客户端发送到服务器这一过程中的角色
    MUA(Mail User Agent):邮件用户代理,接收邮件的客户端,使用方是用户,负责把编辑好的邮件发送到MTA
    MTA(Mail Transfer Agent):邮件传输代理,邮箱服务器,负责邮件的接收,传输,接收之后检查收件人是不是属于自己邮箱,如果是,则交给MDA,不是则继续传输到下一个MTA
    MDA(Mail Delivery Agent):邮件投递代理,保存邮件,等待收件人领取,可以做邮件的分析,删除等功能
    
    邮件使用过程中常用协议
    SMTP(Simple Mail Transfer Protocol)简单邮件传输协议,端口号常用(25,465;其中25是邮件服务器默认端口,465是加密端口) 主要负责发送邮件,MUA往MTA发邮件使用smtp这一协议,MTA对MTA也用smtp协议发邮件
    POP3(Post Office Protocol3)邮局通讯协议 端口号常用(110)主要负责从邮箱服务器获取邮件
    IMAP(Internet Mail Access Protocol)交互式邮件存取协议,端口号常用(143)主要负责对本地和服务器上的邮件操作进行同步

    发送一封邮件指令如下,其中\r\n表示换行,如果在命令行里操作可以去掉\r\n,在Java中拼接字符串要带上,

HELO mail.yuming.com \r\n
MAIL FROM:<zhangsan@yuming.com>\r\n
RCPT TO:<mahuateng@qq.com>\r\n
DATA\r\n
From: 张三 <zhangsan@yuming.com>\r\n
Sender: 张三 <zhangsan@yuming.com>\r\n
Reply-To: 张三 <zhangsan@yuming.com>\r\n
To: mahuateng@qq.com\r\n
Message-ID: <728800004.0.1411122805115@my-PC>\r\n
Subject: 主题.\r\n
MIME-Version: 1.0\r\n
Content-Type: text/plain; charset=us-ascii\r\n
Content-Transfer-Encoding: 7bit\r\n
内容 . \r\n
.\r\n
QUIT\r\n

实现过程

1、域名配置

    首先有个域名,我是从阿里云买的,
    
    然后要有公网IP,服务器不要用云服务器,因为大多数会把25端口封掉,所以要用自己的服务器,可以是linux也可以是windows,服务器开放25、80端口,做解析用
    
    进入域名解析配置页面,新增A解析和MX解析

2、模拟邮箱服务器接口功能描述

    主要模拟接收邮件和发送邮件这两个操作
    
    接收邮件功能:通过netty 监听25端口,接收客户端指令,分析指令,最终接收邮件并返回状态
    
    发送邮件功能:如果收件方是第三方(比如QQ)邮箱,那先通过dnsjava工具获取qq.com的邮箱服务器地址,然后通过javax.mail工具发送邮件到QQ邮箱,

    当然也可以不用javax.mail,用netty或者是自己写socket也能实现发送邮件,原理都是访问第三方邮件服务器的25端口,然后发送邮件指令,这时候QQ邮箱是会校验发件人是否合法的,所以要先把域名解析做好

3、Java代码实现

maven项目 pom文件引入依赖

    <!-- 接收邮件用 -->
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.34.Final</version>
    </dependency>
    <!-- 服务器间发送邮件用 -->
    <dependency>
        <groupId>com.sun.mail</groupId>
        <artifactId>javax.mail</artifactId>
        <version>1.6.2</version>
    </dependency>
    <!-- 解析dns用 -->
    <dependency>
        <groupId>dnsjava</groupId>
        <artifactId>dnsjava</artifactId>
        <version>3.5.0</version>
    </dependency>

netty接收邮件代码

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

/**
 * 邮件服务器,netty
 */
public class SmtpServer {
	/** 接收线程 */
	private static EventLoopGroup bossGroup = new NioEventLoopGroup();
	/** 工作线程 */
	private static EventLoopGroup workerGroup = new NioEventLoopGroup();
	/** channel处理结果 */
	private static ChannelFuture channelFuture;

	/**
	 * 启动服务
	 */
	public static void start() throws Exception {
		try {
			ServerBootstrap b = new ServerBootstrap();

			b.group(bossGroup, workerGroup)// 配置接收线程和工作线程
					.channel(NioServerSocketChannel.class)// 设置管道
					.option(ChannelOption.SO_BACKLOG, Integer.valueOf(1024))// 最大连接数
					.childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel ch) throws Exception {
							ChannelPipeline pipeline = ch.pipeline();
							// 配置解析方式、处理handler
							pipeline.addLast(new DelimiterBasedFrameDecoder(8 * 1024, Delimiters.lineDelimiter()));
							pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
							pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
							pipeline.addLast(new SmtpServerHandler());
						}

					});// 初始化器,绑定I/O事件的处理类
			channelFuture = b.bind(25).sync();

			channelFuture.addListener((ChannelFutureListener) channelFuture -> {
				System.out.println("邮件服务器已启动");
			});
			channelFuture.channel().closeFuture().sync();
		} finally {
			shutdown();
			System.out.println("邮件服务器已关闭");
		}
	}

	/**
	 * 关闭服务
	 */
	public static void shutdown() {
		if (channelFuture != null) {
			channelFuture.channel().close().syncUninterruptibly();
		}
		if ((bossGroup != null) && (!bossGroup.isShutdown())) {
			bossGroup.shutdownGracefully();
		}
		if ((workerGroup != null) && (!workerGroup.isShutdown())) {
			workerGroup.shutdownGracefully();
		}
	}

	public static void main(String[] args) {
		try {
			start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class SmtpServerHandler extends SimpleChannelInboundHandler<String> {

	private boolean finishData = false;
	private boolean userAcc = false;
	private boolean userPwd = false;

	private String user = null;
	private String pwd = null;
	private StringBuffer emailData = new StringBuffer();

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		Channel channel = ctx.channel();
		// 与客户端成功建立连接,并回复状态
		channel.writeAndFlush("220\r\n");
	}

	@Override
	public void channelRead0(ChannelHandlerContext ctx, String msg) {
		Channel channel = ctx.channel();
		String line = msg;

		System.out.println(String.format("接收到指令:【%s】", line));

		if (finishData == false) {
			onCommand(channel, line);
		} else {
			onData(channel, line);
		}
	}

	private void onCommand(Channel channel, String line) {

		String command;
		int i = line.indexOf(" ");
		if (i > 0) {
			command = line.substring(0, i).toUpperCase();
		} else {
			command = line.toUpperCase();
		}

		if (command.length() == 0) {
			channel.writeAndFlush("500\r\n");
			return;
		}

		switch (command) {
		case "HELO":
		case "EHLO":

			// TODO 一般客户端需要传账号密码才可以发邮件,那这里需要返回服务器支持的账号校验方式 如:250-AUTH=LOGIN

			channel.writeAndFlush("250-AUTH LOGIN PLAIN XOAUTH XOAUTH2\r\n");

			channel.writeAndFlush("250-AUTH=LOGIN\r\n");

			channel.writeAndFlush("250\r\n");
			break;
		case "AUTH":

			// 接收到用户登录指令用户需要输入账号
			userAcc = true;
			channel.writeAndFlush("334 VXNlcm5hbWU6\r\n");
			break;
		case "MAIL":
			// 邮件发件人
			emailData.setLength(0);
			channel.writeAndFlush("250\r\n");
			break;
		case "RCPT":
			// 邮件接收人
			channel.writeAndFlush("250\r\n");
			break;
		case "DATA":
			// 准备接收文件
			finishData = true;
			channel.writeAndFlush("354\r\n");
			break;
		case "RSET":
			channel.writeAndFlush("250\r\n");
			break;
		case "QUIT":
			// 退出
			channel.writeAndFlush("221\r\n");
			break;
		default:

			if (userAcc) {
				userPwd = true;
				userAcc = false;

				user = line;
				channel.writeAndFlush("334 UGFzc3dvcmQ6\r\n");

			} else if (userPwd) {
				userPwd = false;

				pwd = line;

				System.out.println(String.format("用户账号【%s】,密码【%s】", user, pwd));

				if ("admin".equals(user) && "123456".equals(pwd)) {
					// 登录成功
					channel.writeAndFlush("235\r\n");
				} else {
					// 登录失败
					channel.writeAndFlush("530\r\n");
				}

			} else {
				channel.writeAndFlush("500 \r\n");
				channel.close();
			}

			break;
		}

	}

	private void onData(Channel channel, String line) {
		if (line.trim().equals(".")) {
			// 默认邮件结束符是一个.
			finishData = false;
			channel.writeAndFlush("250\r\n");

			System.out.println(String.format("新来了一封邮件【%s】", emailData.toString()));

			// TODO 保存邮件到本地,数据库,

			// TODO 检查收件人,是第三方邮箱需要把这封邮件转到第三方邮箱

		} else {
			if (line.startsWith("..")) {
				line = line.substring(1);
			}
			emailData.append(line);
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		Channel ch = ctx.channel();
		ch.close();
	}
}

服务器间发送邮件

import java.util.Properties;
import javax.mail.Address;
import javax.mail.Message.RecipientType;
import javax.mail.Session;
import javax.mail.URLName;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.Type;
import com.sun.mail.smtp.SMTPTransport;

/**
 * 邮箱服务器之间发邮件
 */
public class ServerToServer {

	public static void sendMsg() throws Exception {
		Properties prop = System.getProperties();
		Session session = Session.getInstance(prop, null);
		// 开启可以查看发送的指令以及服务器回复的内容
		session.setDebug(true);

		MimeMessage mimeMessage = new MimeMessage(session);

		mimeMessage.setSubject("主题");

		String nickName = "张三";
		InternetAddress sender = new InternetAddress(MimeUtility.encodeText(nickName) + " <zhangsan@yuming.com>");
		mimeMessage.setSender(sender);
		mimeMessage.setFrom(sender);

		mimeMessage.setRecipient(RecipientType.TO, new InternetAddress("mahuateng@qq.com"));
		mimeMessage.setReplyTo(new Address[] { sender });
		mimeMessage.setText("内容");

		//查询域名对应的邮箱服务器地址
		Lookup lookup = new Lookup("qq.com", Type.MX);
		lookup.run();
		if (lookup.getResult() != Lookup.SUCCESSFUL) {
			System.out.println("ERROR: " + lookup.getErrorString());
			return;
		}
		Record[] answers = lookup.getAnswers();

		String dns = null;
		for (Record record : answers) {
			dns = record.getAdditionalName().toString();
		}

		if (null != dns) {
			if (dns.endsWith(".")) {
				dns = dns.substring(0, dns.length() - 1);
			}
			SMTPTransport transport = (SMTPTransport) session.getTransport(new URLName("smtp", dns, 25, null, null, null));
			transport.connect();
			transport.sendMessage(mimeMessage, new Address[] { new InternetAddress("mahuateng@qq.com") });
		}

	}

	public static void main(String[] args) throws Exception {
		sendMsg();
	}

}

 类似资料: