使用java語言基於SMTP協議手寫郵件客戶端

_zeng發表於2019-07-21

使用java語言基於SMTP協議手寫郵件客戶端

1. 說明

電子郵件是網際網路上常見的應用,他是網際網路早期的產品,直至今日依然受到廣大使用者的喜愛(在中國可能因為文化背景不同,電子郵件只在辦公的時候常用)。

電子郵件系統由以下幾個部分組成:

  • 使用者代理
  • 郵件伺服器
  • 郵件傳輸協議

總所周知,目前市面上流行的電子郵箱有qq郵箱,163郵箱等,我們可以去申請一個qq郵箱或者163郵箱,原因是因為騰訊和網易提供了郵件伺服器。

同時我們也知道我們不僅僅可以通過qq郵箱的官方客戶端收發郵件,而且可以通過其他客戶端登入qq郵箱,比如說網易郵箱大師等。這說明郵件服務商提供了郵件伺服器,但是使用者代理卻不侷限與該廠商,而使用者代理是通過郵件傳輸協議與郵件伺服器進行通訊的,也就是說我們只要理解了郵件傳輸協議,瞭解一門網路程式語言,就可以動手實現我們自己的郵件客戶端了。

那麼我們開始實現吧;

2. SMTP協議

SMTP的全稱是Simple Mail Transfer Protocol,簡單郵件傳輸協議。顧名思義,這個協議十分的簡單,通過對該協議的RFC文件的閱讀,我們可以掌握該協議的基本內容,瞭解從使用者代理與郵件伺服器的通訊規則。

3 準備工作

  1. 閱讀SMTP協議的RFC文件
  2. 搭Maven環境
  3. 編寫程式碼

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文件去實現一個自己的郵件客戶端,以幫助我發現更多的樂趣。

相關文章