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

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

图3
如图3所示与qq邮件服务器建立连接,域名"smtp.qq.com",SMTP默认端口号25。建立连接后服务器将返回状态码220;与服务器的交互:发送"HELO"命令,开始与服务器的交互,服务器将返回状态码250(请求动作正确完成)。
发送"AUTH LOGIN"命令,开始验证身份,服务器将返回状态码334(服务器等待用户输入验证信息)。发送经过base64编码的用户名,服务器等待用户输入验证信息,将返回状态码334。发送经过base64编码的密码(不是登录密码,而是开启SMTP时的授权码),用户验证成功,服务器将返回状态码235如图4所示。

图4
| msg = "\r\n I love computer networks!" endMsg = "\r\n.\r\n" # 选择一个邮件服务 mailServer = "smtp.qq.com" # 发送方地址和接收方地址,from 和 to fromAddress = "2485947593@qq.com" toAddress = "3050387300@qq.com" |

图5
连接建立并且验证成功后,就可开始传送邮件。邮件的传送从MAIL FROM命令开始,MAIL 命令后面有发件人的地址。MAIL FROM: 2485947593@qq.com。若SMTP服务器已准备好接收邮件,则回答250 OK。接着SMTP客户端发送一个或多个RCPT (收件人recipient的缩写)命令,格式为RCPT TO: <3050387300@qq.com>。每发送一个 RCPT命令,都应有相应的信息从SMTP服务器返回。正常情况下,SMTP服务器回复信息是354 Start mail input; end with . 。表示回车换行。此时SMTP客户端就可开始传送邮件内容,并用. (两个回车,中间一个点)表示邮件内容的结束。
邮件发送完毕后,SMTP客户应发送QUIT命令。SMTP服务器返回的信息是221 (服务关闭),表示SMTP同意释放TCP连接。邮件传送的全部过程就此结束。同时,我们还可以登陆邮箱,查看邮件收发情况如图6、7所示。

图6

图7
- # SMTPClient.py
- from socket import *
- msg = "\r\n I love computer networks!"
- endMsg = "\r\n.\r\n"
- # 选择一个邮件服务
- mailServer = "smtp.qq.com"
- # 发送方地址和接收方地址,from 和 to
- fromAddress = "2485947593@qq.com"
- toAddress = "3050387300@qq.com"
- # 发送方,验证信息,由于邮箱输入信息会使用base64编码,因此需要进行编码
- username = "MjQ4NTk0NzU5M0BxcS5jb20=" # 输入自己的用户名对应的编码
- password = "ZHRobnlvZmVyZGZsZWFpYg==" # 此处不是自己的密码,而是开启SMTP服务时对应的授权码dthnyoferdfleaib
-
- # 创建客户端套接字并建立连接
- 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()
附:了解SMTP协议(简单邮件传输协议)常见命令和响应
命令:
HELO——发送端的主机信息
MAIL FROM——发件人
RCPT TO—— 收件人,可以有多个
DATA——邮件主体内容
QUIT——断开连接
VRFY—— 需要对收件人名字进行验证
EXPN——需要扩展的邮件发送清单
HELP——命令名
响应:
220——服务器就绪
221——服务关闭
421——服务器未就绪,关闭传输信道
250——要求的邮件操作完成
501——参数格式错误