加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

java 使用SMTP协议发送邮件(亲测可行) 以及乱码处理

(2012-04-20 15:58:01)
标签:

java

使用

smtp协议

发送邮件

亲测可行

杂谈

分类: java

使用SMTP协议发送邮件,可以不通过SMTP服务器,直接将邮件发送到邮件服务器。很多服务器端程序可能需要向很多用户发送邮件,直接通过SMTP发送可能是最有效的。

关于SMTP协议定义在RFC821,可以在此看中文版

第一步:通过目标email查找邮件服务器。
例如:asklxf@sohu.com,其邮件服务器地址为:sohumx.sohu.com

import java.net.*;
import java.io.*;
import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;

public class Smtp {

    public static void main(String[] args) throws Exception {
        // DNS服务器,看看本机的DNS配置
        String dns = "dns://192.168.1.1";
        // 邮箱后缀:
        String domain = "sohu.com";
        Hashtable env = new Hashtable();
        env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
        env.put("java.naming.provider.url", dns);
        DirContext ctx = new InitialDirContext(env);
        Attributes attr = ctx.getAttributes(domain, new String[]{"MX" });
        NamingEnumeration servers = attr.getAll();
        // 列出所有邮件服务器:
        while(servers.hasMore()) {
            System.out.println(servers.next());
        }
    }
}

第二步:直接连接邮件服务器的25端口,用SMTP协议发送邮件。
这里使用sohu信箱,邮件服务器为sohumx.sohu.com,收信人必须在此服务器上:

import java.net.*;
import java.io.*;
import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;

public class Smtp {

    private static String END_FLAG = "\r\n";

    public static void main(String[] args) throws Exception {
        String mx = "sohumx.sohu.com";
        InetAddress addr = InetAddress.getByName(mx);
        Socket socket = new Socket(addr, 25);

        InputStream in = socket.getInputStream();
        OutputStream out = socket.getOutputStream();

        // 连接成功后服务器会响应:
        response(in);

        // 首先发送HELO命令:
        send("HELO 
www.javasprite.com" + END_FLAG, out);
        response(in);

        // 然后发送发件人地址:
        send("MAIL FROM: someone@somewhere.com" + END_FLAG, out);
        response(in);

        // 设置收件人地址:
        send("RCPT TO: asklxf@sohu.com" + END_FLAG, out);
        response(in);

        // 开始发送邮件正文:
        send("DATA" + END_FLAG, out);
        response(in);

        send("From: someone@somewhere.com" + END_FLAG, out);
        send("To: asklxf@sohu.com" + END_FLAG, out);
        send("Subject: Test without smtp server" + END_FLAG, out);
        send("Content-Type: text/plain;" + END_FLAG, out);
        send(END_FLAG + END_FLAG, out);

        // 发送邮件正文,如果用中文,需要BASE64编码:
        send("text message body!" + END_FLAG, out);
        // 每行以\r\n结束,不可过长,可拆成多行。

        // 以"\r\n.\r\n"作为结束标志:
        send(END_FLAG + "." + END_FLAG, out);
        response(in);

        // 结束并确认发送:
        send("QUIT" + END_FLAG, out);
        response(in);
        in.close();
        out.close();
        socket.close();
    }

    public static void response(InputStream in) throws Exception {
        byte[] buffer = new byte[1024];
        int n = in.read(buffer);
        String s = new String(buffer, 0, n);
        // 服务器会返回:### Text
        // 具体含义见RFC821
        System.out.println(s);
    }

    public static void send(String s, OutputStream out) throws Exception {
        byte[] buffer = s.getBytes();
        out.write(buffer);
        // 不要忘了flush(),否则可能在缓冲区:
        out.flush();
    }
}

Ok,打开outlook收信,会发现有一封来自someone@somewhere.com的信件。

第三步:处理服务器返回码,各种异常,包装成Java组件以便重用:

public interface SendMail {
    void send(String from, String to, String subject, String text)
}

public class SendMailImpl extends Thread implements SendMail {
    // TODO: 自己写......
}




1.目标SMTP服务器的参数

