huashi3483 发表于 2004-9-27 18:54

[转帖]JAVA 平台的MAIL实战精华

JAVA 平台的MAIL实战精华
本人承诺:以下内容为100%原创,绝对没有参考或引用<<any book>>的PAGE any中的nay line的any char

    JAVA平台事实上已经为我们提供了MAIL实现(JAVAMAIL API),但是,JAVAMAIL的实现实在不值一提
无论其易用性还是性能,都差强人意.SUN的开发小组成员他们只能是JAVA精英,但他们不是MAIL的行家,对我
而言,JAVAMAIL最多只能算是一个标准,一种接口,而SUN根本不应该自己去实现.

    一种技术,在任何平台上实现都是同样的技术,语言本身只是一种工具,而不应该让技术服从于语言.
但JAVAMAIL就把MAIL技术服从JAVA语言的层次,所以它已经不具有MAIL自己本来的性能优势.
    那么,本文就把MAIL技术还它本来面目,它不是JAVA的API,而是MAIL技术在JAVA平台上的实现.

    当然,本文不会教你如何从最底层来实现MAIL技术的各种协议,也不会自己实现SMTP和POP,IMAP等
服务程序----和JAVAMAIL在同一起跑线上,基于已有的服务程序来应用.纵观整个JAVA网络编程,90%是对应
用层编程,很少要我们自己用JAVA写服务的,那不是一两个人做的事.


    好了,言归正传.
    一.MTA部分的实现:
    MTA部份,说到底,我们不必关心一个MAIL实体是如何路由的,然后如何最终转发到目标服务器上,其间
要遵循哪些协议等问题,我们只关心,如何把一封信发出去?
   
    把一封信发出去,传统的做法是把个MAIL实体提交到一个SMTP的发送队列中,我们在JAVA平台上要做
的事也就是实现如何和SMTP服务打交道.当然如果你没有SMTP服务,也可以直接把一个MAIL实体直接发送到目标
地址的SMTP上,而且后一种更有效率.
   
    我们先来看一下如何把一个MAIL实体提交给本地的SMTP服务器:
    1.连结SMTP的25端口
    2.可选的认证
    3.提交信件来源
    4.提交目的地址
    5.提交MAIL实体
    6.断开连结

    在和一个SMTP服务听一次会话中,每个命令参数的规范请自己参看RFC822.命令参数没有太多的技术可
言.你只要在DOS命令行(或Bash Shell)上起一个telnet服务试一下就明白了所有过程:
    不要认证的过程:
     tlent mailhost 25
    < 220 xxx.xxx SMTP server ............
    > HELO
    < 250 xxx.xxx sourcehost(ip) okay
    > MAIL FROM: <aaa@aaa.com>
    < 250 <aaa@aaa.com>,sender ok
    > RCPT T <bbb@bbb.com>
    < 250 ok
    > DATA
    < 354 go ahead
    > sommessage
    > .
    < 250 ok
    > QUIT
    < 221 xxx.xxx
    < Connection closed by host.
    如果要求认证,只是发送的命令参数不同,把用户名和密码提交过去而已.这样我们只要建立一个socket,
就直接发送和服务器打交道的命令行,再也不要建立什么JAVAMAIL的会话对象,认证对象等一系列复杂的对象.

    下面的代码,我按整个实现过程顺序解释,为了照顾代码的完全性,把说明的内容和整个代码放在一起,从
    ---------begin-------开始到--------end--------结束中是一个完整的JAVA源程序中加上说明的

---------------------------------begin--------------------------------------
import java.net.*;
import java.io.*;
import java.util.*;
public class  SendMail
{
    private Socket sc;                //一个发送会话的SOCKET连结
    private int PORT = 25;            //SMTP端口
    private BufferedReader in;            //SOCKET的输入流,用于接收命令响应
    private PrintWriter out;            //SOCKET的输出流,用于发送命令
    private String smtpServer;            //SMTP主机
    private boolean htmlStyle = false;    //是否用HTML格式发送
    private boolean  authentication = false;    //服务器是否要求认证
    private String authorName = "guest";        //用于认证的默认用户名
    private String authorPasswd = "guest";        //用于认证的默认口令
    private String[] toArr;                //同时发送的目标地址数组
    private String[] ccArr;                //同时抄送的目标地址数组
    private String[] bccArr;            //同时暗送的目标地址数组
    private String from;                //发信人的地址
    private String charset = "gb2312";        //默认的字符编码
    private int priority = 3;            //优先级

