用java開發Email工具之發送郵件

14 篇文章 0 订阅
  作者:馮睿

本文介紹了如何利用Java的網絡API來實現一個電子郵件工具程序。通常Email工具都是使用SMTP(簡單郵件傳輸協議, Simple Mail Transfer Protocol)來發送郵件,使用POP3協議來接受電子郵件。在本文中只對這兩個協議作簡單介紹。如果有興趣的讀者可以參考以下站點:

POP3: ftp://ftp.isi.edu/in-notes/rfc1939.txt

SMTP: ftp://ftp.isi.edu/in-notes/rfc2821.txt

Java中雖然提供了JavaMail API,但是由於在這篇文章中我將從底層來探討電子郵件軟件是如何工作的,因此不會使用JavaMail API。本文中的例子是在J2SE 1.4下開發的。


電子郵件的格式


在開發Email軟件之前,你需要瞭解電子郵件的格式。根據RFC 2882(http://www.faqs.org/rfcs/rfc2822.html)的規定,電子郵件由很多行組成,每行由〈CRLF〉(ASCII代碼13和ASCII代碼10)結束。每行的最大長度為998個字符。其中有些行提供了收發電子郵件所必需的信息,這些行被稱為頭(Header),所有的頭構成了頭域(Header Field)。其他的行用於保存郵件的具體內容。

頭域提供了很多信息,其中包括郵件的來源;郵件的目的地和郵件的主題等。每個頭由名稱和冒號加上相應的值構成。例如From:、Send:和 Reply-To:中記錄了郵件的來源。在From:中記錄的是郵件的作者;在Sender:中指定了發送郵件的代理(可以是郵件地址,也可以是機器名稱);Reply-To:中指定了接受回信的郵箱地址。

一封郵件可能有多個作者,因此From:中可以指定一個或多個郵箱地址。下面給出了一個個From:的例子:

From: Ray Feng 〈rayfeng@yahoo.com.cn〉, bogus@yahoo.com.cn



在一封電子郵件中只能有一個Sender。因此Sender:的值只能包含一個郵箱地址。如果在From:中只有一個作者,而且Sender:的值和From:的值相同,則Sender:就不會出現在電子郵件中,否則會出現信息冗余;反之Sender:則應該出現在郵件中。下面是一個 Sender:的例子:

Sender: Ray Feng rayfeng@yahoo.com.cn



在電子郵件中可以指定將回信發送到多個郵箱地址中。因此Reply-To:中可以包含一個或多個郵箱地址,每個地址之間用逗號隔開。如果郵件中有 Reply-To:,回信會被發送到羅列在Reply-To:中的所有地址;如果郵件中沒有Reply-To:,則回信會被發送到羅列在From:中的地址。那麼誰會收到郵件呢?To:和Cc:中保存了接受郵件的郵箱地址。兩者的值都可以包含多個郵箱地址。

除了郵件的來源和接受者,RFC 2882中還定義了其他一些頭,例如Subject:中包含了電子郵件的主題。下面是一個電子郵件頭域的例子:

From: Ray Feng 〈rayfeng@yahoo.com.cn〉
To: bogus 〈bogus@yahoo.com.cn〉
Cc: John 〈John@yahoo.com.cn〉
Subject: Test Email



附件

在MIME中允許在電子郵件中添加二進制文件,被添加的文件叫做附件。附件的內容可以作為郵件的一部分進行傳輸。MIME是如果實現這個功能的呢?在MIME中引入了很多頭,其中和附件相關的最重要的就是Content-Type:和Content-Tracnsfer-Encoding:。為了在一封電子郵件中區分不同的部分,MIME要求在Content-Type: multipart/mixed頭中包含一個邊界參數。邊界參數的值是一個在雙引號中的字符串。通過這個字符串,程序就可以區分電子郵件的不同部分。在傳輸電子郵件的內容前,程序先傳輸一個〈CRLF〉,兩個連字符和邊界參數。當完成Email內容的傳輸後,程序會在最後傳輸邊界參數和兩個連字符。

下面的電子郵件中包含了兩個部分,一個部分是由iso-8859-1字符組成的文本,一部分是名為file.txt的附件。這裡沒有包含Content-Transfer-Encoding:頭,表明使用缺省的7位ASCII字符。

Content-Type: multipart/mixed; boundary="***"
--***
Content-Type: text/plain; charset="iso-8859-1"
This message has an attachment.
--***
Content-Type: text/plain; name="file.txt"
Attachment text.
--***--




發送電子郵件






screen.width-333)this.width=screen.width-333;">

