本文来完成《Computer Networking: A Top-Down Approach》里有编写一个使用SMTP协议发送邮件的小程序的实验。

本实验使用的服务器是网易的smtp.126.com,向QQ邮箱发送一封邮件。在开始编写程序之前,先用telnet来摸索一下具体的流程。

实现原理

1. 首先建立TCP/IP连接,在命令行里输入`telnet smtp.126.com 25`连接网易邮件服务器,如果连接成功,服务器会返回`220 126.com Anti-spam GT for Coremail System (126com[20140526])`。

2. 发送`EHLO Alice`来开始与服务器的交互,我发现EHLO命令后面的内容对这个过程没有什么影响。如果无误,服务器返回:
250-mail
250-PIPELINING
250-AUTH LOGIN PLAIN
250-AUTH=LOGIN PLAIN
250-coremail 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UrdqNVBUCa
UUUUj
250-STARTTLS
250 8BITMIME

这么多东西看似复杂,其实知道返回的是数字250开始 的消息即可,毕竟程序要看的只是状态码。

3. 发送`AUTH LOGIN`来开始验证,服务器会返回`334 dXNlcm5hbWU6`,这个时候发送经过base64编码的网易邮箱地址,然后服务器又会返回`334 UGFzc3dvcmQ6`,接着发送经过base64编码的网页邮箱密码,如果用户名密码正确,服务器返回`235 Authentication successful`。

4. 发送`MAIL FROM: <*****@126.com>`,如果无误,服务器返回`250 Mail OK`。

5. 接着发送`RCPT TO: <*****@qq.com>`,如果没有问题,服务器返回`250 Mail OK`。

6. 发送`DATA`,服务器返回`354 End data with <CR><LF>.<CR><LF>`。

7. 现在我们可以开始发生邮件的内容了,比如:
from:^^^^@126.com
subject:I Love Computer Networks!
Content-type:text/html
<img src=\"http://www.people.com.cn/mediafile/200512/09/F2005120913054600000.jpg\"><h1>I love computer networks!</h1>
.

记得一定要加上from和subject这两个头,否则会被服务器判为垃圾邮件(虽然就是垃圾邮件)。如果没有问题,服务器返回250 Mail OK queued as smtp2,DMmowEAJb0O8NZJWTNpkAA--.10072S2 1452422762

8. 发送`QUIT`结束本次会话,然后服务器会自动断开连接。

9. 翻一下收件箱,邮件的确是收到了。![](https://img.sine-x.com/smtp.png)

编写程序

大概的流程也知道了,程序的任务就是建立连接,然后把该发送的消息发送出去,然后检测一下返回的状态码就好了。

from socket import *

# Mail content
subject = "I love computer networks!"
contenttype = "text/html"
content = "<img src=\"http://www.people.com.cn/mediafile/200512/09/F2005120913054600000.jpg\"><h1>I love computer networks!</h1>"

# Choose a mail server (e.g. Google mail server) and call it mailserver 
mailserver = 'smtp.126.com'

# Sender and reciever
fromaddress = "*****@126.com"
toaddress = "*****@qq.com"

# Auth information (Encode with base64)
username = "*****"
password = "*****"

# Create socket called clientSocket and establish a TCP connection with mailserver 
clientSocket = socket(AF_INET,SOCK_STREAM)
clientSocket.connect((mailserver, 25))

recv = clientSocket.recv(1024)
print recv 
if recv[:3] != '220': 
	print '220 reply not received from server.'

# Send EHLO command and print server response. 
heloCommand = 'EHLO Alice\r\n' 
clientSocket.send(heloCommand) 
recv1 = clientSocket.recv(1024) 
print recv1 
if recv1[:3] != '250':     
	print '250 reply not received from server.' 

# Auth
clientSocket.sendall('AUTH LOGIN\r\n')
recv = clientSocket.recv(1024)
print recv
if (recv[:3] != '334'):
	print '334 reply not received from server'
clientSocket.sendall(username + '\r\n')
recv = clientSocket.recv(1024)
print recv
if (recv[:3] != '334'):
	print '334 reply not received from server'
clientSocket.sendall(password + '\r\n')
recv = clientSocket.recv(1024)
print recv
if (recv[:3] != '235'):
	print '235 reply not received from server'

# Send MAIL FROM command and print server response. 
clientSocket.sendall('MAIL FROM: <' + fromaddress + '>\r\n')
recv = clientSocket.recv(1024)
print recv
if (recv[:3] != '250'):
	print '250 reply not received from server'

# Send RCPT TO command and print server response.  
clientSocket.sendall('RCPT TO: <' + toaddress + '>\r\n')
recv = clientSocket.recv(1024)
print recv
if (recv[:3] != '250'):
	print '250 reply not received from server'

# Send DATA command and print server response. 
clientSocket.send('DATA\r\n')
recv = clientSocket.recv(1024)
print recv
if (recv[:3] != '354'):
	print '354 reply not received from server'

# Send message data. 
msg = 'from:' + fromaddress + '\r\n'
msg += 'subject:' + subject + '\r\n'
msg += 'Content-Type:' + contenttype + '\t\n'
msg += '\r\n' + content
clientSocket.sendall(msg)

# Message ends with a single period. 
clientSocket.sendall('\r\n.\r\n')
recv = clientSocket.recv(1024)
print recv
if (recv[:3] != '250'):
	print '250 reply not received from server'

# Send QUIT command and get server response. 
clientSocket.sendall('QUIT\r\n')

# Close connection
clientSocket.close()