您的位置:首页 > 编程语言 > Java开发

再谈用java实现Smtp发送邮件之Socket编程

2017-05-18 18:07 726 查看

很多其它内容欢迎訪问个人站点 http://icodeyou.com

前几天利用Socket实现了用java语言搭建webserver,全程下来应该会对Socket这个东西已经使用的很熟悉了。尽管抽象,可是使用过一次之后就会感受到它在网络通信上的作用是多么的强大。正好,今天就继续用Socket来练习使用下面Smtp协议发送一封简单的电子邮件。今天的故事呢,是我要约我女神出去吃饭啦啦啦~~~所以,面对Smtp。仅仅许成功,不许失败。

全局假定我的邮箱为cnsmtp01@163.com 女神的邮箱为cnsmtp02@163.com 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:<cnsmtp01@163.com > 这时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:<cnsmtp01@163.com
> 回车 RCPT TO:<cnsmtp02@163.com >
回车 发件人和收件人设置好后。自然该告诉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:cnsmtp01@163.com 回车 to:cnsmtp02@163.com 回车 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:<cnsmtp01@163.com>
S: 250 Mail OK
C: RCPT TO:<cnsmtp02@163.com>
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:cnsmtp01@163.com
C: to:cnsmtp02@163.com
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 = "cnsmtp01@163.com";
String receiver = "cnsmtp02@163.com";
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 = "cnsmtp01@163.com";
String receiver = "cnsmtp02@163.com";
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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: