我们在上一篇中解决了接收一行命令的问题后,就可以来具体的分析邮件发送过程中涉及到的 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 语言来实现非常的简单:

    //解码一行命令,这里比较简单就是按空格进行分隔就行了
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;
}//

结合我们前面的例子就可以有:

        //解码一下,这样后面的 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倍都不止 -- 就是能有这么多)。

根据以上思想可以简单的写出一个版本的实现为:

//解码一行命令,这里比较简单就是按空格进行分隔就行了
//这是用可怕的指针运算的版本
void DecodeCmd(lstring * line, char sp, char ** cmds, int cmds_count)
{
int i = ;
int index = ;
int count = ; cmds[index] = line->str; for (i=; i<line->len; i++)
{
if (sp == line->str[i])
{
index++;
line->str[i] = '\0'; //直接修改为字符串结束符号,如果是只读的字符串这样做其实是不对的,不过效率很高 cmds[index] = line->str + i; //指针向后移动 if (i >= line->len - ) break;//如果是最后一定字符了就要退出,如果不是指针还要再移动一位
cmds[index] = line->str + i + ; count++;
if (count >= cmds_count) break; //不要大于缓冲区
} }// }//

调用前先要声明命令参数的缓冲区,如下:

    char * cmds[] = {NULL};
int cmds_count = ; 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[]); domain = NewString(cmds[], 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

--------------------------------------------------

版权声明:

本系列文章已授权百家号 "clq的程序员学前班" .

一步一步从原理跟我学邮件收取及发送 8.EHLO 命令详解的更多相关文章

  1. 一步一步从原理跟我学邮件收取及发送 3.telnet命令行发一封信

    首先要感谢博客园管理员的及时回复,本系列的第二篇文章得以恢复到首页,这是对作者的莫大鼓励.说实在的本来我真的挺受打击的.好在管理员说只是排版上有些问题,要用代码块修饰下相关的信息.说来惭愧因为常年编码 ...

  2. 一步一步从原理跟我学邮件收取及发送 12.telnet命令行收一封信pop3

    本系列上一篇文章中我们就说到了,这一次我们要说 pop3 收信了.虽然我觉得应该先说完 mime 格式,不过估计大家已经不耐烦了 -- 怎么老在说发送啊?我们要看收取!    好吧,来啦,来啦!收取邮 ...

  3. 一步一步从原理跟我学邮件收取及发送 2.邮箱的登录和绕不开的base64

    一步一步从原理跟我学邮件收取及发送 2.邮箱的登录和绕不开的base64 好了,经过本系列上一篇文章 "1.网络命令的发送",假设大家已经掌握了 email 电子邮件的命令发送的方 ...

  4. 一步一步从原理跟我学邮件收取及发送 4.不同平台下的socket

    既然是面向程序员的文章那当然不能只说说原理,一定要有实际动手的操作.    其实作为我个人的经历来说,对于网络编程,这是最重要的一章! 作为一位混迹业内近20年的快退休的程序员,我学习过很多的开发语言 ...

  5. 一步一步从原理跟我学邮件收取及发送 5.C语言的socket示例

    说到 C 语言版本的程序,首先要解决的问题就是兼容性. 作为 20 年开发有 10 多年是在服务端的程序员,我深刻地感受到服务端平台的两极分化之严重,linux 派对 windows 那是超级的不屑一 ...

  6. 一步一步从原理跟我学邮件收取及发送 10.四句代码说清base64

    经过前几篇的文章,大家应该都能预感到一定要讲解 base64 函数的内容了.是的,马上要到程序登录的代码,base64 是必须要实现的. base64 很早以前我就接触了,在项目中也很喜欢用.但每换一 ...

  7. 一步一步从原理跟我学邮件收取及发送 11.完整的发送示例与go语言

    经过了这个系列的前几篇文章的学习,现在要写出一个完整的 smtp 邮件发送过程简直易如反掌.    例如我们可以轻松地写出以下的纯 C 语言代码(引用的其他C语言文件请看文末的 github 地址): ...

  8. 一步一步从原理跟我学邮件收取及发送 9.多行结果与socket的阻塞

    前几篇的文章发表后,有网友留言说没有涉及到阻塞的问题吗?在 socket 的编程当中,这确实是个很重要的问题.结合目前我们文章的内容进度,我们来看看为什么说阻塞概念很重要. 接着上篇的内容,当我们发送 ...

  9. 一步一步从原理跟我学邮件收取及发送 13.mime格式与常见字符编码

    在前面的本系列文章中我们已经学会了邮件的发送和收取.但在收取中我们看到的是一串串的乱码,回忆前面的发送过程,我们会奇怪:我们前面的邮件是明文啊.为什么明文的邮件明明也可以正常工作,还要弄乱码似的字符串 ...

随机推荐

  1. Vue2.0 相对于Vue1.0的变化:生命周期

    1.生命周期 根据提供的生命周期钩子说明Vue.js实例各个阶段的情况,Vue.js 2.0对不少钩子进行了修改,以下说明:(灰色字体代表是 2.0已经废弃或者被更名的钩子,黑色字体代表1.0 和2. ...

  2. java基础,集合,Arraylist,源码解析(基础)

    ArrayList 是什么,定义? 这是动态的数组,它提供了动态的增加和减少元素,实现了List接口(List实现Collection,所以也实现Collection接口)灵活的设置数组的大小等好处 ...

  3. JavaScript for in的缺陷

    for in 语句用来列举对象的属性(成员),如下 1 2 3 4 5 6 7 var obj = { name:"jack",       getName:function(){ ...

  4. C/C++中peek函数的原理及应用

    C++中的peek函数 该调用形式为cin.peek() 其返回值是一个char型的字符,其返回值是指针指向的当前字符,但它只是观测,指针仍停留在当前位置,并不后移.如果要访问的字符是文件结束符,则函 ...

  5. 几道数位DP

    因为这几天写的几道数位DP大多都太水..而且也确实没什么好讲所以就扔到一起了. [hdu4772]Good Numbers 要求统计区间内 各位数之和能被10整除 的数的个数. 练手,f[i][j][ ...

  6. 2016 USP-ICMC-Codeforces-Gym101063C-Sleep Buddies Gym101063F-Bandejao Gym101063J-The Keys

    Gym101063C-Sleep Buddies It is nighttime in the Earth Colony on Mars and everyone is getting ready t ...

  7. Wolf and Rabbit

    http://acm.hdu.edu.cn/showproblem.php?pid=1222 Wolf and Rabbit Time Limit: 2000/1000 MS (Java/Others ...

  8. Win10没有以太网图标如何找回?以太网适配器不见了怎么恢复?

    Win10以太网适配器不见了怎么恢复?以太网其实就是Win7系统中常说的"本地连接"假若用户发现网络适配器中的以太网适配器图标不见了,可以在设备管理器中添加一些这类适配器,具体过程 ...

  9. C#的LINQ

    在过去如果我们如果需要去查询某些集合或者数组里面的某些元素,我们需要写出大量的带有筛选的遍历集合的代码,但是有了Linq之后,我们就不用写出那些冗余麻烦的遍历代码,只需要关注其中的筛选,排列的函数就可 ...

  10. MySQL的Illegal mix of collationsy异常原因和解决方法

    原创 2008年12月25日 11:54:00 标签: mysql / collation / character / variables / database / server   今天在使用数据库 ...