基於互聯網的電子郵件通常是利用SMTP網絡協議進行傳輸的。根據SMTP,當電子郵件程序需要發送電子郵件時,該程序首先同一個SMTP服務程序建立起雙向的通訊通道(通常是通過套接字建立這種通道的)。這個基本的SMTP服務程序或許是這份電子郵件的最終目的地,也可能只是通向另一個SMTP 服務程序的跳板。總而言之,當電子郵件程序同SMTP服務程序建立起雙相的傳輸通道後,電子郵件程序會向SMTP服務程序發送一系列基於ASCII字符的命令,而SMTP服務程序會對這些命令產生相應的回應來表明相應的操作是成功還是失敗了。

讓我們假設所有的操作都成功了,那麼電子郵件程序將把郵件發送到SMTP服務程序,如果電子郵件的接收地址正好是該SMTP服務程序運行的服務器,那麼SMTP服務程序就會將郵件加入郵件數據庫中,否則SMTP服務程序將把郵件轉發到在其他SMTP服務器上的SMTP服務程序,直到到達目的地為止。圖二通過圖示說明了這一點。





SMTP可以識別很多電子郵件用來與SMTP服務程序通訊的命令。某些命令需要參數,某些命令則不需要。但是每個命令後必須跟一個〈CRLF〉。最常用的六個命令是HELO,MAIL,RCPT,DATA,RSET和QUIT。

按照上面的順序給出這六個命令並非偶然。除了RSET外,其他的命令必須按照特定的順序發送,這是因為SMTP服務程序是基於狀態的。對於每一個建立了雙向通訊通道的電子郵件程序,SMTP服務程序都會保存當前的通訊狀態。

當一個電子郵件程序和SMTP服務程序建立聯繫後,SMTP服務程序將向電子郵件程序發送初始化消息。該消息包含了一個三位回應碼,這個回應碼是用來標識SMTP服務程序的。除此之外,在SMTP服務程序發送給電子郵件程序的消息的頭部也帶有回應碼,它們被用來表示操作成功或者失敗。電子郵件程序接收到這些回應碼後,可以根據其中包含的信息完成相應的工作。而消息的文本部分是給人看的,電子郵件程序可以忽略文本部分。

在收到初始化消息後,電子郵件程序通過發送HELO命令來開始傳輸郵件。HELO命令有一個參數,該參數標誌了SMTP服務程序所在服務器的域名。它將在SMTP服務程序中標識出SMTP服務程序。作為回應,SMTP服務程序進行一些初始化工作,將自己設定到初始狀態以接收電子郵件。當這些工作成功完成後,它發送回一條成功的回應消息給電子郵件程序,該回應消息以回應碼250開頭。

在HELO命令之後,電子郵件程序會發送MAIL命令。MAIL命令將在SMTP服務程序中標識出發送者,它有兩個參數:FROM:和一個電子郵件地址。如果SMTP服務程序能夠成功地解析電子郵件地址的話,通常它將返回以250開頭的回應消息;否者將發送回表示操作失敗的回應消息。

在MAIL之後是RCPT命令。RCPT命令在SMTP服務程序中標識出一個郵件的接收者,它也有兩個參數:TO:和一個電子郵件地址。如果郵件由多個接收者,則程序需要多次發送RCPT命令。

