一、背景简介
邮件传输协议包括SMTP(简单邮件传输协议,RFC821)及其扩充协议MIME;邮件接收协议包括POP3和功能更强大的IMAP协议;服务邮件发送的服务器其端口为25(开启ssl可能使用465或587端口),服务邮件接收的服务器端口为110。
1、 SMTP(简单邮件传输协议)
SMTP是一种提供可靠且有效的电子邮件传输的协议。SMTP是建立在FTP文件传输服务上的一种邮件服务,主要用于系统之间的邮件信息传递,并提供有关来信的通知。在SMTP握手阶段,客户端向SMTP服务器分别指定发件人和收件人的电子邮件地址。握手阶段完毕,SMTP服务器把客户端发出的邮件消息添加到发信队列中,通过TCP提供的可靠数据传输服务把该消息准确地传送到收件人的服务器。
连接和发送过程如下:
(1)建立TCP连接。
(2)客户端发送 HELO 命令以标识发件人自己的身份,客户端发送 MAIL 命令。服务器以OK作为响应,表明准备接收。
(3)使用 AUTH 命令登录SMTP服务器,输入用户名和密码(注意,用户名和密码都需要base64加密)。
(4)客户端发送 RCPT 命令,标识该电子邮件的计划接收人,可以有多个RCPT行。服务器以OK作为响应,表示愿意为收件人发送邮件。
(5)协商结束后,使用 DATA 命令发送。
(6)以 . 号表示结束,输入内容一起发送出去,结束此次发送,用 QUIT 命令退出。
2、设置QQ邮箱
(1)对于QQ邮箱需要先设置独立密码,然后才能开启支持SMTP客户端软件
(2) 在设置->账号中启动POP3/SMTP服务和IMAP/SMTP服务
(3) 发送指定短信后即可获得授权码
二、BASE64编码程序
为什么需要base64编码程序?因为在SMTP过程中,使用 AUTH 命令登录SMTP服务器,输入用户名和密码需要base64加密后才能有效传输!
package chapter07;
public class BASE64 {
public static void main(String[] args) {
String userName="你的邮箱";
String authCode = "你的授权码";
//显示邮箱名的base64编码结果
System.out.println(encode(userName));
//显示授权码的base64编码结果
System.out.println(encode(authCode));
}
public static String encode(String str) {
java.util.Base64.Encoder encoder = java.util.Base64.getEncoder();
String encodeMsg = null;
try
{
encodeMsg = encoder.encodeToString(str.getBytes("UTF-8"));
}
catch (Exception e)
{
e.printStackTrace();
}
return encodeMsg;
}
}
三、客户端编码程序
1、发送方法
public void send(String msg) {
//输出字符流,由Socket调用系统底层函数,经网卡发送字节流
pw.println(msg);
try {
//进行邮件交互、发送smtp指令之间应该暂停一段时间
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2、发送按钮部分代码
String smtpAddr = tfSmtpAddr.getText().trim();
String smtpPort = tfSmtpPort.getText().trim();
try {
tcpMailClient = new TCPMailClient(smtpAddr, smtpPort);
tcpMailClient.send("HELO myfriend");
tcpMailClient.send("AUTH LOGIN");
String userName = "你的完整邮箱地址";
String authCode = "申请的授权码";
String msg = BASE64.encode(userName);
tcpMailClient.send(msg);
msg = BASE64.encode(authCode);
tcpMailClient.send(msg);
msg = "MAIL FROM:<" + tfSenderAddr.getText().trim() + ">";
tcpMailClient.send(msg);
msg = "RCPT TO:<" + tfRecieverAddr.getText().trim() + ">";
tcpMailClient.send(msg);
msg = "DATA";
tcpMailClient.send(msg);
msg = "FROM:" + tfSenderAddr.getText().trim();
tcpMailClient.send(msg);
msg = "SUBJECT:" + tfSubject.getText().trim();
tcpMailClient.send(msg);
msg = "TO:" + tfRecieverAddr.getText().trim();
tcpMailClient.send(msg);
//发送空行,隔开邮件头和邮件正文
tcpMailClient.send("\n");
//省略……
3、TCPMailClientFX2完整代码
package chapter07;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.IOException;
public class TCPMailClientFX2 extends Application {
private Button btnExit = new Button("退出");
private Button btnSend = new Button("发送");
private TextArea taSend = new TextArea();
private TextArea taDisplay = new TextArea();
private TextField tfIP = new TextField("smtp.qq.com");
private TextField tfPort = new TextField("25");
private TextField tfSender = new TextField("你的邮件地址");
private TextField tfReceiver = new TextField("目的邮件地址");
private TextField tfTitle = new TextField();
private TCPMailClient tcpMailClient;
private Thread readThread;
private Thread sendThread;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
BorderPane mainPane = new BorderPane();
VBox head = new VBox();
HBox connHbox = new HBox();
connHbox.setAlignment(Pos.CENTER);
connHbox.setSpacing(10);
connHbox.getChildren().addAll(new Label("邮件服务器地址:"), tfIP, new Label("邮件服务器端口:"), tfPort);
HBox addrHbox = new HBox();
addrHbox.setAlignment(Pos.CENTER);
addrHbox.setSpacing(10);
addrHbox.getChildren().addAll(new Label("邮件发送者地址:"), tfSender, new Label("邮件接收者地址:"), tfReceiver);
HBox titleHbox = new HBox();
tfTitle.setPrefWidth(400);
titleHbox.setAlignment(Pos.CENTER);
titleHbox.getChildren().addAll(new Label("邮件标题:"), tfTitle);
head.getChildren().addAll(connHbox, addrHbox, titleHbox);
head.setSpacing(20);
mainPane.setTop(head);
HBox middle = new HBox();
middle.setAlignment(Pos.CENTER);
VBox vBox = new VBox();
vBox.setSpacing(10);
vBox.setPadding(new Insets(10, 20, 10, 20));
// 设置发送信息的文本框
// 自动换行
taDisplay.setWrapText(true);
// 只读
taDisplay.setEditable(false);
vBox.getChildren().addAll(new Label("信息显示区: "), taDisplay);
VBox.setVgrow(taDisplay, Priority.ALWAYS);
VBox mailVBox = new VBox();
mailVBox.setPadding(new Insets(10, 20, 10, 20));
// 设置发送信息的文本框
// 自动换行
taSend.setWrapText(true);
mailVBox.getChildren().addAll(new Label("邮件正文: "), taSend);
mailVBox.setSpacing(10);
VBox.setVgrow(taSend, Priority.ALWAYS);
middle.getChildren().addAll(mailVBox, vBox);
mainPane.setCenter(middle);
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setPadding(new Insets(10, 20, 10, 20));
hBox.setAlignment(Pos.CENTER_RIGHT);
String ip = tfIP.getText().trim();
String port = tfPort.getText().trim();
btnExit.setOnAction(event -> {
exit();
});
btnSend.setOnAction(event -> {
String smtpAddr = tfIP.getText().trim();
String smtpPort = tfPort.getText().trim();
try {
tcpMailClient = new TCPMailClient(smtpAddr, smtpPort);
} catch (IOException e) {
e.printStackTrace();
}
// 启用接收信息进程
readThread = new Thread(() -> {
String msg = null;
while ((msg = tcpMailClient.receive()) != null) {
String msgTemp = msg;
Platform.runLater(() -> {
taDisplay.appendText(msgTemp + "\n");
});
}
Platform.runLater(() -> {
taDisplay.appendText("对话已关闭!\n");
readThread.start();
sendThread = new Thread(() -> {
tcpMailClient.send("HELO myfriend");
tcpMailClient.send("AUTH LOGIN");
String userName = "你的邮箱";
String authCode = "你的授权码";
String msg = BASE64.encode(userName);
tcpMailClient.send(msg);
msg = BASE64.encode(authCode);
tcpMailClient.send(msg);
msg = "MAIL FROM:<" + tfSender.getText().trim() + ">";
tcpMailClient.send(msg);
msg = "RCPT TO:<" + tfReceiver.getText().trim() + ">";
tcpMailClient.send(msg);
msg = "DATA";
tcpMailClient.send(msg);
msg = "FROM:" + tfSender.getText().trim();
tcpMailClient.send(msg);
msg = "SUBJECT:" + tfTitle.getText().trim();
tcpMailClient.send(msg);
msg = "TO:" + tfReceiver.getText().trim();
tcpMailClient.send(msg);
tcpMailClient.send("\n");
msg = "";
tcpMailClient.send(msg);
msg = taSend.getText().trim();
tcpMailClient.send(msg);
msg = ".";
tcpMailClient.send(msg);
msg = "QUIT";
tcpMailClient.send(msg);
btnSend.setDisable(false);
});
sendThread.start();
// 禁用发送按钮
btnSend.setDisable(true);
});
hBox.getChildren().addAll(btnSend, btnExit);
mainPane.setBottom(hBox);
Scene scene = new Scene(mainPane, 700, 400);
// 响应窗体关闭
primaryStage.setOnCloseRequest(event -> {
exit();
});
primaryStage.setScene(scene);
primaryStage.show();
}
public void exit() {
// 系统退出时,单独的读线程没有结束,因此会出现异常。
// 解决方案:在这里通知线程中断,在线程循环中增加条件检测当前线程是否被中断。
// readThread.interrupt();
readThread.stop();
System.exit(0);
}
}