    以下对上面的属性提供存取方法
    public void setSmtpServer(String smtpServer)
    {
        this.smtpServer = smtpServer;
    }   
    public void setHtmlStyle(boolean htmlStyle)
    {
        this.htmlStyle = htmlStyle;
    }
    public void setAuthentication(boolean  authentication)
    {
        this.authentication =  authentication;
    }
    public void setAuthorName(String authorName)
    {
        this.authorName = authorName;
    }
    public void setAuthorPasswd(String authorPasswd)
    {
        this.authorPasswd = authorPasswd;
    }
    public void setToArr(String[] toArr)
    {
        this.toArr = toArr;
    }
    public void setCcArr(String[] ccArr)
    {
        this.ccArr = ccArr;
    }
    public void setBccArr(String[] bccArr)
    {
        this.bccArr = bccArr;
    }
    public void setCharset(String charset)
    {
        this.charset = charset;
    }
    public void setFrom(String from)
    {
        this.from = from;
    }
    public void setPriority(int priority)
    {
        this.priority = priority;
    }

     开始建立SOCKET  ,同时初始化输入输出,如果是应用程序本方法的功能应用在构造方法中完成
     public boolean createConnect()            
     {
        if (smtpServer == null)
        {
            smtpServer = "localhost";
        }
        try
        {
            sc = new Socket(smtpServer,PORT);
            in = new BufferedReader(new InputStreamReader(sc.getInputStream()));
            out = new PrintWriter(sc.getOutputStream());
        }
        catch (IOException e)
        {
            return false;
        }
        return true;
    }

    为了方便调试,在一次会话中一个命令发送应该有一个响应,所以把一个命令发送和响应过程封装到一个
    方法中
    public String do_command(String s) throws IOException
    {
        if (s != null)
        {
            out.print(s);
            out.flush();
        }   
        String line;
        if ((line = in.readLine()) != null)
        {
            return line;
        }
        else
        {
            return "";
        }
    }

    在发送MAIL实体前,认证和非认证的服务器发送命令不同,所以把发送实体前的会话封装到本方法中
    注意本方法返回boolean类型是调试成功后封装的,为了在send方法中调用方便,但在具体调试时,本方法
    应用返回String类型,也就是每次把do_command("AUTH LOGIN\r\n").indexOf("334")赋给line并把line
    返回出来以便能在错误时知道返回的错误码


    public boolean sendHeader()
    {
        try
        {
            String line;
            do_command(null);
            if(authentication)
            {
    如果是服务器要求认证,可能是有两种加密方法,一是MD5,一是BASE64,目前很少用MD5认证的,所以本方法
    中用BASE64对明码用户名和口令编码, MailEncode.Base64Encode是MailEncode的静态方法,在以下的介绍
    中会提供相应的编码和加密方法源程序

                authorName =  MailEncode.Base64Encode(authorName);
                authorPasswd =  MailEncode.Base64Encode(authorPasswd);
                if (-1 == do_command("EHLO "+ smtpServer+"\r\n").indexOf("250"))
                    return false;   
                while(true)
                {
                    if(-1 != in.readLine().indexOf("250 "))
                        break;
                }
                if (-1 == do_command("AUTH LOGIN\r\n").indexOf("334"))
                    return false;               
                if (-1 == do_command(authorName+"\r\n").indexOf("334"))
                    return false;               
                if (-1 == do_command(authorPasswd+"\r\n").indexOf("235"))
                    return false;
            }
            else
            {
                if (-1 == do_command("HELO "+ smtpServer+"\r\n").indexOf("250"))
                    return false;  
            }
            
            if (-1 == (line = do_command("MAIL FROM: "+ from+"\r\n")).indexOf("250"))
                return false;
        对于目标地址,发送,抄送和暗送,在发送过程中没有任何区别.区别只是在MAIL实体中它们的位置而在
        SMTP会话中它们只以相同的RCPT TO命令发送,注意,有些服务器不允许一次连结发送给太多的地址.那么
        你应该限制toArr,ccArr,bccArr三个数组的总长度不超它们设定的最大值.当然如果你只有一个发送地址
        你就不必要在FOR回圈中处理,但本方法为了兼容同时发送给多人(而不是写在抄送中),用FOR回圈中来处理
        假你是一个目标地址,你应该生成一个元素的数组String[] toArr = {"aaa@aaa.com"};或者你可以重载本
        方法让to只是一个字符串

            if(toArr != null)
            {
                for(int i=0;i<toArr.length;i++)
                {
                    if (-1 == (line = do_command("RCPT T "+ toArr+"\r\n")).indexOf("250"))
                        return false;
                }
            }
            else
                return false;
       其实,从程序本身来说如果没有toArr只要有ccArr或bccArr还是可以发送的,但这样的信件没有目标地址却有抄送(暗送
       看不到)不合逻辑,在MAIL协议中一个重要原则是宽进严出,也就是我们接收别人的信格式可以放宽,他们发给我的只要符合
       协议我就应该接收和解析,而我发送出去的一定要非常严格地遵循标准,所以本处如果没有写发送就直接返回
            if(ccArr != null)
            {
                for(int i=0;i<ccArr.length;i++)
                {
                    if (-1 == (line = do_command("RCPT T "+ ccArr+"\r\n")).indexOf("250"))
                        return false;
                }
            }
            if(bccArr != null)
            {
                for(int i=0;i<bccArr.length;i++)
                {
                    if (-1 == (line = do_command("RCPT T "+ bccArr+"\r\n")).indexOf("250"))
                        return false;
                }
            }
            if (-1 == (line = do_command("DATA\r\n")).indexOf("354"))
                return false;
        }
        catch (IOException e)
        {
            return false;
        }
        return true;
    }


