一步一步从原理跟我学邮件收取及发送 8.EHLO 命令详解

时间:2021-09-06 09:28:08

我们在上一篇中解决了接收一行命令的问题后,就可以来具体的分析邮件发送过程中涉及到的 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&period;telnet命令行发一封信

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. vector it-&gt&semi;和&ast;it

    //每次写代码总是被迭代器的iter->和*iter弄晕,主要是被protobuf弄晕了 #include <vector> struct test{ test(){ memset( ...

  2. vscode配置

    默认的挺难看的 颜色主题换成 Monokai Dimmed 用户设置 // 将设置放入此文件中以覆盖默认设置 { "editor.fontFamily": "Monaco ...

  3. Codeforces Round &num;184 &lpar;Div&period; 2&rpar; E&period; Playing with String(博弈)

    题目大意 两个人轮流在一个字符串上删掉一个字符,没有字符可删的人输掉游戏 删字符的规则如下: 1. 每次从一个字符串中选取一个字符,它是一个长度至少为 3 的奇回文串的中心 2. 删掉该字符,同时,他 ...

  4. Oracle中的日期

    --1.日期字符转换函数的用法 /****************************TO_CHAR********************************/ -------------- ...

  5. C&plus;&plus; char和string的区别

    'a'是char, "a"是char string,这两者都是普通的字符和字符串,和C中没什么不同 值得注意的是后者包含两个字符,末尾有一个隐身的'\0'而:string str ...

  6. Exec sql&sol;c

    Exec sql/c 利用高级语言的过程性结构来弥补SQL语言实现复杂应用方面的不足. 嵌入SQL的高级语言称为主语言或宿主语言. 在混合编程中,SQL语句负责操作数据库,高级语言语句负责控制程序流程 ...

  7. mysql5&period;6&period;24的安装与简单使用

    1, 下载绿色版Mysql5.6.24 http://dlsw.baidu.com/sw-search-sp/soft/ea/12585/mysql-5.6.24-win32.1432006610.z ...

  8. 关于css选择器中有小数点的标签获取

    需求说明 因为项目中章节配置的时候有小数点,1,1.1,1.2,1.11的标题,这个时候每一行标题的id,class设置成标题号是独一无二的标记.但是,直接用js获取是获取不到的,例如$('#3.22 ...

  9. 五种ip proxy的设置方法

    我们在制作爬虫爬取想要的资料时,由于是计算机自动抓取,强度大.速度快,通常会给网站服务器带来巨大压力,所以同一个IP反复爬取同一个网页,就很可能被封,在这里介绍相关的技巧,以免被封:但在制作爬虫时,还 ...

  10. 简单读!Mybatis源码(一)一条select的一生

    工具除了会用,还应该多做点.我觉得使用一个软件工具(开源类),一般会经历几个步骤: 1. 通过wiki了解大致作用,然后开始码代码: 2. 系统性地学习其特性,找出可能需要的点,用上去: 3. 通过阅 ...