socket编程实现SMTP邮件客户端

套接字编程作业3:邮件客户端

1、作业描述

《计算机网络:自顶向下方法》中第二章末尾给出了此编程作业的简单描述:

这个编程作业的目的是创建一个向任何接收方发送电子邮件的简单邮件客户。你的客户将必须与邮件服务器(如谷歌的电子邮件服务器)创建一个TCP连接,使用SMTP协议与该邮件服务器进行交谈,经该邮件服务器向某接收方(如你的朋友)发送一个电子邮件报文,最后关闭与该邮件服务器的TCP连接。

对本作业,配套Web站点为你的客户提供了框架代码。你的任务是完善该代码并通过向不同的用户账户发送电子邮件来测试你的客户。你也可以尝试通过不同的服务器(例如谷歌的邮件服务器和你所在大学的邮件服务器)进行发送。

2、基础知识

(1)了解SMTP协议(简单邮件传输协议)常见命令和响应

命令:

HELO——发送端的主机信息

MAIL FROM——发件人

RCPT TO—— 收件人,可以有多个

DATA——邮件主体内容

QUIT——断开连接

VRFY—— 需要对收件人名字进行验证

EXPN——需要扩展的邮件发送清单

HELP——命令名

响应:

220——服务器就绪

221——服务关闭

421——服务器未就绪,关闭传输信道

250——要求的邮件操作完成

501——参数格式错误

502——命令不可实现

503——错误的命令序列

504——命令参数不可实现

553——邮箱名不可用,要求的操作未执行

354——开始邮件输入,以“.”结束

535——权限验证失败

554——操作失败

(2)电子邮件系统的组成结构

电子邮件是一种异步通信方式,通信时不需要双方同时在场。电子邮件把邮件发送到收件人使用的邮件服务器,并放在其中的收件人邮箱中,收件人可以随时上网到自己使用的邮件服务器进行读取。

在这里插入图片描述

一个 电子邮件系统应具有三个最主要的组成构件:用户代理(User Agent);邮件服务器;电子邮件使用的协议,如SMTP、POP3 (或IMAP)等。

在这里插入图片描述

用户代理(UA):用户与电子邮件系统的接口。用户代理使用户能够通过一个很友好的接口发送和接收邮件,用户代理至少应当具有撰写、显示和邮件处理的功能。通常情况下,用户代理就是一个运行在PC.上的程序,常见的有Outlook、Foxmail 和Thunderbird等。
邮件服务器:组成电子邮件系统的核心。邮件服务器的功能是发送和接收邮件,同时还要向发信人报告邮件传送的情况(已交付、被拒绝、丢失等)。邮件服务器采用客户/服务器方式工作,但它能够同时充当客户和服务器。例如,当邮件服务器A向邮件服务器B发送邮件时,A就作为SMTP客户,而B是SMTP服务器;反之,当B向A发送邮件时,B就是SMTP客户,而A就是SMTP服务器。
邮件发送协议和读取协议:

邮件发送协议用于用户代理向邮件服务器发送邮件或在邮件服务器之间发送邮件,通常使用的是SMTP; 邮件读取协议用于用户代理从邮件服务器读取邮件,如POP3。SMTP采用的是**“推”(Push)的通信方式,即在用户代理向邮件服务器发送邮件及在邮件服务器之间发送邮件时,SMTP客户端主动将邮件“推”送到SMTP服务器端。POP3采用的是“拉”(Pull)**的通信方式,即用户读取邮件时,用户代理向邮件服务器发出请求,“拉”取用户邮箱中的邮件。

(3)base 64 编码

bse64是一种以64个可见字符集对二进制数据进行编码的编码算法。

1.1应用范围

base64常用于网络数据传输过程的编解码环节。HTTP环境下传递较长的标识信息。例如,在Java Persistence系统Hibernate中,就采用了base64来将一个较长的一个标识符(一般为128-bit的UUID)编码为一个字符串,用作HTTP表单和HTTP GET URL中的参数。在其他应用程序中,也常常需要把二进制数据编码为适合放在URL(包括隐藏表单域)中的形式。此时,采用Base64编码不仅比较简短,同时也具有不可读性,即所编码的数据不会被人用肉眼所直接看到 [引用百度百科] 。
【1】电子邮件加密;
【2】数据加密;
【3】采用base64来规避反垃圾邮件工具,因为那些工具通常都不会翻译base64的讯息;
【4】图片、文件网络传输。

