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
首先有个域名,我是从阿里云买的,
然后要有公网IP,服务器不要用云服务器,因为大多数会把25端口封掉,所以要用自己的服务器,可以是linux也可以是windows,服务器开放25、80端口,做解析用
进入域名解析配置页面,新增A解析和MX解析
主要模拟接收邮件和发送邮件这两个操作
接收邮件功能:通过netty 监听25端口,接收客户端指令,分析指令,最终接收邮件并返回状态
发送邮件功能:如果收件方是第三方(比如QQ)邮箱,那先通过dnsjava工具获取qq.com的邮箱服务器地址,然后通过javax.mail工具发送邮件到QQ邮箱,
当然也可以不用javax.mail,用netty或者是自己写socket也能实现发送邮件,原理都是访问第三方邮件服务器的25端口,然后发送邮件指令,这时候QQ邮箱是会校验发件人是否合法的,所以要先把域名解析做好
<!-- 接收邮件用 -->
<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>
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();
}
}