咱们在上一篇中解决了接收一行命令的问题后,就能够来具体的分析邮件发送过程当中涉及到的 SMTP 协议内容了。
首先来看通信过程当中的第一个内容:服务器在客户端链接上来后会主动发送一个问好的信息,因此这第一行的内容是服务器发送的,这时候客户端要回答的内容其实并不肯定。缘由是根据不一样的客户端意图,客户端要发送的内容是有些差别的。以咱们示例中要发送一封信来讲,要回复的第一句话就是 "EHLO" 命令,而看过咱们前面文章都知道,要链接 163 邮箱这样的服务器光有这个命令仍是不够的,还要带上 "163.com"。那么就有两个问题,一是谁规定要加这个和呢?二是若是是其实的邮箱应该加什么呢?
这个答案就要到 RFC 文档中去找了,smtp 的 rfc 文档为 RFC821 能够在如下网址中看到(内容仍是比较多的,其实你们能够先别急着看,咱们文章会讲解其中的内容)
http://man.chinaunix.net/develop/rfc/RFC821.txt
或者备用网址
http://newbt.net/ms/vdisk/show_bbs.php?id=8001DA8441F9AE9DC7AFF0709A875279&pid=160
其实若是你们真的仔细看了 rfc 的文档必定会以为奇怪,通篇文章都没有 "EHLO" 命令啊,这就涉及到 rfc 文档的一个特色了,那就是一种通信协议一般是记载在多个文档当中的,形成这种状况的缘由一是通信协议在发展,加入废弃了一些内容,而 rfc 写上去后通常就是不修改了的,通常是另外再写一个文档补充;另一种常见的就是某个通信协议是将以前的好几种组合在一块儿造成的,因此就确定要引用别的 rfc 文档了。回到 "EHLO" 命令上来,这个命令其实记述在另一个文档 RFC1869 当中,能够在如下网址中查看:
http://man.chinaunix.net/develop/rfc/RFC1869.txt
或者备用网址
http://newbt.net/ms/vdisk/show_bbs.php?id=9D31E50F0BEE16B48703FDF4234A332E&pid=160
我又要说你们先别急着查看,由于文档中的说明太复杂了,单就 "EHLO" 命令后面带的内容来讲,实际上是来自第一个文档的 "HELO" 命令的表述部分(HELO 就是英文 hello 的意思,而 EHLO 是扩展的 hello 的意思)。文档记载为 "HELO <SP> <domain> <CRLF>"。文档中的表述其实仍然有很强的误导性,命令中的空格是千万不要放到命令中的,那只是文档为了分隔语句而已,你会说命令中确实有空格啊!没错,看到那个 "<SP>" 没有,那个才表示的是空格 ... 若是有同窗一上来就看命令的必定会很郁闷<SP>究竟是什么意思,固然文档中是有解释的,只不过是在文档的最后,若是你老老实实的从头读到尾的话 ... 基本上都要破口大骂吧。总的来讲阅读 rfc 文档是件很辛苦的事,但有时候又很必要,并且以上的都是中文版本,实在上因为翻译和中英文差别的问题,有些细节操做时还得去查看英文原版的。
在命令中的 "<domain>" 是关键,它就是前述命令中的 "163.com",而每一个服务器的 domain 是不能乱写的,实际上要来自服务器风链接上的第一条响应命令行。例如 "220 newbt.net ESMTP eEmail-Server 2.0" 或者 "220 163.com Anti-spam GT for Coremail System (163com[20141201])",这个命令是由空格分隔的多个参数组成的,在实际的开发中实际上只须要按空格分隔字符串,而后取第二个参数就好了,这个就是 EHLO 命令后面要带上的东西。
代码实现:
知道了原理,其实用 java 语言来实现很是的简单:
php
//解码一行命令,这里比较简单就是按空格进行分隔就好了 public static String[] DecodeCmd(String line, String sp) { //String[] aa = "aaa|bbb|ccc".split("|"); String[] tmp = line.split(sp); //用空格分开//“.”和“|”都是转义字符,必须得加"\\";//不必定是空格也有多是其余的 String[] cmds = {"", "", "", "", ""}; //先定义多几个,以面后面使用时产生异常 for(int i=0;i<tmp.length;i++) { if (i >= cmds.length) break; cmds[i] = tmp[i]; } return cmds; }//
结合咱们前面的例子就能够有:
java
//解码一下,这样后面的 EHLO 才能有正确的第二个参数 String cmds[] = DecodeCmd(line, " "); String domain = cmds[1]; //要从对方的应答中取出域名//空格分开的各个命令参数中的第二个 //发送一个命令 //SendLine("EHLO"); //163 这样是不行的,必定要有 domain SendLine("EHLO" + " " + domain); //domain 要求其实来自 HELO 命令//HELO <SP> <domain> <CRLF>
完整代码你们能够手工加入以前文章的代码中去,也能够到 github 地址去下载:
https://github.com/clqsrc/c_lib_lstring
另外这系列文章的 java 示例我只放了一个源码,就不象 C 语言系列那样给出每一篇演变的代码了,由于 java 的源码相对比较简单,你们应该都看得懂。
就不用象 C 语言的那样分得那么清楚了。
C 语言要实现的话,有了前面的基础,要实现其实也不复杂。值得一提的是 C 语言的分隔字符串,要说的是 C 语言中分隔字符串时有种很常见的作法,就是利用 C 语言字符串的特色,直接在原字符串上打上字符串结束符号,这样的代码对于不少刚从学校 C 语言书本中走出来的初学者来讲是个巨大的挑战,可是由于这种方法没有从新分配内存,运行效率是很是的高(之后有机会我再给你们详细讲解程序优化中不从新分配内存能让程序效率提升到什么程度,能够提早说下,服务端大量链接的状况下提升100倍都不止 -- 就是能有这么多)。
根据以上思想能够简单的写出一个版本的实现为:git
//解码一行命令,这里比较简单就是按空格进行分隔就好了 //这是用可怕的指针运算的版本 void DecodeCmd(lstring * line, char sp, char ** cmds, int cmds_count) { int i = 0; int index = 0; int count = 0; cmds[index] = line->str; for (i=0; i<line->len; i++) { if (sp == line->str[i]) { index++; line->str[i] = '\0'; //直接修改成字符串结束符号,若是是只读的字符串这样作实际上是不对的,不过效率很高 cmds[index] = line->str + i; //指针向后移动 if (i >= line->len - 1) break;//若是是最后必定字符了就要退出,若是不是指针还要再移动一位 cmds[index] = line->str + i + 1; count++; if (count >= cmds_count) break; //不要大于缓冲区 } }// }//
调用前先要声明命令参数的缓冲区,以下:程序员
char * cmds[5] = {NULL}; int cmds_count = 5; rs = RecvLine(gSo, m, &buf); //只收取一行 printf("\r\nRecvLine:"); printf(rs->str); printf("\r\n"); DecodeCmd(rs, ' ', cmds, cmds_count); printf("\r\ndomain:%s\r\n", cmds[1]); domain = NewString(cmds[1], m); s = NewString("EHLO", m); LString_AppendConst(s," "); s->Append(s, domain); //去掉这一行试试,163 邮箱就会返回错误了 LString_AppendConst(s,"\r\n"); SendBuf(gSo, s->str, s->len);
完整代码就多了些,贴上来你们也难看清楚,能够到如下 github 地址下载或查看:
https://github.com/clqsrc/c_lib_lstring/tree/master/email_book/book_8
github
--------------------------------------------------数组
版权声明:服务器
本系列文章已受权百家号 "clq的程序员学前班" .dom