1.2编码表

bse64编码表是64个可见字符集。
在这里插入图片描述

1.3编码过程(3x8变为4x6)

base64编码,每3个8位明文数据为一组,取这3个字数据的ASCII码,然后以6位为一组组成4个新的数据。对于不足3字节的处理:
【1】不足三字节后面填充0;
【2】对于编码前的数据产生的6位,如果为0,则索引到的字符为‘A’;因不足3字节而填充的0,用’=’来替代,有点结束符的意思。

例如对“ABCD”进行base64编码:

在这里插入图片描述

对于不足6位的补零(图中浅红色的4位),索引为“A”;对于最后不足3字节,进行补零处理(图中红色部分),以“=”替代,因此,“ABCD”的base64编码为:“QUJDRA==”。

1.4解码过程

base64解码,即是base64编码的逆过程,如果理解了编过过程,解码过程也就容易理解。将base64编码数据根据编码表分别索引到编码值,然后每4个编码值一组组成一个24位的数据流,解码为3个字符。对于末尾位“=”的base64数据,最终取得的4字节数据,需要去掉“=”再进行转换。

解码过程可以参考上图,逆向理解:“QUJDRA==” ——>“ABCD”

《base64编码参考文章》
https://blog.csdn.net/qq_20553613/article/details/95756154

3、流程分析

本文将实现一个SMTP客户端,使用163邮箱作为发件人,向指定的QQ邮箱发送一封邮件。SMTP协议即简单邮件传输协议,允许用户按照标准发送/接收邮件。

在本文中,SMTP邮件客户端程序的基本流程如下:

(1)建立TCP连接并域服务器交互

与163邮件服务器建立连接,域名"smtp.163.com",SMTP默认端口号25。建立连接后服务器将返回状态码220;与服务器的交互:发送"HELO"命令,开始与服务器的交互,服务器将返回状态码250(请求动作正确完成)。

(2)验证身份

发送"AUTH LOGIN"命令,开始验证身份,服务器将返回状态码334(服务器等待用户输入验证信息)。发送经过base64编码的用户名,服务器等待用户输入验证信息,将返回状态码334。发送经过base64编码的密码(不是登录密码,而是开启SMTP时的授权码),用户验证成功,服务器将返回状态码235。

(3)邮件传送

在这里插入图片描述

连接建立并且验证成功后,就可开始传送邮件。邮件的传送从MAIL FROM命令开始,MAIL 命令后面有发件人的地址。如MAIL FROM: abc@163.com。若SMTP服务器已准备好接收邮件,则回答250 OK。
接着SMTP客户端发送一个或多个RCPT (收件人recipient的缩写)命令,格式为RCPT TO: <收件人地址>。每发送一个 RCPT命令,都应有相应的信息从SMTP服务器返回,如250 OK或550 No such user here (无此用户)。
RCPT命令的作用:先弄清接收方系统是否已做好接收邮件的准备,然后才发送邮件,以便不至于发送了很长的邮件后才知道地址错误,进而避免浪费通信资源。

获得0K的回答后,客户端就使用DATA命令,表示要开始传输邮件的内容。
正常情况下,SMTP服务器回复信息是354 Start mail input; end with . 。表示回车换行。此时SMTP客户端就可开始传送邮件内容,并用. (两个回车,中间一个点)表示邮件内容的结束。

(4)连接释放

邮件发送完毕后,SMTP客户应发送QUIT命令。SMTP服务器返回的信息是221 (服务关闭),表示SMTP同意释放TCP连接。邮件传送的全部过程就此结束。

注意
  • 163邮箱默认关闭SMTP服务,QQ邮箱也是默认关闭的,但是学校的邮箱一般是打开的。如果没有打开,需要在设置中打开SMTP服务。另外,163邮箱在打开SMTP服务后,会设置一个授权码,使用这个授权码作为密码登录,不是平时使用的密码。

4、代码实现

# SMTPClient.py
from socket import *

msg = "\r\n I love computer networks!"
endMsg = "\r\n.\r\n"
# 选择一个邮件服务
mailServer = "smtp.163.com"
# 发送方地址和接收方地址,from 和 to
fromAddress = "***@163.com"
toAddress = "***@qq.com"
# 发送方,验证信息,由于邮箱输入信息会使用base64编码,因此需要进行编码
username = "***"  # 输入自己的用户名对应的编码
password = "***"  # 此处不是自己的密码,而是开启SMTP服务时对应的授权码

