我们在上一篇中解决了接收一行命令的问题后,就可以来具体的分析邮件发送过程中涉及到的 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. Django--进阶--中间件的使用

    - 中间件是什么? 中间件是一个钩子框架,它们可以介入Django 的请求和响应处理过程.它是一个轻量级.底层的“插件”系统,用于在全局修改Django 的输入或输出. 每个中间件组件负责完成某个特定 ...

  2. Spring Boot快速入门(二):http请求

    原文地址:https://lierabbit.cn/articles/4 一.准备 postman:一个接口测试工具 创建一个新工程 选择web 不会的请看Spring Boot快速入门(一):Hel ...

  3. cyclictest 简介

    1. cyclictest 简介以及安装 1.1 cyclictest 简介 cyclictest 是什么? 看名字应该就能大致猜出来它是一种 test 程序,Cyclictest的维基主页这么介绍它 ...

  4. 基于FPGA驱动VGA显示图片的小问题

    学习VGA显示图片的过程中,遇到了一个小问题,我在显示屏上开了一个60x60的框,放了一张图片进去显示,但是最终的结果如下图所示. 出现了一个竖黑边,看了看代码,分析了一下逻辑没问题,然而看这个显示那 ...

  5. JavaScript基础知识(概念、常量和变量)

    1.JavaScript概念 JavaScript是脚本语言; 编写之后,可以直接运行(缺失了编译的过程) 2.JavaScript发展 LiveScript    =>    JavaScri ...

  6. 常用SQL语句集合

    一.数据定义 1.创建新数据库:CREATE DATABASE database_name2.创建新表:CREATE TABLE table_name (column_name datatype,co ...

  7. Inception服务的安装以及使用Python 3 实现MySQL的审计

    Inception服务的安装以及使用Python实现MySQL的审计 Bison是Inception服务所依赖的包之一,但是某些Linux版本已安装的Bison,或者是通过yum安装的Bison,通常 ...

  8. JS原型、原型链深入理解

    原型是JavaScript中一个比较难理解的概念,原型相关的属性也比较多,对象有”prototype”属性,函数对象有”prototype”属性,原型对象有”constructor”属性. 一.初识原 ...

  9. (转)Docker之Compose服务编排

    转自:https://www.cnblogs.com/52fhy/p/5991344.html Compose是Docker的服务编排工具,主要用来构建基于Docker的复杂应用,Compose 通过 ...

  10. ZOj 3466 The Hive II

    There is a hive in the village. Like this. There are 8 columns(from A to H) in this hive. Different ...