使用java語言基於SMTP協議手寫郵件客戶端
1. 說明
電子郵件是網際網路上常見的應用,他是網際網路早期的產品,直至今日依然受到廣大使用者的喜愛(在中國可能因為文化背景不同,電子郵件只在辦公的時候常用)。
電子郵件系統由以下幾個部分組成:
- 使用者代理
- 郵件伺服器
- 郵件傳輸協議
總所周知,目前市面上流行的電子郵箱有qq郵箱,163郵箱等,我們可以去申請一個qq郵箱或者163郵箱,原因是因為騰訊和網易提供了郵件伺服器。
同時我們也知道我們不僅僅可以通過qq郵箱的官方客戶端收發郵件,而且可以通過其他客戶端登入qq郵箱,比如說網易郵箱大師等。這說明郵件服務商提供了郵件伺服器,但是使用者代理卻不侷限與該廠商,而使用者代理是通過郵件傳輸協議與郵件伺服器進行通訊的,也就是說我們只要理解了郵件傳輸協議,瞭解一門網路程式語言,就可以動手實現我們自己的郵件客戶端了。
那麼我們開始實現吧;
2. SMTP協議
SMTP的全稱是Simple Mail Transfer Protocol,簡單郵件傳輸協議。顧名思義,這個協議十分的簡單,通過對該協議的RFC文件的閱讀,我們可以掌握該協議的基本內容,瞭解從使用者代理與郵件伺服器的通訊規則。
3 準備工作
- 閱讀SMTP協議的RFC文件
- 搭Maven環境
- 編寫程式碼
4. SMTP協議精要
RFC文件當然是英文的,本來非常害怕,但是發現他的內容其實很少,所以邊看文件邊查詞典讀了兩遍(千萬別怕),下面是主要內容介紹。
SMTP協議分為標準SMTP協議和擴充套件SMTP協議,標準SMTP協議是1982年在RFC821 文件中定義的,而擴充套件SMTP協議是1995年在RFC1869 文件中定義的。擴充套件SMTP協議在標準 SMTP協議基礎上的改動非常小,主要增加了郵件安全方面的認證功能,現在我們說的SMTP協議基本上都是擴充套件SMTP協議。
introduction介紹:
主要介紹了SMTP擴充套件協議為訊息傳輸代理提供了一個穩定的高效的基礎(也就是說SMTP協議可以用來實現訊息傳輸代理客戶端,也急速郵件客戶端)。然後說明了擴充套件的內容。
SMTP擴充套件協議的框架
SMTP傳輸的是郵件物件,郵件物件包括封面和內容
- 封面包括髮件人的地址,多個收件人的地址和交付模式,使用一系列的協議單元傳送。
- 內容包括頭部和主題兩個部分,使用SMTP資料協議單元傳送,頭部包括一系列鍵值對,頭部總是使用ASCII編碼
SMTP協議包含得多指令,但是隻需要用到以下指令就可以完成簡單的郵件傳送
ehlo <domain>
如 ehlo zeng
與SMTP協議建立連線後需要傳送的第一條命令。
auth para
設定驗證方式,如
auth login
mail from: <傳送者郵箱>
設定傳送者郵箱,如
mail from:<xxxx@qq.com>
rcpt to:<收件者郵箱>
設定收件者郵箱,如
rcpt to:<xxxx@qq.com>
data
表示將要傳送郵件的內容,這個命令後面的傳送都是郵件內容
quit
結束郵件傳送
所有命令末尾都是回車換行
5. 原始碼
import com.sun.xml.internal.messaging.saaj.util.Base64;
import lombok.Data;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* @author zeng
*/
public class MyEmailClient {
public static void main(String[] args) throws IOException {
//敏感資訊。。
Token token=new Token("",25,"","");
Socket socket=null;
PrintWriter printWriter=null;
BufferedReader br=null;
try {
//1. 連線smtp郵箱伺服器
socket=new Socket(token.getAddress(),token.getPort());
printWriter=new PrintWriter(socket.getOutputStream(),true);
br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
//2. 第一條命令 ehlo
printWriter.println("ehlo zeng");
System.out.println(br.readLine());
//3. 傳送,auth
printWriter.println("auth login");
System.out.println(br.readLine());
//4. 使用者名稱和密碼
printWriter.println(token.getUserName());
printWriter.println(token.getPassWord());
//會有一大串資訊返回,如果最後返回235 Authentication successful則成功
String temp=null;
while ((temp=br.readLine())!=null){
System.out.println(temp);
if ("235 Authentication successful".equals(temp)){
break;
}
}
System.out.println("認證成功");
//設定發件人和收件人,敏感資訊
String sentUser="";
String recUser="";
printWriter.println("mail from:<"+sentUser+">");
System.out.println(br.readLine());
printWriter.println("rcpt to:<"+recUser+">");
System.out.println(br.readLine());
//設定data
printWriter.println("data");
System.out.println(br.readLine());
//設定郵件主題
printWriter.println("subject:test");
printWriter.println("from:"+sentUser);
printWriter.println("to:"+recUser);
//設定郵件格式
printWriter.println("Content-Type: text/plain;charset=\"utf8\"");
printWriter.println();
//郵件正文
printWriter.println("來自java手寫smtp郵件客戶端");
printWriter.println(".");
printWriter.print("");
System.out.println(br.readLine());
//退出
printWriter.println("rset");
System.out.println(br.readLine());
printWriter.println("quit");
System.out.println(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}finally {
//釋放連線
socket.close();
printWriter.close();
br.close();
}
}
}
@Data
class Token{
String address;
Integer port;
String userName;
String passWord;
Token(String address, Integer port, String userName, String passWord) {
this.address = address;
this.port = port;
this.userName = new String(Base64.encode(userName.getBytes()));
this.passWord = new String(Base64.encode(passWord.getBytes()));
}
}
6.題外話
之所以寫這個的原因是自己把計網考完後閱讀了《計算機網路 自頂向下方法》這本書,該書應用層協議的課後習題就有一個實現郵件客戶端,當時看到這個題目的時候,感覺不可思議,因為之前課堂上學計網的時候都是一些理論的知識,真沒想過自己動手寫程式碼。之前在寫Spring程式碼的時候也用過mail相關的類,所以自己也決定通過讀RFC文件去實現一個自己的郵件客戶端,以幫助我發現更多的樂趣。