Socket实验(三)

smtp协议邮箱客户端

Posted by 婷 on May 31, 2021 本文总阅读量

实验内容

利用SMTP协议来发送电子邮件,连接的端口采用非SSL端口,端口号为25。这次的实验完成发送邮件文本内容以及附件(图片以及文件)的任务。后面的实验再来增加SSL协议。本质上就是实现一个邮件客户端。

实验过程

基本思路

创建客户端套接字–>与收件人的SMTP服务器进行连接–>发送HELO命令–>发送AUTH LOGIN命令–>发送发件人的邮箱账号的base64编码–>发送发件人的邮箱SMTP授权码的base64编码–>发送MAIL FROM命令–>发送RCPT TO命令–> 发送DATA命令–>发送SMTP消息格式的头部行 –>发送SMTP的消息内容(也即邮件内容)–>发送正文结束命令\r\n.\r\n –>发送QUIT命令–>关闭套接字

消息格式

SMTP中Email的消息格式由头部行与消息体两部分组成,具体是这么规定的。头部行与消息行中间要隔出来一行(\r\n)。

  • 头部行
    • To
    • From
    • Subject
  • 消息体
    • 消息本身

2mdFj1.png

如果发送内容中有包含其他音频图片之类的其他多媒体文件,就需要在邮件头部增加额外的行以声明MIME的内容类型,具体实现在代码中也有体现。

点击查看MIME头部信息

    string title ="From: " + from + "\r\nTo: " + to + "\r\nSubject: " + subject +"\r\n"; 
    string mime_header1 = "MIME-Version: 1.0\r\n";
    string mime_header3 = "Content-Type: multipart/mixed;boundary=@boundary@\r\n\r\n";
    string total = title + mime_header1  + mime_header3;
    send(sockfd,total.c_str(),strlen(total.c_str()),0);
    

准备工作

  • 获得邮箱授权码

    具体获得授权码的过程可以谷歌查询,不过要注意的有三点

    • 要开启授权码的邮箱是发件人的邮箱,不是收件人的邮箱(如果是收件人的邮箱那我还发个*)
    • 授权码只会显示一次,记得拿纸笔或拍照记录
    • 授权码要保密,别让不法分子利用了
  • base64编码

    因为发送用户名,授权码以及邮件内容的时候会用到,有两种方法,一种是直接在线编码,另一种则是自己在代码里面加上base64编码的函数。我的用的是第二种方法。

代码

详细的代码见notssl.cpp,对于每个函数我都有写注释。重点看main函数。

int main(void)
{
    string sender_addr = "发件人邮箱@qq.com"; //发件人邮箱

    string receiver_addr = "收件人邮箱@163.com"; //收件人邮箱

    string subject = "发送jpg png txt docx等文件";//邮件的主题

    string authrization_code = "原始授权码";//发件人的邮箱的授权码

    string story="没有使用ssl的邮箱客户端\r\n";//邮箱正文内容

    smtp example("smtp.qq.com",25,32);//发件人的邮箱smtp服务器

    example.start(sender_addr,receiver_addr,subject,story,authrization_code);

    example.attachment(5,"readme.txt","photo.png","photo.jpg","测试文档.docx","hello.cpp");

    example.closesocket();

    return 0;
}
  • 只要填写好发件人邮箱,收件人邮箱,邮件主题,发件人邮箱的授权码,就可以基本实现想要的功能。附件的发送参照attachment这个函数的注释说明即可。

  • 需要注意的是,声明一个smtp对象的时候,你的主机名字要对应你的发件人的smtp邮箱服务器。
  • 其中的authrization_code要填写发件人邮箱的授权码,这里为了隐私我就不把我的授权码挂上去了。

实验结果

终端编译,然后执行可执行文件

2mdVHK.md.png

邮箱中收到的消息

2mdAnx.md.png

点开邮件

2mdeAO.md.png

参考链接

其他细节

  • POP3 IMAP的端口与SMTP的端口也不一样 ,如果是写邮件服务端的话就可能需要了解跟打开POP3 IMAP的服务吧

  • telnet的命令

    在命令行用telnet命令也可以实现连接邮箱的smtp服务器并发送邮件的功能

  • 卡在服务器验证阶段

    这里巨坑来着。。。之前也写过邮箱客户端的代码,但是就是因为发送函数send第三个参数的原因一直卡在服务器验证那里。正确的写法是

    send(sockfd, photo64.c_str(), photo64.length(), 0); 
    

    之前一直没有完成就是发送的数据大小的原因,也即第三个参数,那个时候我以为是自己定义的发送区的大小。但是很奇怪发送其他的命令这样子并不会出现问题,只有发送服务器验证命令的时候才会出问题。也是这个原因,巨坑,导致卡在服务器验证阶段很久。

  • va_arg函数

    见函数void smtp::attachment(int size,...)以及前面的参考链接

    其中va_arg函数的第二个参数如果是想表示字符串的话没办用string,只能用char*,因为这个是c语言库的函数。

  • 读取文件的大小

    见函数string smtp::base64_openfile(string filename)

        FILE *fp = fopen(filename.c_str(), "rb+");//二进制方式打开文件
        char *ar ;
      
        if(fp == NULL)
        {
            cout<<"openfile fail"<<endl;
            fclose(fp);
            return "";
        }
      
        //求得文件的大小
    	fseek(fp, 0, SEEK_END);//这里将指针移动到文件末尾
    	int size = ftell(fp);
    	rewind(fp);//将fp指针设置到文件开头
    

    因为要读取文件时会用到fread()函数,需要知道文件的大小。用fseek将指针移动到文件末尾,再用ftell算出文件的大小。这时候算出之后,一定要记得将指针移动到文件开头,不然fread()函数就会从末尾开始读,读到的将会是错误的数据。

  • fread函数

    前面的参考链接也有对fread函数的介绍,再这也不多加赘述。只是想说明,求出文件的大小再用fread函数会降低对资源的损耗。

  • string的加法

    C++中的string重载了+,+=,但是在进行连加的时候有一个限制,就是连加的时候前两项中必须有一项是string类型。

    所以可以看到代码中有这段看似有点zz的操作

    sendstring = "--@boundary@\r\nContent-Type: text/plain; name=";
    sendstring = sendstring +"\"" +filename +"\"" + "\r\n";  
    

待完成

  • c++python有邮件客户端的库,以后有机会可以尝试用一下,肯定比自己造的轮子好用。
  • 打算传个音频的,但是不知道什么原因传送失败,也没太多时间纠结于这上面,以后有空了再回头找找问题,函数为void smtp::send_attachment_music(string filename)

代码

传送门