  目标SMTP服务器的参数包括目标SMTP服务器的地址,访问SMTP服务器是否需要提供安全信息以及访问SMTP服务器所需要的用户名和密码,其中目标SMTP服务器的地址是必须的。我们需要提供的参数和相关要求如表1所示

表1 SMTP服务器参数表

参数名 参数类型 参数说明 是否必须提供 说明
serverAddress String SMTP服务器的地址 比如SMTP服务的地址是mail.vivianj.org
authorizationRequired boolean 使用SMTP服务器发送邮件时是否需要提供安全信息 如果需要提供安全信息,这个参数设为true,否则设为false
principal String 访问SMTP邮件服务器时使用的用户名  
credentials String 访问SMTP邮件服务器时使用的密码  

  2.被发送邮件的参数

  要发送一个邮件,我们需要提供的参数和相关要求如表2所示。

表2 邮件参数表

参数名 参数类型 参数说明 是否必须提供 例子
from String 邮件发送者  
to String 邮件的接收者,可以使用xxx@xx.com,xxx1@xx.com,...的形式传递多个接收者 比如我们可以使用king@vivianj.org, guilaida@163.com 来设置邮件有两个接收者
cc String 邮件抄送的接收者,可以使用xxx@xx.com,xxx1@xx.com,...的形式传递多个抄送的接收者  
bcc String 邮件暗送的接收者,可以使用xxx@xx.com,xxx1@xx.com,...的形式传递多个暗送的接收者  
subject String 邮件的主题  
contentType String 被发送邮件的格式,默认使用text/plain,另外一种可选的类型是text/html,你还可以在这个参数中加入字符集的设置 比如我们可以使用”text/html;charset= GB2312”来设置被发送的邮件使用html格式,编码使用GB2312
attachment String 邮件附件  
Content Object 邮件内容  





  很多朋友都使用过javaMail进行邮件发送,在邮件正文中的乱码轻易解决。但邮件主题的乱码无论怎样转码总是显示一堆乱码。到底应该怎么处理呢? 
JavaMail中的邮件主题需要进行BASE64编码, 

格式形如: 
=?GB2312?B?XPq1xMPcwuvS0b6t1tjWw6Osx+u+ob/stcfCvKOssqLQ3rjEw9zC66Oh?= 

所以,直接使用msg.setSubject("中文主题"),或者msg.setSubject("中文主题".getBytes("8859_1"), "GB2312"))都一样会出现乱码。 

在设置邮件主题前需要将主题字串的字节编码为BASE64格式,并添加编码头,示例代码如下: 