# 创建客户端套接字并建立连接
serverPort = 25  # SMTP使用25号端口
clientSocket = socket(AF_INET, SOCK_STREAM)
clientSocket.connect((mailServer, serverPort))  # connect只能接收一个参数
# 从客户套接字中接收信息
recv = clientSocket.recv(1024).decode()
print(recv)
if '220' != recv[:3]:
    print('220 reply not received from server.')

# 发送 HELO 命令并且打印服务端回复
# 开始与服务器的交互,服务器将返回状态码250,说明请求动作正确完成
heloCommand = 'HELO Alice\r\n'
clientSocket.send(heloCommand.encode())  # 随时注意对信息编码和解码
recv1 = clientSocket.recv(1024).decode()
print(recv1)
if '250' != recv1[:3]:
    print('250 reply not received from server.')

# 发送"AUTH LOGIN"命令,验证身份.服务器将返回状态码334(服务器等待用户输入验证信息)
clientSocket.sendall('AUTH LOGIN\r\n'.encode())
recv2 = clientSocket.recv(1024).decode()
print(recv2)
if '334' != recv2[:3]:
    print('334 reply not received from server.')

# 发送验证信息
clientSocket.sendall((username + '\r\n').encode())
recvName = clientSocket.recv(1024).decode()
print(recvName)
if '334' != recvName[:3]:
    print('334 reply not received from server')

clientSocket.sendall((password + '\r\n').encode())
recvPass = clientSocket.recv(1024).decode()
print(recvPass)
# 如果用户验证成功,服务器将返回状态码235
if '235' != recvPass[:3]:
    print('235 reply not received from server')

# TCP连接建立好之后,通过用户验证就可以开始发送邮件。邮件的传送从MAIL命令开始,MAIL命令后面附上发件人的地址。
# 发送 MAIL FROM 命令,并包含发件人邮箱地址
clientSocket.sendall(('MAIL FROM: <' + fromAddress + '>\r\n').encode())
recvFrom = clientSocket.recv(1024).decode()
print(recvFrom)
if '250' != recvFrom[:3]:
    print('250 reply not received from server')

# 接着SMTP客户端发送一个或多个RCPT (收件人recipient的缩写)命令,格式为RCPT TO: <收件人地址>。
# 发送 RCPT TO 命令,并包含收件人邮箱地址,返回状态码 250
clientSocket.sendall(('RCPT TO: <' + toAddress + '>\r\n').encode())
recvTo = clientSocket.recv(1024).decode()  # 注意UDP使用sendto,recvfrom
print(recvTo)
if '250' != recvTo[:3]:
    print('250 reply not received from server')

# 发送 DATA 命令,表示即将发送邮件内容。服务器将返回状态码354(开始邮件输入,以"."结束)
clientSocket.send('DATA\r\n'.encode())
recvData = clientSocket.recv(1024).decode()
print(recvData)
if '354' != recvData[:3]:
    print('354 reply not received from server')

# 编辑邮件信息,发送数据
subject = "I love computer networks!"
contentType = "text/plain"

message = 'from:' + fromAddress + '\r\n'
message += 'to:' + toAddress + '\r\n'
message += 'subject:' + subject + '\r\n'
message += 'Content-Type:' + contentType + '\t\n'
message += '\r\n' + msg
clientSocket.sendall(message.encode())

# 以"."结束。请求成功返回 250
clientSocket.sendall(endMsg.encode())
recvEnd = clientSocket.recv(1024).decode()
print(recvEnd)
if '250' != recvEnd[:3]:
    print('250 reply not received from server')

# 发送"QUIT"命令,断开和邮件服务器的连接
clientSocket.sendall('QUIT\r\n'.encode())

clientSocket.close()

5、运行结果

运行成功

如果运行成功,会依次看到服务器返回的每条消息,也就是每次操作后返回的状态码,因此对应的需要了解每个命令及其状态码含义。
在这里插入图片描述

运行失败(用户验证无法通过)

如果,没有注意输入的password为授权码,而是输入了自己的登录密码,会出现如下问题,也就是用户验证无法通过:

在这里插入图片描述

同时,我们还可以登陆邮箱,查看邮件收发情况。如果使用两个 QQ 邮箱也能按照如上操作实现邮件收发。

网易163邮箱

在这里插入图片描述

QQ邮箱

在这里插入图片描述

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值