Email的曆史比Web還要久遠,直到現在,Email也是互連網上應用非常廣泛的服務。
幾乎所有的程式設計語言都支援發送和接收電子郵件,但是,先等等,在我們開始編寫代碼之前,有必要搞清楚電子郵件是如何在互連網上運作的。
我們來看看傳統郵件是如何運作的。假設你現在在北京,要給一個香港的朋友發一封信,怎麼做呢。
首先你得寫好信,裝進信封,寫上地址,貼上郵票,然後就近找個郵局,把信仍進去。
信件會從就近的小郵局轉運到大郵局,再從大郵局往別的城市發,比如先發到天津,再走海運到達香港,也可能走京九線到香港,但是你不用關心具體路線,你只需要知道一件事,就是信件走得很慢,至少要幾天時間。
信件到達香港的某個郵局,也不會直接送到朋友的家裡,因為郵局的叔叔是很聰明的,他怕你的朋友不在家,一趟一趟地白跑,所以,信件會投遞到你的朋友的郵箱裡,郵箱可能在公寓的一層,或者家門口,直到你的朋友回家的時候檢查郵箱,發現信件後,就可以取到郵件了。
電子郵件的流程基本上也是按上面的方式運作的,只不過速度不是按天算,而是按秒算。
現在我們回到電子郵件,假設我們自己的電子郵件地址是me@163.com,對方的電子郵件地址是friend@sina.com(注意地址都是虛構的哈),現在我們用Outlook或者Foxmail之類的軟體寫好郵件,填上對方的Email地址,點“發送”,電子郵件就發出去了。這些電子郵件軟體被稱為MUA:Mail User Agent——郵件使用者代理。
Email從MUA發出去,不是直接到達對方電腦,而是發到MTA:Mail Transfer Agent——郵件傳輸代理程式,就是那些Email服務提供者,比如網易、新浪等等。由於我們自己的電子郵件是163.com,所以,Email首先被投遞到網易提供的MTA,再由網易的MTA發到對方服務商,也就是新浪的MTA。這個過程中間可能還會經過別的MTA,但是我們不關心具體路線,我們只關心速度。
Email到達新浪的MTA後,由於對方使用的是@sina.com的郵箱,因此,新浪的MTA會把Email投遞到郵件的最終目的地MDA:Mail Delivery Agent——郵件投遞代理。Email到達MDA後,就靜靜地躺在新浪的某個伺服器上,存放在某個檔案或特殊的資料庫裡,我們將這個長期儲存郵件的地方稱之為電子郵箱。
同普通郵件類似,Email不會直接到達對方的電腦,因為對方電腦不一定開機,開機也不一定連網。對方要取到郵件,必須通過MUA從MDA上把郵件取到自己的電腦上。
所以,一封電子郵件的旅程就是:
寄件者 -> MUA -> MTA -> MTA -> 若干個MTA -> MDA <- MUA <- 收件者
有了上述基本概念,要編寫程式來發送和內送郵件,本質上就是: 編寫MUA把郵件發到MTA; 編寫MUA從MDA上收郵件。
發郵件時,MUA和MTA使用的協議就是SMTP:Simple Mail Transfer Protocol,後面的MTA到另一個MTA也是用SMTP協議。
收郵件時,MUA和MDA使用的協議有兩種:POP:Post Office Protocol,目前版本是3,俗稱POP3;IMAP:Internet Message Access Protocol,目前版本是4,優點是不但能取郵件,還可以直接操作MDA上儲存的郵件,比如從收件匣移到垃圾箱,等等。
郵件用戶端軟體在發郵件時,會讓你先配置SMTP伺服器,也就是你要發到哪個MTA上。假設你正在使用163的郵箱,你就不能直接發到新浪的MTA上,因為它只服務新浪的使用者,所以,你得填163提供的SMTP伺服器位址:smtp.163.com,為了證明你是163的使用者,SMTP伺服器還要求你填寫郵箱地址和郵箱口令,這樣,MUA才能正常地把Email通過SMTP協議發送到MTA。
類似的,從MDA收郵件時,MDA伺服器也要求驗證你的郵箱口令,確保不會有人冒充你收取你的郵件,所以,Outlook之類的郵件用戶端會要求你填寫POP3或IMAP伺服器位址、郵箱地址和口令,這樣,MUA才能順利地通過POP或IMAP協議從MDA取到郵件。
在收發郵件前,請先準備好至少兩個電子郵件,如xxx@163.com,xxx@sina.com,xxx@qq.com等,注意兩個郵箱不要用同一家郵件服務商。 1. 郵件協議 1.1 收發郵件
發郵件大家都會吧。發郵件是從用戶端把郵件發送到郵件伺服器,收郵件是把郵件伺服器的郵件下載到用戶端。
我們在163、126、QQ、sohu、sina等網站註冊的Email賬戶,其實就是在郵件伺服器中註冊的。這些網站都有自己的郵件伺服器。 1.2 郵件協議概述
與HTTP協議相同,收發郵件也是需要有傳輸協議的。 SMTP:(Simple Mail Transfer Protocol,簡易郵件傳輸通訊協定)發郵件協議; POP3:(Post Office Protocol Version 3,郵局協議第3版)收郵件協議; IMAP:(Internet Message Access Protocol,網際網路訊息訪問協議)收發郵件協議,我們的課程不涉及該協議。 1.3 理解郵件收發過程
其實你可以把郵件伺服器理解為郵局。如果你需要給朋友寄一封信,那麼你需要把信放到郵筒中,這樣你的信會“自動”到達郵局,郵局會把信郵到另一個省市的郵局中。然後這封信會被送到收信人的郵箱中。最終收信人需要自己經常查看郵箱是否有新的信件。
其實每個郵件伺服器都由SMTP伺服器和POP3伺服器構成,其中SMTP伺服器負責發郵件的請求,而POP3負責收郵件的請求。
當然,有時我們也會使用163的帳號,向126的帳號發送郵件。這時郵件是發送到126的郵件伺服器,而對於163的郵件伺服器是不會儲存這封郵件的。
1.4 郵件伺服器名稱
smtp伺服器的連接埠號碼為25,伺服器名稱為smtp.xxx.xxx。
pop3伺服器的連接埠號碼為110,伺服器名稱為pop3.xxx.xxx。
例如: 163:smtp.163.com和pop3.163.com; 126:smtp.126.com和pop3.126.com; qq:smtp.qq.com和pop3.qq.com; sohu:smtp.sohu.com和pop3.sohu.com; sina:smtp.sina.com和pop3.sina.com。 2. Telnet收發郵件
Telnet協議是TCP/IP協議族中的一員,是Internet遠程登陸服務的標準協議和主要方式。它為使用者提供了在本機電腦上完成遠程主機工作的能力。在終端使用者的電腦上使用telnet程式,用它串連到伺服器。終端使用者可以在telnet程式中輸入命令,這些命令會在伺服器上運行,就像直接在伺服器的控制台上輸入一樣。可以在本地就能控制伺服器。要開始一個telnet會話,必須輸入使用者名稱和密碼來登入伺服器。Telnet是常用的遠端控制Web伺服器的方法 2.1 BASE64加密
BASE64是一種密碼編譯演算法,這種加密方式是可逆的。它的作用是使加密後的文本無法用肉眼識別。Java提供了sun.misc.BASE64Encoder這個類,用來對做Base64的加密和解密,但我們知道,使用sun包下的東西會有警告。甚至在eclipse中根本使用不了這個類(需要設定),所以我們還是聽sun公司的話,不要去使用它內部使用的類,我們去使用apache commons組件中的codec包下的Base64這個類來完成BASE64加密和解密。
package cn.itcast;import org.apache.commons.codec.binary.Base64;public class Base64Utils { public static String encode(String s) { return encode(s, "utf-8"); } public static String decode(String s) { return decode(s, "utf-8"); } public static String encode(String s, String charset) { try { byte[] bytes = s.getBytes(charset); bytes = Base64.encodeBase64(bytes); return new String(bytes, charset); } catch (Exception e) { throw new RuntimeException(e); } } public static String decode(String s, String charset) { try { byte[] bytes = s.getBytes(charset); bytes = Base64.decodeBase64(bytes); return new String(bytes, charset); } catch (Exception e) { throw new RuntimeException(e); } }}
2.2 telnet發郵件
Xshell是一個強大的安全終端類比軟體,它支援SSH1, SSH2, 以及Microsoft Windows 平台的TELNET 協議。Xshell 通過互連網到遠程主機的安全連線以及它創新性的設計和特色協助使用者在複雜的網路環境中享受他們的工作
Xshell可以在Windows介面下用來訪問遠端不同系統下的伺服器,從而比較好的達到遠端控制終端的目的。
串連163的smtp伺服器
串連成功後需要如下步驟才能發送郵件:
(1)、與伺服器打招呼:ehlo你的名字
(2)、發出登入請求:auth login
(3)、輸入加密後的郵箱名:(itcast_cxf@163.com)aXRjYXN0X2N4ZkAxNjMuY29t
(4)、輸入加密後的郵箱密碼:(itcast)aXRjYXN0
(5)、輸入誰來發送郵件,即from:mail from:itcast_cxf@163.com
(6)、輸入把郵件發給誰,即to:rcpt to:itcast_cxf@126.com
(7)、發送填寫資料請求:data
(8)、開始輸入資料,資料包含:from、to、subject,以及郵件內容,如果輸入結束後,以一個“.”為一行,表示輸入結束:
from:<zhangBoZhi@163.com>to:<itcast_cxf@sina.com>subject: 我愛上你了我已經深深的愛上你了,我是張柏芝。.
注意,在標題和郵件內文之間要有一個空行。當要退出時,一定要以一個“.”為單行,表示輸入結束。
(9)、最後一步:quit
3. telnet收郵件 3.1 telnet收郵件的步驟
pop3無需使用Base64加密
收郵件已連線的服務器是pop3.xxx.com,pop3協議的預設連接埠號碼是110。請注意。這與發郵件完全不同。如果你在163有郵箱賬戶,那麼你想使用telnet收郵件,需要已連線的服務器是pop3.163.com
串連pop3伺服器:telnet pop3.163.com 110
| 命令 |
功能描述 |
| user命令 |
user 使用者名稱,例如:user itcast_cxf@163.com |
| pass命令 |
pass 密碼,例如:pass itcast |
| stat命令 |
stat命令用來查看郵箱中郵件的個數,所有郵件所佔的空間 |
| list命令 |
list命令用來查看所有郵件,或指定郵件的狀態,例如:list 1是查看第一封郵件的大小, list是查看郵件清單,即列出所有郵件的編號,及大小 |
| retr命令 |
查看指定郵件的內容,例如:retr 1#是查看第一封郵件的內容 |
| dele命令 |
標記某郵件為刪除,但不是馬上刪除,而是在退出時才會真正刪除 |
| quit命令 |
退出。如果在退出之前已經使用dele命令標記了某些郵件,那麼會在退出是刪除它們 |
4. JavaMail 4.1 JavaMail概述
Java Mail是由SUN公司提供的專門針對郵件的API,主要Jar包:mail.jar、activation.jar。
在使用MyEclipse建立web項目時,需要小心。如果只是在web項目中使用java mail是沒有什麼問題的,發布到Tomcat上運行一點問題都沒有。
但是如果是在web項目中寫測試那就出問題了。
在MyEclipse中,會自動給web項目匯入javax.mail包中的類,但是不全(其實是只有介面,而沒有介面的實作類別),所以只靠MyEclipse中的類是不能運行java mail項目的,但是如果這時你再去自行匯入mail.jar時,就會出現衝突。
處理方案:到下面路徑中找到javaee.jar檔案,把javax.mail刪除。。。
D:\Program Files\MyEclipse\Common\plugins\com.genuitec.eclipse.j2eedt.core_10.0.0.me201110301321\data\libraryset\EE_5
4.2 JavaMail中主要類
java mail中主要類:javax.mail.Session、javax.mail.internet.MimeMessage、javax.mail.Transport。
Session:表示會話,即用戶端與郵件伺服器之間的會話。想獲得會話需要給出賬戶和密碼,當然還要給出伺服器名稱。在郵件服務中的Session對象,就相當於串連資料庫時的Connection對象。
MimeMessage:表示郵件類,它是Message的子類。它包含郵件的主題(標題)、內容,收件者地址、寄件者地址,還可以設定抄送和暗送,甚至還可以設定附件。
Transport:用來發送郵件。它是發送器。 4.3 JavaMail之Hello World
在使用telnet發郵件時,還需要自己來處理Base64編碼的問題,但使用JavaMail就不必理會這些問題了,都由JavaMail來處理。
第一步:獲得Session
Session session = Session.getInstance(Properties prop, Authenticator auth);
其中prop需要指定兩個索引值,一個是指定伺服器主機名稱,另一個是指定是否需要認證。我們當然需要認證。
Properties prop = new Properties();prop.setProperty(“mail.host”, “smtp.163.com”);//設定伺服器主機名稱prop.setProperty(“mail.smtp.auth”, “true”);//設定需要認證
其中Authenticator是一個介面表示認證器,即校正用戶端的身份。我們需要自己來實現這個介面,實現這個介面需要使用賬戶和密碼。
Authenticator auth = new Authenticator() { public PasswordAuthentication getPasswordAuthentication () { new PasswordAuthentication(“itcast_cxf”, “itcast”);//使用者名稱和密碼 }};
通過上面的準備,現在可以擷取得Session對象了:
Session session = Session.getInstance(prop, auth);
第二步:建立MimeMessage對象
建立MimeMessage需要使用Session對象來建立:
MimeMessage msg = new MimeMessage(session);
然後需要設定發信人地址、收信人地址、主題,以及郵件內文。
msg.setFrom(new InternetAddress(“itcast_cxf@163.com”));//設定發信人msg.addRecipients(RecipientType.TO, “itcast_cxf@qq.com,itcast_cxf@sina.com”);//設定多個收信人msg.addRecipients(RecipientType.CC, “itcast_cxf@sohu.com,itcast_cxf@126.com”);//設定多個抄送msg.addRecipients(RecipientType.BCC, ”itcast_cxf@hotmail.com”);//設定暗送msg.setSubject(“這是一封測試郵件”);//設定主題(標題)msg.setContent(“當然是hello world!”, “text/plain;charset=utf-8”);//設定本文
第三步:發送郵件
Transport.send(msg);//發送郵件
4.4 JavaMail發送帶有附件的郵件
一封郵件可以包含本文、附件N個,所以本文與N個附件都是郵件的一個部份。
上面的hello world案例中,只是發送了帶有本文的郵件。所以在調用setContent()方法時直接設定了本文,如果想發送帶有附件郵件,那麼需要設定郵件的內容為MimeMultiPart。
MimeMulitpart parts = new MimeMulitpart();//多組件對象,可以理解為是組件的集合msg.setContent(parts);//設定郵件的內容為多組件內容。
然後我們需要把本文、N個附件建立為“主體組件”對象(MimeBodyPart),添加到MimeMuiltPart中即可。
MimeBodyPart part1 = new MimeBodyPart();//建立一個組件part1.setCotnent(“這是本文部分”, “text/html;charset=utf-8”);//給組件設定內容parts.addBodyPart(part1);//把組件添加到組件集中。
下面我們建立一個附件:
MimeBodyPart part2 = new MimeBodyPart();//建立一個組件part2.attachFile(“F:\\a.jpg”);//設定附件part2.setFileName(“hello.jpg”);//設定附件名稱parts.addBodyPart(part2);//把附件添加到組件集中
注意,如果在設定檔案名稱時,檔案名稱中包含了中文的話,那麼需要使用MimeUitlity類來給中文編碼:
part2.setFileName(MimeUitlity.encodeText(“美女.jpg”));
public class JavaMailDemo { public void sendMail() throws Exception { /* * 1. 得到session */ Properties props = new Properties(); props.setProperty("mail.host", "smtp.163.com"); props.setProperty("mail.smtp.auth", "true"); Authenticator auth = new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("itcast_cxf", "itcast"); } }; Session session = Session.getInstance(props, auth); /* * 2. 建立MimeMessage */ MimeMessage msg = new MimeMessage(session); msg.setFrom(new InternetAddress("itcast_cxf@163.com"));//設定寄件者 msg.setRecipients(RecipientType.TO, "itcast_cxf@126.com");//設定收件者 msg.setRecipients(RecipientType.CC, "itcast_cxf@sohu.com");//設定抄送 msg.setRecipients(RecipientType.BCC, "itcast_cxf@sina.com");//設定暗送 msg.setSubject("這是來自ITCAST的測試郵件"); msg.setContent("這就是一封垃圾郵件。", "text/html;charset=utf-8"); /* * 3. 發郵件 */ Transport.send(msg); } /** * 帶有附件的郵件。。。 */ public void sendMail2() throws Exception { /* * 1. 得到session */ Properties props = new Properties(); props.setProperty("mail.host", "smtp.163.com"); props.setProperty("mail.smtp.auth", "true"); Authenticator auth = new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("itcast_cxf", "itcast"); } }; Session session = Session.getInstance(props, auth); /* * 2. 建立MimeMessage */ MimeMessage msg = new MimeMessage(session); msg.setFrom(new InternetAddress("itcast_cxf@163.com"));//設定寄件者 msg.setRecipients(RecipientType.TO, "itcast_cxf@126.com");//設定收件者 msg.setSubject("這是來自ITCAST的測試郵件有附件"); //////////////////////////////////////////////////////// /* * 當發送包含附件的郵件時,郵件體就為多組件形式。 * 1. 建立一個多組件的組件內容。MimeMultipart * MimeMultipart就是一個集合,用來裝載多個主體組件。 * 2. 我們需要建立兩個主體組件,一個是常值內容的,另一個是附件的。 * 主體組件叫MimeBodyPart * 3. 把MimeMultipart設定給MimeMessage的內容。 */ MimeMultipart list = new MimeMultipart();//建立多部分內容 // 建立MimeBodyPart MimeBodyPart part1 = new MimeBodyPart(); // 設定主體組件的內容 part1.setContent("這是一封包含附件的垃圾郵件", "text/html;charset=utf-8"); // 把主體組件添加到集合中 list.addBodyPart(part1); // 建立MimeBodyPart MimeBodyPart part2 = new MimeBodyPart(); part2.attachFile(new File("F:/f/白冰.jpg"));//設定附件的內容 //設定顯示的檔案名稱,其中encodeText用來處理中文亂碼問題 part2.setFileName(MimeUtility.encodeText("大美女.jpg")); list.addBodyPart(part2); msg.setContent(list);//把它設定給郵件作為郵件的內容。 //////////////////////////////////////////////////////// /* * 3. 發郵件 */ Transport.send(msg); } public void sendMail3() throws Exception { /* * 1. 得到session */ Session session = MailUtils.createSession("smtp.163.com", "itcast_cxf", "itcast"); /* * 2. 建立郵件對象 */ Mail mail = new Mail("itcast_cxf@163.com", "itcast_cxf@126.com,itcast_cxf@sina.com", "不是垃圾郵件能是什麼呢。", "這裡是本文"); /* * 建立兩個附件對象 */ AttachBean ab1 = new AttachBean(new File("F:/f/白冰.jpg"), "小美女.jpg"); AttachBean ab2 = new AttachBean(new File("F:/f/big.jpg"), "我的羽絨服.jpg"); // 添加到mail中 mail.addAttach(ab1); mail.addAttach(ab2); /* * 3. 發送 */ MailUtils.send(session, mail); }}
5. MailUtils
public class MailUtils { public static Session createSession(String host, final String username, final String password) { Properties prop = new Properties(); prop.setProperty("mail.host", host);// 指定主機 prop.setProperty("mail.smtp.auth", "true");// 指定驗證為true // 建立驗證器 Authenticator auth = new Authenticator() { public PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } }; // 擷取session對象 return Session.getInstance(prop, auth); } /** * 發送指定的郵件 * * @param mail */ public static void send(Session session, final Mail mail) throws MessagingException, IOException { MimeMessage msg = new MimeMessage(session);// 建立郵件對象 msg.setFrom(new InternetAddress(mail.getFrom()));// 設定寄件者 msg.addRecipients(RecipientType.TO, mail.getToAddress());// 設定收件者 // 設定抄送 String cc = mail.getCcAddress(); if (!cc.isEmpty()) { msg.addRecipients(RecipientType.CC, cc); } // 設定暗送 String bcc = mail.getBccAddress(); if (!bcc.isEmpty()) { msg.addRecipients(RecipientType.BCC, bcc); } msg.setSubject(mail.getSubject());// 設定主題 MimeMultipart parts = new MimeMultipart();// 建立組件集對象 MimeBodyPart part = new MimeBodyPart();// 建立一個組件 part.setContent(mail.getContent(), "text/html;charset=utf-8");// 設定郵件常值內容 parts.addBodyPart(part);// 把組件添加到組件集中 /////////////////////////////////////////// // 添加附件 List<AttachBean> attachBeanList = mail.getAttachs();// 擷取所有附件 if (attachBeanList != null) { for (AttachBean attach : attachBeanList) { MimeBodyPart attachPart = new MimeBodyPart();// 建立一個組件 attachPart.attachFile(attach.getFile());// 設定附件檔案 attachPart.setFileName(MimeUtility.encodeText(attach .getFileName()));// 設定附件檔案名稱 parts.addBodyPart(attachPart); } } msg.setContent(parts);// 給郵件設定內容 Transport.send(msg);// 發郵件 }}