    在发送MAIL实体时,为了处理方便和性能的原因,我把有附件和没有附件的方法分开来
    BASE64是目前任何MUA都能处理的编码,本着宽进严出的原则我们严格使用BASE64编码

    public boolean send(String subject,String message)
    {
        subject = MailEncode.Base64Encode(subject);
        subject = "=?GB2312?B?"+subject + "?=";
        message = MailEncode.Base64Encode(message);
        try
        {
            String line;
            if(!sendHeader()) return false;
            message = "MIME-Version: 1.0\r\n\r\n"+message;
            message = "Content-Transfer-Encoding: base64\r\n"+message;
            if(htmlStyle)
                message = "Content-Type: text/html;charset=\""+charset+"\"\r\n"+message;
            else
                message = "Content-Type: text/plain;charset=\""+charset+"\"\r\n"+message;
               
            message = "Subject: "+subject+"\r\n"+message;

        这儿是发送和抄送的列表,它只是在信体中的标记不同,暗送不必写,在和SMTP会话中直接RCPT过去
            String target = "";
            String ctarget = "";
            for(int i=0;i< toArr.length;i++)
            {
                target += toArr;
                if(i < toArr.length-1)
                    target += ";";
            }
            if(ccArr != null)
            {
                for(int i=0;i<ccArr.length;i++)
                {
                    ctarget += ccArr;
                    if(i < ccArr.length-1)
                        ctarget += ";";
                }
            }
                                        //不能把bccArr加入
            message = "T "+target+"\r\n"+message;
            if(ctarget.length() !=0)
                message = "Cc: "+ctarget+"\r\n"+message;
            message = "From: "+from+"\r\n"+message;
            out.print(message+"\r\n");
            if (-1 == (line=do_command("\r\n.\r\n")).indexOf("250"))
                return false;
            in.close();
            out.close();
            sc.close();
        }
        catch (IOException e)
        {
            return false;
        }
        return true;
    }

    下面是对有附件的发送,因为信体中的文本和附件本要经过不同的处理,它们中间要加入各种分隔符和MIME类型,所以
    按顺序把每一行先放入ArrayList中,最后一次取出来发送,其中把附件编码成字符串分行的方法会在以下介绍上给出

