1. 程式人生 > >再談用java實現Smtp發送郵件之Socket編程

再談用java實現Smtp發送郵件之Socket編程

~~ 成功 剛才 還要 登陸 computer and ont sys

很多其它內容歡迎訪問個人站點 http://icodeyou.com

前幾天利用Socket實現了用java語言搭建webserver,全程下來應該會對Socket這個東西已經使用的很熟悉了。盡管抽象,可是使用過一次之後就會感受到它在網絡通信上的作用是多麽的強大。正好,今天就繼續用Socket來練習使用下面Smtp協議發送一封簡單的電子郵件。今天的故事呢,是我要約我女神出去吃飯啦啦啦~~~所以,面對Smtp。僅僅許成功,不許失敗。

全局假定我的郵箱為[email protected] 女神的郵箱為[email protected]

password都是 computer (呦,還是個情侶郵箱~)

為了更加體現程序猿的高大上。所以我選擇使用命令行的方式(SB程序猿。。

。)。打開cmd,在命令行裏輸入telnet,“嗯。竟然給我顯示telnet不是內部或外部命令”!完蛋,怎麽辦,這才第一步。就出師不利啊,看來今天要跪,趕快想解決的方法。進入 控制面板---程序和功能---啟動或關閉windows功能---telnetclient。勾選上然後確定就可以(有些人的電腦還會看到“telnet服務端”,註意別選錯了。服務端是指你的電腦作為server讓別人登陸的,而咱們如今要做的是自己的電腦作為client登陸郵箱server)

技術分享

再在cmd中試一下,直接輸入 telnet smtp.163.com 25 ,第一行會顯示郵件server返回的歡迎信息,我這裏返回的就是 “220 163.com Anti-spam GT for Coremail System (163com[20121016])” 當中220說明郵件server跟我已經建立了連接,它已經開始想要幫幫我了,哈哈。友好一點,跟server大哥打個招呼吧,輸入HELO huan ,(huan是隨便輸入的,輸入什麽都行。server是不會鳥你究竟輸入的是什麽),這時server返回的是“250 OK”,說明如今server等著我繼續發送命令了。

輸入 MAIL FROM:<[email protected]

> 這時server竟然給我返回了553,並告訴我須要認證。也是,既然登陸人家的server,總得有個人家的賬號吧。