RCPT命令之後,程序需要發送電子郵件本身了。程序先發送一個DATA命令,當接收到表示成功的回應消息後,將電子郵件逐行發送給SMTP服務程序,當所有的行都發送完畢後,程序發送一行由句號組成的行。在此之後,電子郵件程序等待SMTP服務程序的回應消息,以確定郵件被SMTP服務程序正常接收了。這一切都成功後,程序可以發送RSET命令來退出郵件傳輸過程。最後,當要斷開和SMPT服務程序建立的連接時,程序發送QUIT命令。主要提醒的一點是,雖然上面的命令都是大寫的,但是在實際的協議對大小寫不敏感。
現在也許你關心的問題是回應碼的格式是怎樣的。最左邊的一位數字代表操作是否成功,1代表收到命令,2代表操作成功完成,3代表等待後續命令,4代表操作臨時未能完成(電子郵件程序可以在當前的郵件傳輸過程中重新發送命令),5代表操作不能完成(電子郵件程序不能在當前的郵件傳輸過程中重新發送命令)。第二位數字代表回應的領域,0代表語法錯誤,1代表消息請求,2代表傳輸通道,3和4沒有指定,5代表與郵件系統相關。最有一位數字對第二位數字做補充說明,這裡就不再詳述。根據上面的信息,我們可以看出250代表請求的命令已經成功完成;220代表SMTP服務程序正在等待HELO命令;而 503代表命令順序錯誤。有興趣的朋友可以參見RFC 2821。

下面提供了一個基於命令行的例子SMTPDemo,這個例子可以幫助你理解基於SMTP的郵件傳輸機制。這個程序將利用標準端口25連接到一個SMTP服務程序上。為了使程序能夠運行,你需要將home更改為你使用的郵件服務器的地址。

// SMTPDemo.java
import java.io.*;
import java.net.*;
class SMTPDemo
{
public static void main (String [] args)
{
String SMTPServer = "home
int SMTPPort = 25;
Socket client = null;
try
{
// 向SMTP服務程序建立一個套接字連接。
client = new Socket (SMTPServer, SMTPPort);
// 創建一個BufferedReader對象,以便從命令行讀取用戶輸入。
BufferedReader stdin;
stdin = new BufferedReader (new InputStreamReader (System.in));
// 創建一個BufferedReader對象,以便從套接字讀取輸出。
InputStream is = client.getInputStream ();
BufferedReader sockin;
sockin = new BufferedReader (new InputStreamReader (is));
// 創建一個PrintWriter對象,以便向套接字寫入內容。
OutputStream os = client.getOutputStream ();
PrintWriter sockout;
sockout = new PrintWriter (os, true);
// 顯示同SMTP服務程序的握手過程。
System.out.println ("S:" + sockin.readLine ());
while (true)
{
System.out.print ("C:");
// 讀取用戶輸入。
String cmd = stdin.readLine ();
// 將用戶輸入的命令發送到SMTP服務程序。
sockout.println (cmd);
// 從套接字讀取SMTP服務程序的回應消息並顯示在屏幕上。
String reply = sockin.readLine ();
System.out.println ("S:" + reply);
// 如果發送了DATA命令並且獲得成功的回應消息,從輸入設備讀取行,
// 直到讀取到完全由句號組成的行時停止, 這些行構成了電子郵件。
if (cmd.toLowerCase ().startsWith ("data") &&
reply.substring (0, 3).equals ("354"))
{
do
{
cmd = stdin.readLine ();
if (cmd != null && cmd.length () 〉 1 &&
cmd.charAt (0) == ´.´)
cmd = ".";
sockout.println (cmd);
if (cmd.equals ("."))
break;
}
while (true);
// 從SMTP服務程序中讀取回應消息並顯示。
reply = sockin.readLine ();
System.out.println ("S:" + reply);
continue;
}
// 如果用戶輸入QUIT命令,則退出程序。
if (cmd.toLowerCase ().startsWith ("quit"))
break;
}
}
catch (IOException e)
{
System.out.println (e.toString ());
}
finally
{
try
{
if (client != null)
client.close ();
}
catch (IOException e)
{
}
}
}
}



當運行SMTPDemo時,你將會看到下面的輸出。其中C:後面是用戶的輸入,S:後面是SMTP服務程序返回的信息。

S:220 home.digital.com Microsoft ESMTP MAIL Service, Version: 4.0.2195.2966 ready
at Fri, 13 Dec 2002 15:06:58 +0800



當運行SMTPDemo後,郵件服務程序返回了初始化信息。

C:helo digital.com
S:250 home.digital.com Hello [23.2.254.53]



通過發送helo digital.com命令開始郵件傳輸過程。digital.com是郵件服務器所在域的域名。然後郵件服務程序返回了以250開頭的歡迎信息。