try{ 
String subject = "中华全国helloworld"; 
String content = "测试邮件中语言问题.helloworld"; 
Context ctx = new InitialContext(); 
if(ctx == null) 
throw new Exception("NO Context()"); 
System.out.PRintln(ctx); 


javax.mail.session mailsession = (javax.mail.Session)ctx.lookup("java:comp/env/mail/session"); 
System.out.println(mailsession); 


Message msg = new MimeMessage(mailsession); 


InternetAddress[] toAddrs = InternetAddress.parse("david.li@maxcard.com",false); 
msg.setRecipients(Message.RecipientType.TO, toAddrs); 
//msg.setSubject("=?GB2312?B?"+Base64.encode(subject.getBytes())+"?="); 

//将中文转化为GB2312编码 
subject = StringUtil.getString(subject, "GB2312"); 
subject = new String(Base64.encode((subject).getBytes())); 
msg.setSubject("=?GB2312?B?" + subject + "?="); 

msg.setFrom(new InternetAddress("david.li@maxcard.com")); 
msg.setText(StringUtil.getString(content,"GB2312")); 
// 
Transport t = mailsession.getTransport("smtp"); 
t.send(msg); 
out.println("成功"); 
//Transport.send(msg); 
}catch(Exception e){ 
out.println("失败"); 
System.out.println(e); 



SMTP协议简介

  SMTP目前已是事实上的在Internet传输E-Mail的标准,是一个相对简单的基于文本的协议。在其之上指定了一条消息的一个或多个接收者(在大多数情况下被确定是存在的),然后消息文本就传输了。可以很简单地通过Telnet程序来测试一个SMTP服务器,SMTP使用TCP端口25。要为一个给定的域名决定一个SMTP服务器,需要使用MX(Mail eXchange)DNS

SMTP协议发展

  在20世纪80年代早期SMTP开始被广泛地使用。当时它只是作为UUCP的补充,UUCP更适合于处理在间歇连接的机器间传送邮件。相反SMTP在发送和接收的机器始终都联网的情况下工作得最好。
  SMTP独立于特定的传输子系统,且只需要可靠有序的数据流信道支持。SMTP重要特性之一是其能跨越网络传输邮件,即“SMTP邮件中继”。通常,一个网络可以由公用因特网上TCP可相互间访问的主机、防火墙分隔的TCP/IP网络上TCP可相互访问的主机,以及其他LAN/WAN中的主机利用非TCP传输层协议组成。使用SMTP,可实现相同网络上处理机之间的邮件传输,也可通过中继器或网关是实现某处理机与其他网络之间的邮件传输。

SMTP的作用:

  在smtp这种方式下,邮件的发送可能经过从发送端到接收端路径上的大量中间中继器或网关主机。域名服务系统( DNS)的邮件交换服务器可以用来识别出传输邮件的下一跳IP地址。
  Sendmail是最早实现SMTP的邮件传输代理之一。到2001年至少有50个程序将SMTP 实现为一个客户端(消息的发送者)或一个服务器(消息的接受者)。一些其他的流行的SMTP服务器包括Philip Hazel exim,IBMPostfix,D.J.BernsteinQmail,以及Microsoft Exchange Server.
  由于这个协议开始是基于纯ASCⅡ文本的,在二进制文件上处理得并不好。后来开发了用来编码二进制文件的标准,如MIME,以使其通过SMTP来传输。今天,大多数SMTP服务器都支持8位MIME扩展,它使二进制文件的传输变得几乎和纯文本一样简单。
  ( 注意:SMTP是一个"推"的协议,它不允许根据需要从远程服务器上“拉”来消息。要做到这点,邮件客户端必须使用POP3IMAP上。另一个,SMTP服务器可以使用ETRN(Extended Turn,扩展回车)命令在SMTP上触发一个发送。)

SMTP协议的局限:

  垃圾邮件仍然是个重要的问题。原始的SMTP协议的局限之一在于它没有为发送方进行认证的功能。因此定义了SMTP-AUTH 扩展。由于SMTP 巨大安装基础的网络效应,广阔地修改SMTP或者完全替代它被认为是不现实的。Internet Mail 2000 就是这样一个为替换而做的建议。IRTF 反垃圾邮件研究小组正在研究一些提供简单、灵活、轻量级的、可升级的源端认证的建议。最有可能被接受的建议是Sender Policy Framework 协议。

编辑本段SMTP协议命令及工作原理:

SMTP命令:

  SMTP命令是发送于SMTP主机之间的ASCⅡ信息,可能使用到的命令如下表所示。
  SMTP协议命令
  
命令 描述
DATA 开始信息写作
EXPN<string> 验证给定的邮箱列表是否存在,扩充邮箱列表,也常被禁用
HELO<domain> 向服务器标识用户身份,返回邮件服务器身份
HELP<command> 查询服务器支持什么命令,返回命令中的信息
MAIL FROM<host> 在主机上初始化一个邮件会话
NOOP 无操作,服务器应响应OK
QUIT 终止邮件会话
RCPT TO<user> 标识单个的邮件接收人;常在MAIL命令后面可有多个rcpt to:
RSET 重置会话,当前传输被取消
SAML FROM<host> 发送邮件到用户终端和邮箱
SEND FROM<host> 发送邮件到用户终端
SOML FROM<host> 发送邮件到用户终端或邮箱
TURN 接收端和发送端交换角色
VRFY<user> 用于验证指定的用户/邮箱是否存在;由于安全方面的原因,服务器常禁止此命令

SMTP协议工作原理:

  SMTP是工作在两种情况下:一是电子邮件从客户机传输到服务器:二是从某一个服务器传输到另一个服务器。SMTP也是个请求/响应协议,命令和响应都是基于ASCⅡ文本,并以CRLF符结束。响应包括一个表示返回状态的三位数字代码。SMTPTCP协议25号端口监听连续请求。

smtp连接和发送过程:

  (1)建立TCP 连接。
  (2)客户端发送HELO命令以标识发件人自己的身份,然后客户端发送MAIL命令;服务器端正希望以OK作为响应,表明准备接收。
  (3)客户端发送RCPT命令,以标识该电子邮件的计划接收人,可以有多个RCPT行;服务器端则表示是否愿意为收件人接收邮件。
  (4)协商结束,发送邮件,用命令DATA发送。
  (5)以“.”号表示结束输入内容一起发送出去,结束此次发送,用QUIT命令退出。
  SMTP协议的邮件路由过程及个人SMTP邮件服务器简单配置

SMTP协议的邮件路由过程:

  SMTP服务器基于域名服务DNS中计划收件人的域名来路由电子邮件。SMTP服务器基于DNS中的MX记录来路由电子邮件,MX记录注册了域名和相关的SMTP中继主机,属于该域的电子邮件都应向该主机发送。若SMTP服务器 mail.abc.com 收到一封信要发到shuer@sh.abc.com,则执行以下过程:
  (1)Sendmail 请求DNS给出主机sh.abc.com的CNAME 记录,如有,假若CNAME(别名记录)到shmail.abc.com,则再次请求shmail.abc.com的CNAME记录,直到没有为止。
  (2)假定被CNAME到shmail.abc.com,然后sendmail请求@abc.com 域的DNS给出shmail.abc.com的MX记录(邮件路由及记录),shmail MX 5 shmail.abc.com 10 shmail2.abc.com。
  (3)Sendmail组合请求DNS给出shmail.abc.com的A记录(主机名(或域名)对应的IP地址记录),即IP地址,若返回值为1.2.3.4(假设值)。
  (4)Sendmail与1.2.3.4连接,传送这封给shuser@sh.abc.com 的信到1.2.3.4 这台服务器的SMTP后台程序。

个人SMTP邮件服务器简单配置:

  一、安装POP3和SMTP服务组件
  Windows Server 2003默认情况下是没有安装POP3和SMTP服务组件的,因此我们要手工添加。
  1.安装POP3服务组件
  以系统管理员身份登录Windows Server 2003 系统。依次进入“控制面板→添加或删除程序→添加/删除Windows组件”,在弹出的“Windows组件向导”对话框中选中“电子邮件服务”选项,点击“详细信息”按钮,可以看到该选项包括两部分内容:POP3服务和POP3服务Web管理。为方便用户远程Web方式管理邮件服务器,建议选中“POP 3服务Web管理”。
  2.安装SMTP服务组件
  选中“应用程序服务器”选项,点击“详细信息”按钮,接着在“Internet信息服务(IIS)”选项中查看详细信息,选中“SMTP Service”选项,最后点击“确定”按钮。此外,如果用户需要对邮件服务器进行远程Web管理,一定要选中“万维网服务”中的“远程管理(HTML)”组件。完成以上设置后,点击“下一步”按钮,系统就开始安装配置POP3和SMTP服务了。
  二、配置POP3服务器
  1.创建邮件域
  点击“开始→管理工具→POP3服务”,弹出POP3服务控制台窗口。选中左栏中的POP3服务后,点击右栏中的“新域”,弹出“添加域”对话框,接着在“域名”栏中输入邮件服务器的域名,也就是邮件地址“@”后面的部分,如“MAIL.COM”,最 后点击“确定”按钮。
  2.创建用户邮箱
  选中刚才新建的“MAIL.COM”域,在右栏中点击“添加邮箱”,弹出添加邮箱对话框,在“邮箱名”栏中输入邮件用户名,然后设置用户密码,最后点击“确定”按钮,完成邮箱的创建。
  三、配置SMTP服务器
  完成POP3服务器的配置后,就可开始配置SMTP服务器了。点击“开始→程序→管理工具→Internet信息服务(IIS)管理器”,在“IIS管理器”窗口中右键点击“默认SMTP虚拟服务器”选项,在弹出的菜单中选中“属性”,进入“默认SM TP虚拟服务器”窗口,切换到“常规”标签页,在“IP地址”下拉列表框中选中邮件服务器的IP地址即可。点击“确定”按钮,此时SMTP服务器默认的是匿名访问,打开切换到“访问”标签页,点击“身份验证”按钮,在对话框中去掉“匿名访问“选项,选中”基本身份验证(Basic authentication)“。这样一个简单的邮件服务器就架设完成了。[1]

编辑本段SMTP服务扩展

smtp服务的意义:

  SMTP提供一种可靠的有效的传送机制,它用于传送电子邮件。虽然十几年来,它的作用已经有目共睹,可是对它功能的扩充也是必不可少的。对SMTP服务的扩展我们介绍一下:在SMTP转发的邮件中包括信封和内容这两种东西。我们写信也写信封和信皮,我们可以借生活中的信件来帮助理解。
  (1)SMTP信封比较容易理解,它被作为一系列的SMTP协议单元传送,它包括发送者地址,传送模式,还有一个或多个接收者地址。如果有不清楚的地方,请参阅《SMTP协议标准》。
  (2)至于内容,它是由两部分组成的,一部分是信头,一部分是信体,信头是由一个个的域/值对(一个域,一个值)组成的,如果信体有结构的话,它的结构是以MIME构造的。内容从根本上来说是文本的,一般也是由ASCII码构成的,但是由于使用了MIME,所以这个限制应该也是没有了,但信头却不行,一般都应该使用ASCII码表示。虽然SMTP协议是一个不错的协议,可是对它的扩展还是不可避免,本文主要说明了一种扩展方法,使用这种扩展方法,服务器和用户之间可以相互知道对方使用了扩展,使用了多少,如果进行通信。
  这里我们希望让大家知道网络协议中的一个经验:参数越多,死得越快;参数越少,越能持久。参数太多了,根本不利于使用,无法推广,早晚会被别的协议取代。这也符合科学的基础原理,简单。这说明在实现时一定要小心,如果不小心会便得到的远远小于付出的,有时根本不能提供任何益处。

EHLO命令:

  支持SMTP服务扩展的客户应该以EHLO命令开始SMTP会话,而不是通常的HELO命令。如果服务器也支持,那就返回确认响应,如果不支持就返回失败响应。因为引入了EHLO命令,因此会话开始的第一条命令可以是HELO或EHLO。
  因此引入了新的参数,所以SMTP的MAILFROM和RCPTTO命令行长度也能再是512字节了,但是引入新参数的长度必须加以说明,以便实现者准备缓冲区
  命令格式如下:
  ehlo-cmd::="EHLO"SPdomainCRLF
  在命令成功是,服务器返回代码250,如果失败返回代码550,如果出错,返500,501,502,504或421。对比《SMTP协议标准》,EHLO命令可以出现在任何HELO命令出现的地方,在成功发送一个HELO或EHLO命令后再次发送它会使服务器返回503。客户这时不能缓存服务器返回的任何信息。这里一定要注意的是,每次开始SMTP扩展服务会话的时候必须发送EHLO命令。如果服务器能够处理EHLO命令,它会返回代码250。这样,服务器和客户就处于初始状态了,也就是说,所有的状态表和缓冲区已经准备完毕。通常这种响应是多行的,每行响应包括一个关键字,如果有的话,还有一个或多个参数,响应的语法如下:
  ehlo-ok-rsp::="250"domain[SPgreeting]CRLF
  /("250-"domain[SPgreeting]CRLF
  *("250-"ehlo-lineCRLF)
  "250"SPehlo-lineCRLF)
  greeting::=1*<除了CR或LF的所有字符>
  ehlo-line::=ehlo-keyword*(SPehlo-param)
  ehlo-keyword::=(字母/数字)*(字母/数字/"-")
  ehlo-param::=1*<随了空格和控制字符外的字符>
  ALPHA::=<大写A到Z,小写A到Z>
  DIGIT::=<0到9>
  CR::=<回车,ASCII码13>
  LF::=<换行,ASCII码10>
  SP::=<空格,ASCII码32>
  虽然EHLO关键字可以是大写,小写,大小写混合的,但是对它的处理是大小写敏感的,这是与原来规定不同的。IANA支持SMTP服务扩展注册,相对于每个扩展都有一个相应的EHLO关键字值,每个在IANA注册的服务扩展必须在一个RFC中定义。如果一个关键字以X开头,它指的是这个服务扩展是双方约定的,不是标准的。
  如果出于某种原因,服务器不能列出它所支持的服务扩展,就返回代码554。在接收到这个代码后,客户要么发送HELO,要么发送QUIT命令。有时候服务器接收到EHLO命令,可是命令参数不可接受,它就返回代码501。如果服务器识别了EHLO,但对服务器扩展未实现,则返回代码502。
  如果服务器不再提供服务扩展,则返回代码421。在接收到这个代码后,客户要么发送HELO,要么发送QUIT命令。如果服务器不支持服务扩展,则返回500,服务器保持现有状态,在接收到这个代码后,客户要么发送HELO,要么发送QUIT命令。
  有时候,SMTP服务器会在接收到EHLO命令后因为某种原因关闭连接,这种情况在原来的SMTP协议标准中未涉及。为了处理这种情况,客户必须能够确认服务器是否能够工作,它可以重新连接并发送HELO或EHLO命令。有些服务器在接收到一个EHLO命令后会拒绝接收新的HELO命令,这时可以利用RSET命令重新启动,然后再发送HELO。如果客户不注意这样的小细节,会收到失败代码。
  下面我们来看一下MAILFROM和RCPTTO参数。许多服务扩展是在MAILFROM和RCPTTO命令后加入一些参数来实现的。下面我们看一下这两个命令的格式:
  esmtp-cmd::=inner-esmtp-cmd[SPesmtp-parameters]CRLF
  esmtp-parameters::=esmtp-parameter*(SPesmtp-parameter)
  esmtp-parameter::=esmtp-keyword["="esmtp-value]
  esmtp-keyword::=(字母/数字)*(字母/数字/"-")
  esmtp-value::=1*<除了空格,"="和控制字符的所有字符>
  inner-esmtp-cmd::=("MAILFROM:"返回路径)/("RCPTTO:"转发路径)
  如果服务器不能识别或实现一个或多个MAILFROM或RCPTTO参数,它应该返回代码555。如果这种情况只是暂时的,服务器返回代码455。其它返回代码请查阅相关资料,这里不再详述了。服务器以服务扩展处理时,它处理的任何信息都应该在包头上加上“服务扩展标记”以示区别。
  下面是一个例子:
  (1)双方交互:S是服务器,C是客户。
  S:<等待连接在TCP端口25>
  C:<连接到服务器>
  S:220dbc.mtview.ca.usSMTPserviceready
  C:EHLOymir.claremont.edu
  S:250dbc.mtview.ca.ussayshello
  ...
  (2)下面也是一个例子:
  S:<等待连接在TCP端口25>
  C:<连接到服务器>
  S:220dbc.mtview.ca.usSMTPserviceready
  C:EHLOymir.claremont.edu
  S:250-dbc.mtview.ca.ussayshello
  S:250-EXPN
  S:250-HELP
  S:250-8BITMIME
  S:250-XONE
  S:250XVRB
  ...
  这说明服务器实现了服务扩展EXPN和HELP,这两个是标准的服务扩展,另外两个以X开头的是非标准的。 
  (3)最后,我们来看看服务器不支持服务扩展时的情况:
  S:<等待连接在TCP端口25>
  C:<连接到服务器>
  S:220dbc.mtview.ca.usSMTPserviceready
  C:EHLOymir.claremont.edu
  S:500Commandnotrecognized:EHLO
  ...
  代码500表示服务器不支持服务扩展。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有