    public boolean send(String subject,String message,String[] att)
    {

        subject = MailEncode.Base64Encode(subject);
        subject = "=?GB2312?B?"+subject + "?=";
        message = MailEncode.Base64Encode(message);
        String target="";
        String ctarget = "";
        for(int i=0;i< toArr.length;i++)
        {
            target += toArr;
            if(i < toArr.length-1)
                target += ";";
        }
        if(ccArr != null)
        {
            for(int i=0;i<ccArr.length;i++)
            {
                ctarget += ccArr;
                if(i < ccArr.length-1)
                    ctarget += ";";
            }
        }
        ArrayList al = new ArrayList();
        al.clear();
        al.add("Message-Id: "+System.currentTimeMillis());
        al.add("Date: "+new java.util.Date());
        al.add("X-Priority: "+priority);
        al.add("From: "+from);
        al.add("T "+target);
        if(ctarget.length() !=0)
            al.add("Cc: "+ctarget);
        al.add("Subject: "+subject);
        al.add("MIME-Version: 1.0");
        String s = "------=_NextPart_"+System.currentTimeMillis();
        al.add("Content-Type: multipart/mixed;boundary=\""+s+"\"");
        al.add("X-Mailer: Axman SendMail bate 1.0");
        al.add("");
        al.add("This is a MIME Encoded Message");
        al.add("");
        al.add("--"+s);
        if(htmlStyle)
            al.add("Content-Type: text/html; charset=\""+charset+"\"");
        else
            al.add("Content-Type: text/plain; charset=\""+charset+"\"");
        al.add("Content-Transfer-Encoding: base64");
        al.add("");
        al.add(message);
        al.add("");
        if(att != null)
        {
            for(int i=0;i<att.length;i++)
            {
                int kk = att.lastIndexOf("/");
                if(-i == kk) kk = att.lastIndexOf("\\");
                if(-1 == kk) kk = att.lastIndexOf("_");
                String name = att.substring(kk+1);
                al.add("--"+s);
                al.add("Content-Type: application/octet-stream; name=\""+name+"\"");
                al.add("Content-Transfer-Encoding: base64");
                al.add("Content-Disposition: attachment; filename=\""+name+"\"");
                al.add("");
                MailEncode.Base64EncodeFile(att,al);
                al.add("");
            }
        }
        al.add("--"+s+"--");
        al.add("");
        try
        {
            String line;
            if(!sendHeader())
                return false;
            for(int i =0;i< al.size();i++)
                out.print(al.get(i)+"\r\n");
            if (-1 == do_command("\r\n.\r\n").indexOf("250"))
                return false;
            in.close();
            out.close();
            sc.close();
        }
        catch (IOException e)
        {
            return false;
        }
        return true;
    }

    这个SAVE方法只是把要发的信件保存到本地文件中,其实应该重载一个不带附件的方法和send方法想对应,
    大家可以自己加入
    public void save(String subject,String message,String[] att,String path)
    {

        subject = MailEncode.Base64Encode(subject);
        subject = "=?GB2312?B?"+subject + "?=";
        message = MailEncode.Base64Encode(message);
        String target="";
        String ctarget = "";
        for(int i=0;i< toArr.length;i++)
        {
            target += toArr;
            if(i < toArr.length-1)
                target += ";";
        }
        if(ccArr != null)
        {
            for(int i=0;i<ccArr.length;i++)
            {
                ctarget += ccArr;
                if(i < ccArr.length-1)
                    ctarget += ";";
            }
        }
        ArrayList al = new ArrayList();
        al.clear();
        al.add("Message-Id: "+System.currentTimeMillis());
        al.add("Date: "+new java.util.Date());
        al.add("X-Priority: "+priority);
        al.add("From: "+from);
        al.add("T "+target);
        if(ctarget.length() !=0)
            al.add("Cc: "+ctarget);
        al.add("Subject: "+subject);
        al.add("MIME-Version: 1.0");
        String s = "------=_NextPart_"+System.currentTimeMillis();
        al.add("Content-Type: multipart/mixed;boundary=\""+s+"\"");
        al.add("X-Mailer: Axman SendMail bate 1.0");
        al.add("");
        al.add("This is a MIME Encoded Message");
        al.add("");
        al.add("--"+s);
        if(htmlStyle)
            al.add("Content-Type: text/html; charset=\""+charset+"\"");
        else
            al.add("Content-Type: text/plain; charset=\""+charset+"\"");
        al.add("Content-Transfer-Encoding: base64");
        al.add("");
        al.add(message);
        al.add("");
        if(att != null)
        {
            for(int i=0;i<att.length;i++)
            {
                int kk = att.lastIndexOf("/");
                if(-i == kk) kk = att.lastIndexOf("\\");
                if(-1 == kk) kk = att.lastIndexOf("_");
                String name = att.substring(kk+1);
                al.add("--"+s);
                al.add("Content-Type: application/octet-stream; name=\""+name+"\"");
                al.add("Content-Transfer-Encoding: base64");
                al.add("Content-Disposition: attachment; filename=\""+name+"\"");
                al.add("");
                MailEncode.Base64EncodeFile(att,al);
                al.add("");
            }
        }
        al.add("--"+s+"--");
        al.add("");
        try
        {
            PrintWriter pw = new PrintWriter(new FileWriter(path,true),true);
            for(int i=0;i<al.size();i++)
                pw.println((String)al.get(i));
            pw.close();
        }
        catch(IOException e){}
    }
    public static void main(String[] args)
    {
        SendMail sm = new SendMail();
        sm.setSmtpServer("10.0.0.1");
        if(sm.createConnect())
        {
            String[] to = {"axman@staff.coremsg.com"};
            String[] cc = {"stone@staff.coremsg.com"};
            String[] bcc = {"axman@staff.coremsg.com"};
            sm.setToArr(to);
            sm.setCcArr(cc);
            sm.setBccArr(bcc);
            sm.setFrom("axman@staff.coremsg.com");
            //sm.setAuthentication(true);
            //sm.setAuthorName("axman");
            //sm.setAuthorPasswd("11111");
            sm.setHtmlStyle(true);
            String subject = "中文测试!";
            String message = "大家好啊!";
            //String[] att = {"a.zip","b.zip"};
            System.out.print(sm.send(subject,message,null));
        }
        else
        {
            System.out.println("怎么连不上SMTP服务器啊?\r\n");
            return;
        }
    }
}