所以接下來輸入AUTH LOGIN。server返回了334和我看不懂的東西,這是要求咱們要輸入username和password信息了。可是都知道,像usernamepassword這樣的信息是不能在互聯網裏進行明文傳輸的,而smtpserverusername和password採用的是base64編碼加密方式,所以在百度搜一個在線base64加密站點就好了。(比方http://base64.xpcha.com/)。如圖

技術分享

把得到的密文往控制臺中粘貼後回車,這時server會再要求我輸入password,跟剛才的方式一樣就可以,假設正確的話。會返回235。並告訴我認證成功了。Perfect。已經進展到一半了。繼續!

我以下就要告訴server從哪個郵箱發,發給誰。所以依次輸入MAIL FROM:<[email protected] > 回車 RCPT TO:<[email protected] > 回車 發件人和收件人設置好後。自然該告訴server我要發的內容是什麽了,所以輸入DATA後回車,server返回給我354,並讓我輸入,以<CR><LF>.<CR><LF>結尾,好。那就直接輸入正文: Can I date with you?

然後“回車” “.” “回車”來告訴server我的內容輸入完成了。它能夠發送了。這個時候觸目驚心的一幕出現了。server並沒有給我返回2XX的正確碼,而是給我返回了554。這是為什麽。server大哥不肯幫我了?

技術分享

看著它給我的鏈接,像是163自己的錯誤說明文檔。我就復制下來了,打開瀏覽器查看了一下,原來是163server覺得我發的是垃圾郵件。所以它拒絕給我發信。但是大哥,我這是真心的啊,哪是什麽垃圾郵件啊,求求你就讓我發送吧(想想也是,假設好多人都這樣給女神發了一堆的垃圾郵件,我會不高興的%>_<%)。

分析一下為什麽會被覺得是垃圾郵件吧:郵件得有主題(subject),發件人(from)。收件人(to)。郵件正文等。但是我僅僅寫了個郵件正文,或許server就把這個當成是垃圾郵件了。嗯,越臆想就覺得越有道理,來試一下吧。這次我輸入了DATA後。server讓我輸入內容,我先輸入了subject:Would you go on a date with me ? 然後回車 from:[email protected] 回車 to:[email protected] 回車 Can you eat a meal together? 回車 . 回車 哈哈,這個時候server給我返回的是Mail OK,我發送成功了!

接下來就是要等待女神的答復了。

。。所有過程見下圖:(留個問題,女神的郵箱裏肯定會收到這封郵件,可是會收到我原本想發的正文“Can you eat a meal together?

”嗎?假設不知道。查一下報文格式。或見以下的java程序,對照一下正文部分後面的數據格式差別)

技術分享


所有過程:

telnet smtp.163.com 25
S: 220 163.com Anti-spam GT for Coremail System <163com[20121016]>
C: HELO huan
S: 250 OK
C: AUTH LOGIN
S: 334 dXNlcm5hbWU6
C: Y25zbXRwMDE=
S: 334 UGFzc3dvcmQ6
C: Y29tcHV0ZXI=
S: 235 Authentication successful
C: MAIL FROM:<[email protected]>
S: 250 Mail OK
C: RCPT TO:<[email protected]>
S: 250 Mail OK
C: DATA
S: 354 End data with <CR><LF>.<CR><LF>
C: subject:Would you go on a date with me ?
C: from:[email protected]
C: to:[email protected]
C: Can you eat a meal together?
C: .
S: 250 Mail OK queued as smtp7,C8CowEDpwFYP_ERUQxX8AA--.14S2 1413807190
C: RSET
S: 250 OK
C: QUIT
S: 221 Bye


時候差點兒相同了,我認為女神應該會給我回復郵件了。怎麽看,打開瀏覽器進入163郵箱嗎?太out、太low了。直接命令行吧! telnet pop.163.com 110 ,剛才已經演示了所有的smtp命令。所以操作起來pop的應該非常easy了,直接上圖:

技術分享

看到女神回復我什麽了嗎。。

。簡直狗血。。。

好了。一次的收發郵件過程全都攻克了。並且結果是。,。被拒了。事實上呢。剛才沒說,我的女神是編號的,從女神0號,女神1號…女神n號,難道剛剛第0個女神拒絕我後我就頹了嗎?那怎麽行,我得越挫越勇啊,繼續給剩下的女神發郵件。但是,我這麽多女神,我不能給每一個女神發郵件都用這樣的命令行方式吧,那不虛死我。那麽問題來了——發郵件技術哪家強? 既然咱們是學計算機的,那就編軟件唄。讓軟件替咱們批量發,簡直Perfect!

所以又回到這次的議題上來了,怎麽用java實現smtp發送郵件?對,還是要請出大神Socket來幫忙。

經歷了上次Socket的折磨和剛才命令行的磨練。接下來就是把他們巧妙融合的時候了。所以,別走開。

1、定義一些咱們一會會用到的郵箱名,usernamepassword等信息(正常編程沒人會把username和password寫的這麽明確。。。

):

	String sender = "[email protected]"; 
        String receiver = "[email protected]"; 
        String password = "computer";

	//上文說過,這個username與password是要使用base64進行加密的。加密方法見下文附錄1具體解釋 
        String user = new BASE64Encoder().encode(sender.substring(0, sender.indexOf("@")).getBytes());  //截取出“cnsmtp01”並加密 
        String pass = new BASE64Encoder().encode(password.getBytes());   //加密 “computer”

2、建立Socket連接:

Socket socket = new Socket("smtp.163.com", 25);  //smtp服務使用25號port監聽

3、獲取該socket的輸入輸出流

        InputStream inputStream = socket.getInputStream(); 
        OutputStream outputStream = socket.getOutputStream(); 
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 
        PrintWriter writter = new PrintWriter(outputStream, true);  //我TM去 這個true太關鍵了。我剛才沒寫這個別坑了!你能夠不加這個試下效果。下文附錄2會寫到為什麽加true

4、發送HELO信息

        //HELO 
        writter.println("HELO huan"); 
        System.out.println(reader.readLine());


5、發送AUTH LOGIN信息

        //AUTH LOGIN 
        writter.println("auth login"); 
        System.out.println(reader.readLine()); 
        writter.println(user); 
        System.out.println(reader.readLine()); 
        writter.println(pass); 
        System.out.println(reader.readLine()); 
        //Above   Authentication successful


6、發送發件人和收件人信息

        //Set mail from   and   rcpt to 
        writter.println("mail from:<" + sender +">"); 
        System.out.println(reader.readLine()); 
        writter.println("rcpt to:<" + receiver +">"); 
        System.out.println(reader.readLine());


7、告訴server我要傳數據

        //Set data 
        writter.println("data"); 
        System.out.println(reader.readLine());


8、發郵件主題,收件人,發件人,正文

        writter.println("subject:女神,是我"); 
        writter.println("from:" + sender); 
        writter.println("to:" + receiver); 
        writter.println("Content-Type: text/plain;charset=\"gb2312\"");//假設發送正文必須加這個。並且以下要有一個空行 
        writter.println(); 
        writter.println("女神。晚上能夠共進晚餐嗎?"); 
        writter.println(".");//告訴server我發送的內容完成了 
        writter.println(""); 
        System.out.println(reader.readLine());


9、幫你發了郵件,感謝server,和它Say Goodbye吧,都不用請它吃飯,多好

        writter.println("rset"); 
        System.out.println(reader.readLine()); 
        writter.println("quit"); 
        System.out.println(reader.readLine());


所以,加上異常等操作,全部的代碼例如以下:

public class SMTPMain {
    public static void main(String[] args) { 
        String sender = "[email protected]"; 
        String receiver = "[email protected]"; 
        String password = "computer"; 
        String user = new BASE64Encoder().encode(sender.substring(0, sender.indexOf("@")).getBytes()); 
        String pass = new BASE64Encoder().encode(password.getBytes());
        try { 
            Socket socket = new Socket("smtp.163.com", 25); 
            InputStream inputStream = socket.getInputStream(); 
            OutputStream outputStream = socket.getOutputStream(); 
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 
            PrintWriter writter = new PrintWriter(outputStream, true);  //我TM去 這個true太關鍵了! 
            System.out.println(reader.readLine()); 
            //HELO 
            writter.println("HELO huan"); 
            System.out.println(reader.readLine()); 
            //AUTH LOGIN 
            writter.println("auth login"); 
            System.out.println(reader.readLine()); 
            writter.println(user); 
            System.out.println(reader.readLine()); 
            writter.println(pass); 
            System.out.println(reader.readLine()); 
            //Above   Authentication successful <pre name="code" class="java">            
            //Set mail from   and   rcpt to 
            writter.println("mail from:<" + sender +">"); 
            System.out.println(reader.readLine()); 
            writter.println("rcpt to:<" + receiver +">"); 
            System.out.println(reader.readLine()); 
           
            //Set data 
            writter.println("data"); 
            System.out.println(reader.readLine()); 
            writter.println("subject:女神,是我"); 
            writter.println("from:" + sender); 
            writter.println("to:" + receiver); 
            writter.println("Content-Type: text/plain;charset=\"gb2312\""); 
            writter.println(); 
            writter.println("女神,晚上能夠共進晚餐嗎?"); 
            writter.println("."); 
            writter.println(""); 
            System.out.println(reader.readLine()); 

            //Say GoodBye
            writter.println("rset"); 
            System.out.println(reader.readLine()); 
            writter.println("quit"); 
            System.out.println(reader.readLine()); 
            } catch (Exception e) {
                 e.printStackTrace(); 
           } 
        } 
  }


這下,我全然不怵藍翔的挖掘機了,這簡直就是我的約會神器。僅僅要把女神x號的郵箱一改,程序一執行,我這郵件就瞬間發出去了,哈哈,簡直機智如狗。

如今執行程序,看控制臺輸出

技術分享

多次執行程序,女神1號。2號…的郵箱裏都收到了例如以下圖的郵件(能夠把多個女神的郵箱加到集合[list map vector…]裏。然後再一循環。分分鐘搞定)

技術分享

至於什麽界面什麽的。就仁者見仁智者見智吧。習慣java的就swing,習慣android也能夠xml,事實上我是認為android更方便一些,控件更easy用一些。若是用java寫界面忘了的話,推薦使用 windows builder 。 能夠幫你非常快繪制出界面來。然後你要做的就是獲取控件,寫監聽器等等。

附錄1:

關於base64加密:

詳細什麽是base64加密,這樣的概念性的東西能在百度百科找到的我就不說了。說一下在java裏怎麽去用它。

1、首先下載一個工具jar文件,叫做“sun.misc.BASE64Decoder.jar” (百度搜就能找到,假設找不到能夠到我最後提供的本人github上去下載下來使用)

2、有了jar文件後。須要把jar包導入project,方法為:右鍵project名—Build Path—Configure Build Path—Add External JARS,選擇你剛下載的jar包後確定就能夠了

3、之後在java文件中寫到 new BASE64Encoder().encode(password.getBytes()); 時。會提示沒有導入相應的包,能夠按Ctrl + Shift + O(歐)來讓IDE自己主動為我們導入(前提是你的jar包導入沒問題)

附錄2:

說說PrintWriter.println()方法(或write()方法,事實上就差了一個換行,剩下參數什麽的都一樣)。PrintWriter writter = new PrintWriter(outputStream, true); 在PrintWritter的構造函數中。能夠不加true。也能夠加true,差別在於:加了true的話,在以下進行writter.println(“helloworld”);後,“helloworld”就會馬上發送出去;相反,不加true的話,必須在writter.println(“helloworld”);後 再調用writter.flush();來清空緩沖區,強制發送出去。我開始就沒有加true,並且沒調用flush()方法,我以為是serverSB了,結果。。

。。

附錄3:

為什麽telnet pop協議時登錄server輸入username和password時會明文,這讓我非常奇怪,希望有人幫我解答。

附錄4:

有沒有註意到在使用smtp協議時,認證的時候須要你輸入發送人信息。輸入data後又要寫一遍發件人的信息?難道server傻嗎,非要讓你輸入兩遍?能夠想一想為什麽。然後自己親自嘗試一下,因為咱們平時用的郵件代理都把這兩個覺得是同一個了,所以掩蓋了一個發送郵件時候的小技巧,即能夠偽裝欺騙。詳細的自己試一下。印象才會更深刻。

附錄5:

大寫和小寫問題。有沒有註意到我在cmd裏輸入的命令部分都是大寫,而在java程序裏輸出的命令部分都是小寫?我是想說,命令部分是不區分大寫和小寫的,可是命令後面的參數是嚴格區分大寫和小寫的,自己能夠試一下。(比如郵箱username和password本來就是區分大寫和小寫的吧)

附錄6:

關於telnet本身的問題。

在telnet裏的一行輸入了錯誤的數據。我想刪除,然後再繼續輸入正確的,回車。這個時候你會發現明明輸入的沒有問題,但是server返回的卻是錯誤代碼,比方 500 Error:bad syntax 原因就在於命令你要一次性輸入正確。假設中途輸錯了。不用退格,已經晚了,直接打個回車。肯定會報錯,再出入正確的就好了。假設總是打錯,像163server就直接給你斷開連接,它以為你惡意要攻擊它呢。像新浪的郵箱server,就非常忠誠,在有效連接時間內一直等著你輸入正確的命令,這點能夠自己試一下。


附錄7:

記得之前在linux中用shell發送郵件時,並沒有強制要求我必須用合法的身份登錄郵箱server才幹進行發郵件操作,為什麽?由於那個時候我自己的本機相當於是SMTPserver。我自己當然就不用驗證身份了。而如今是想用SMTP協議登錄別人的server(如163),此時163就必需要合法的用戶身份才幹使其登錄並發郵件了。


針對此篇文章還要說明的:這僅僅是為了理解SMTP的一篇非常基礎性的解說。對於代碼部分。為了體現主體,明顯缺少通過server返回代碼推斷語句,因而以上程序缺少健壯性。自己在理解好實際編程時應該考慮到真正的網絡請求情況,考慮丟包情況,依據server返回代碼進行對應推斷。

好了,就寫到這了,我要去和女神吃飯飯了~哪裏有問題能夠在以下留言~

個人github: http://github.com/icodeu

代碼托管地址:http://github.com/icodeu/JavaForSMTP

個人微信號:qqwanghuan 僅僅為技術交流

很多其它內容歡迎訪問個人站點 http://icodeyou.com


再談用java實現Smtp發送郵件之Socket編程