SMTP协议

256 阅读3分钟

本文正在参与 “网络协议必知必会”征文活动

SMTP是一个用来进行邮件交换的协议,它也被设计成了明文的形式。这也有一些它的考量,最初的时候,希望能够类似FTP协议,模拟一个类似交互式处理终端。可以一定程度提供灵活性。但是这让它设计成了只能传输可见的字符。

可见字符就有一个的编码:BASE64编码,稍后我们实现的客户端中也会有这个使用。这里就不具体给出实现了,已经是经典中的经典了。

如果更准确的来说的话,BASE64 属于 MIME 的一部分,这个名字一般也比较耳熟了,在现在的web开发中经常会用到这个 MIME 类型。它最初是设计给邮件使用的。后来逐渐推广到了其他的传输协议,但是仍然保留了 MIME 这个名字。不过现在貌似都叫他媒体类型。这个也是一个比较庞大的话题,不太好介绍,总之如果和现在的名字一样,如果需要更深一步的使用,那么就有相当的必要去进一步的了解它。

回到SMTP协议。它也是基于TCP协议的,所以首先得建立一个TCP连接,由于我们先前已经实现了简单的工具函数,所以这里也就比较简单了。

auto address = net::makeAddress("xxxx", xxxxx);
auto client = net::dialTCP(&address);

我用qq邮箱做的测试,它会需要我们自行去qq邮箱设置里申请开放smtp的使用。然后它会给你一个新的密码。这样就可以使用了。

当我们连接好qq邮箱之后,它会向我们发送一条信息。然后可以开始登录,也就是向服务器发送

AUTH LOGIN\r\n

然后服务器会发送一个状态码和base64编码的 username: 字符串,表示需要输入base64编码的用户名。同理密码也是base64编码。

具体的实现发邮件的过程这里可能有两种选择:比如实现一个发送指定邮件的程序,或者实现一个交互式发送邮件的程序。也不太好选。所以这里就简单实现下工具函数部分。 一个接受数据的一个发送数据的函数

void send_data(SOCKET client, array<char, 1024> &buf, size_t length) {
    auto r = send(client, buf.data(), (int) length, 0);
    if (r <= 0) {
        printf("error %d", r);
        exit(-3);
    }
}

void receive_data(SOCKET client) {
    static array<char, 1024> buf{};
    auto r = recv(client, buf.data(), buf.max_size(), 0);
    if (r <= 0) {
        printf("error receive %d", r);
        exit(-3);
    }
}

发送邮件具体命令只有简单的几条

MAIl FROM:<xxxxx@xx.com>\r\n //填写发件人
RCPT TO:<xxxx@xxxx.com>\r\n  //填写收件人
DATA\r\n //准备输入数据

因为它是请求回复模型,所以每一条数据在发送之后都会有服务器的回复。不太一样的是具体邮件正文部分的填写,它以 \r\n.\r\n. 表示结尾。

邮件的数据内容就和我们经常看到的邮件差不多,大概如下:

from:<xxxxxxxxxxxx>   
to:<xxxxxxxxxxx>     
subject: titile

this is the text

这里的格式其实不这样也可以成功发送,但是接受方会无法识别。

如果我们具体的实现的话,其实可以直接使用阻塞方式直接进行实现,如果要实现交互式命令行,那么需要做到解析用户指令,如果类似根据配置发送邮件,其实也差不多吧,具体在细节实现。

其实这里也能用两个线程来互相同时处理,而且有一个很神奇的地方在于,它不需要进行线程间通信的具体实现。但是你说它真不需要吗?也不是,主要是我们用户充当了一个线程间通信的媒介。我们人处理了这个部分。所以假设我们是实现聊天程序的客户端也会有这种类似的情况,尽管它从理论上需要,但是我们用户会作为实际上数据交换的部分。这点挺有意思的。