C:mail from: rayfeng@digital.com
S:250 2.1.0 rayfeng@digital.com....Sender OK



接下來是輸入郵件發送者的信息mail from:。郵件服務程序返回了成功信息。

C:rcpt to: rayfeng@digital.com
S:250 2.1.5 rayfeng@digital.com



然後是通過rcpt to:指定郵件的接收者。

C:data
S:354 Start mail input; end with 〈CRLF〉.〈CRLF〉
Subject: Test Email
This is the test Email.
.
S:250 2.6.0 HOMEOulkEZ00VNuHKDy00000002@home.digital.com Queued mail for delivery



接下來是輸入郵件的內容。發送DATA命令後,等待服務器發送回命令被成功接收的回應消息。當接收到以354開頭的回應消息時,就可以輸入電子郵件的內容了。完成後以〈CRLF〉.〈CRLF〉結束。

C:quit
S:221 2.0.0 home.digital.com closing connection



最後退出發送電子郵件的過程。請注意回應碼221,最左邊的2代表操作成功,中間的2表示傳輸通道,1表示連接關閉。

前面我曾討論過關於附件的問題。通過SMTPDemo也可以發送附件。通過向郵件服務程序發送下面的命令,就可以在郵件中加入file.txt作為附件。

helo digital.net
mail from: rayfeng@digital.com
rcpt to: rayfeng@digital.com
data
Subject: Attachment Demo
Content-Type: multipart/mixed; boundary="***"
--***
Content-Type: text/plain; charset="iso-8859-1"
This message has an attachment.
--***
Content-Type: text/plain; name="file.txt"
Attachment text.
--***--
quit