------------------------------------------- end -----------------------------------------

如果你自己有BASE64编码方法可以先替换我的程序中的方法,然后把发附件的SEND方法注释(里面没有把文件编码的方法)
你可以先用本代码发一封文本的MAIL看看,我现在来不急写那个方法的说明,所以不好直接把光秃秃的代码贴上来.

好了,今晚先写到这儿,代码中详细的解释周末再写.先把本代码读懂吧,不要急.下次会接着再介绍的.

xShandow 发表于 2004-10-22 20:57

看着很不错的样子....

喜悦 发表于 2012-2-7 12:53

{:3_46:}{:3_46:}{:3_46:}

xiaosu1z0r6 发表于 2012-2-28 17:13

気温も低い

今日は雨ですね...
気温も低いですが、雪ではなくてほっとしてます
今年度は雪はもう終わりましたかね
タイヤもそろそろ替えないと...
ではでは、最新入荷のファション商品を紹介します。
モンスター ヘッドフォンdr.dreが、米モンスターケーブル社と共同開発したヘッドフォン“studio”です。モンスター イヤホンシリーズ商品にインイヤー型のイヤフォン“tour”があります。ヒップホップに最大の威力を発揮しますが、ポップス、ロック、ジャズ、フロア系でも不満なく楽しめます。アップルストアに行くと試聴できます。
adidas サッカースパイクのf50アディゼロ。このアディダス スニーカー 新作は革新的な軽さと安定感を両立させた構造がハイスピードフットボールをサポート。1枚革スプリントスキンアッパーによる素足感覚の軽量性tpuボトムフレームにより足ブレの緩和。トライアングルスタッドを搭載、軽量かつ強靭なスプリントフレームアウトソールによるグリップ性と安定感を実現。素材:アッパー 合成皮革(スプリントスキン)。アウトソール 合成底(スプリントフレームアウトソール/14本アディトラクションアウトソール)。
ランニング ジョギング マラソンにオススメのadidas ランニング シューズ【マラソン 10 】になります。人気のランニングシューズがお買い得価格で登場,adidas originals!ランニングに対応する高い機能性はもちろん。カジュアルラインより提案されたシューズらしく、用途を限定せず気軽に履けるのも魅力。アディダス スポーツシューズ をベースにカジュアルカラーテイストで仕上げたシューズ。独立ヒールユニットが路面形状や斜度の変化に変形とズレ運動で対応し、安定姿勢を保つ機能「formotion」搭載。
アシックス トレーニングシューズは、従来のアイテムに見られるデザイン要素を盛り込みながら、細部にまでこだわったデザインのが特徴です。細身なシルエットや、カタカナのアシックス オニツカタイガーロゴがアクセントになっているアウトソールも特徴の一つです。アッパーエナメルとレザー、エナメルやスエードなどを効果的に配置された秀逸な1足です。アウトソールのカタカナロゴがアクセントできいてます!少し細めのデザインになっています。すごく人気となっています。お客様にも履いて頂きたいアイテム仕上がった今作を是非、お試し下さい。
25周年を迎え増々の盛り上がりを見せる名作バスケットボールシューズ「ナイキ エアフォース1」。今ではそのスタイリッシュなデザインからスポーツカルチャーはもちろんファッションカルチャーにも定番ナイキ スニーカーとして世界中で幅い広い支持を得ているモデルです。こちらはアッパーには上質なオイルドレザーを纏い、スウッシュにはパンチングを施した「hiking boots pack」。ワークブーツのような雰囲気を醸し出しつつ、スニーカーの履き心地を実現したプレミアムの名に恥じないコレクション。様々なシーンでの活躍を期待出来る、重宝すること間違い無しの1足です。

Paul_Sing 发表于 2012-3-1 11:26

很不错{:2_34:}
页: [1]
查看完整版本: [转帖]JAVA 平台的MAIL实战精华