到此為止,我們介紹了如何用Java實現Email工具的發送功能,並從地層分析了郵件發送的機制,不知您是否已經掌握了這些內容。在下一篇文章中,我們將一起來研究Email工具的接收功能。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* * JCatalog Project */ package com.hexiang.utils; import java.util.List; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.Properties; import javax.mail.Session; import javax.mail.Transport; import javax.mail.Message; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.hexiang.exception.CatalogException; /** * Utility class to send email. * * @author <a href="[email protected]">hexiang</a> */ public class EmailUtil { //the logger for this class private static Log logger = LogFactory.getLog("com.hexiang.util.EmailUtil"); /** * Send email to a single recipient. * * @param smtpHost the SMTP email server address * @param senderAddress the sender email address * @param senderName the sender name * @param receiverAddress the recipient email address * @param sub the subject of the email * @param msg the message content of the email */ public static void sendEmail(String smtpHost, String senderAddress, String senderName, String receiverAddress, String sub, String msg) throws CatalogException { List<String> recipients = new ArrayList<String>(); recipients.add(receiverAddress); sendEmail(smtpHost, senderAddress, senderName, recipients, sub, msg); } /** * Send email to a list of recipients. * * @param smtpHost the SMTP email server address * @param senderAddress the sender email address * @param senderName the sender name * @param recipients a list of receipients email addresses * @param sub the subject of the email * @param msg the message content of the email */ public static void sendEmail(String smtpHost, String senderAddress, String senderName, List<String> recipients, String sub, String msg) throws CatalogException { if (smtpHost == null) { String errMsg = "Could not send email: smtp host address is null"; logger.error(errMsg); throw new CatalogException(errMsg); } try { Properties props = System.getProperties(); props.put("mail.smtp.host", smtpHost); Session session = Session.getDefaultInstance(props, null ); MimeMessage message = new MimeMessage( session ); message.addHeader("Content-type", "text/plain"); message.setSubject(sub); message.setFrom(new InternetAddress(senderAddress, senderName)); for (Iterator<String> it = recipients.iterator(); it.hasNext();) { String email = (String)it.next(); message.addRecipients(Message.RecipientType.TO, email); } message.setText(msg); message.setSentDate( new Date() ); Transport.send(message); } catch (Exception e) { String errorMsg = "Could not send email"; logger.error(errorMsg, e); throw new CatalogException("errorMsg", e); } } }
Java Paypal IPN开发可以参照以下步骤进行: 1. 在Paypal账户中设置IPN(Instant Payment Notification),并将IPN URL设置为您的服务器上的Java Servlet。 2. 在Java代码中实现一个Servlet来接收Paypal发送的IPN信息,并解析该信息。 3. 验证Paypal发送的IPN信息的有效性。可以使用Paypal提供的IPN验证工具或者手动验证。 4. 对接收到的IPN信息进行处理,例如更新订单状态或发送电子邮件通知等。 以下是Java Paypal IPN开发的示例代码: ```java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class IPNServlet extends HttpServlet { private static final String PAYPAL_URL = "https://www.paypal.com/cgi-bin/webscr"; private static final String PAYPAL_SANDBOX_URL = "https://www.sandbox.paypal.com/cgi-bin/webscr"; private static final String VERIFY_CMD = "_notify-validate"; private static final String CHARSET = "UTF-8"; private static final String TXN_TYPE = "txn_type"; private static final String ITEM_NUMBER = "item_number"; private static final String PAYMENT_STATUS = "payment_status"; private static final String MC_GROSS = "mc_gross"; private static final String MC_CURRENCY = "mc_currency"; private static final String RECEIVER_EMAIL = "receiver_email"; private static final String BUSINESS = "business"; private static final String CUSTOM = "custom"; private static final String PAYPAL_EMAIL = "YOUR_PAYPAL_EMAIL"; private static final String PAYPAL_IDENTITY_TOKEN = "YOUR_PAYPAL_IDENTITY_TOKEN"; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String url = PAYPAL_URL; if (Boolean.parseBoolean(req.getParameter("test_ipn"))) { url = PAYPAL_SANDBOX_URL; } Map<String, String> params = new HashMap<String, String>(); BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream())); String line = null; while ((line = reader.readLine()) != null) { params.putAll(parseQueryString(line)); } reader.close(); String txnType = params.get(TXN_TYPE); if (txnType == null) { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); return; } String itemNumber = params.get(ITEM_NUMBER); String paymentStatus = params.get(PAYMENT_STATUS); String mcGross = params.get(MC_GROSS); String mcCurrency = params.get(MC_CURRENCY); String receiverEmail = params.get(RECEIVER_EMAIL); String business = params.get(BUSINESS); String custom = params.get(CUSTOM); if (!PAYPAL_EMAIL.equals(receiverEmail) || !PAYPAL_IDENTITY_TOKEN.equals(custom)) { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); return; } String verifyUrl = url + "?" + VERIFY_CMD; for (String name : params.keySet()) { verifyUrl += "&" + name + "=" + URLEncoder.encode(params.get(name), CHARSET); } if (!verify(verifyUrl)) { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); return; } // Handle IPN message } private boolean verify(String url) throws IOException { HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setDoInput(true); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("User-Agent", "Java IPN Verification"); conn.setRequestProperty("Host", "www.paypal.com"); conn.setRequestProperty("Connection", "close"); conn.getOutputStream().write(("cmd=" + VERIFY_CMD).getBytes()); BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null; while ((line = reader.readLine()) != null) { if ("VERIFIED".equals(line)) { return true; } else if ("INVALID".equals(line)) { return false; } } reader.close(); return false; } private Map<String, String> parseQueryString(String queryString) throws UnsupportedEncodingException { Map<String, String> params = new HashMap<String, String>(); for (String param : queryString.split("&")) { String pair[] = param.split("="); String name = URLDecoder.decode(pair[0], CHARSET); String value = ""; if (pair.length > 1) { value = URLDecoder.decode(pair[1], CHARSET); } params.put(name, value); } return params; } private String md5(String string) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); byte[] digest = md5.digest(string.getBytes()); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b & 0xff)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } } ``` 以上代码中,IPNServlet类是一个Java Servlet,用于接收Paypal发送的IPN信息,验证IPN信息的有效性并进行处理。在该类中,PAYPAL_EMAIL和PAYPAL_IDENTITY_TOKEN分别为您Paypal账户的电子邮件地址和身份令牌,用于验证IPN信息是否来自Paypal。在实际的应用中,您需要替换这些值为您自己的Paypal账户信息。 在handle IPN message方法中,您可以根据接收到的IPN信息进行相应的处理,例如更新订单状态或发送电子邮件通知等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值