2017年12月31日更新
代碼更新為最新的OOP代碼,測試環境Python3.6,成功發送郵件。
Socket編程簡介
寫完程序也還是不理解什么事Socket編程,但在知乎里看到的一個問題里面的回答很不錯,這里分享一下:Socket編程簡介
SMTP簡介
SMTP(Simple Mail Transfer Protocol)即簡單郵件傳輸協議,它是一組用于由源地址到目的地址傳送郵件的規則,由它來控制信件的中轉方式。SMTP協議屬于TCP/IP協議簇,它幫助每臺計算機在發送或中轉信件時找到下一個目的地。通過SMTP協議所指定的服務器,就可以把E-mail寄到收信人的服務器上了,整個過程只要幾分鐘。SMTP服務器則是遵循SMTP協議的發送郵件服務器,用來發送或中轉發出的電子郵件。
它使用由TCP提供的可靠的數據傳輸服務把郵件消息從發信人的郵件服務器傳送到收信人的郵件服務器。跟大多數應用層協議一樣,SMTP也存在兩個 端:在發信人的郵件服務器上執行的客戶端和在收信人的郵件服務器上執行的服務器端。SMTP的客戶端和服務器端同時運行在每個郵件服務器上。當一個郵件服 務器在向其他郵件服務器發送郵件消息時,它是作為SMTP客戶在運行。
SMTP協議與人們用于面對面交互的禮儀之間有許多相似之處。首先,運行在發送端郵件服務器主機上的SMTP客戶,發起建立一個到運行在接收端郵件服務 器主機上的SMTP服務器端口號25之間的TCP連接。如果接收郵件服務器當前不在工作,SMTP客戶就等待一段時間后再嘗試建立該連接。SMTP客戶和服務器先執行一些應用層握手操作。就像人們在轉手東西之前往往先自我介紹那樣,SMTP客戶和服務器也在傳送信息之前先自我介紹一下。 在這個SMTP握手階段,SMTP客戶向服務器分別指出發信人和收信人的電子郵件地址。彼此自我介紹完畢之后,客戶發出郵件消息。
MIME簡介
阮一峰的文章給了我很多靈感,具體請看MIME筆記
代碼
這里以QQ郵箱為例,之前用過126郵箱,用普通的25端口就能發送郵件,QQ郵箱則不可以,所以專門研究了下QQ郵箱
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__author__ = 'memgq'
import socket
import ssl
import base64
import time
import os
import random
class SendMail:
__username=''
__password=''
__recipient=''
msg = b'\r\n'
endmsg = b'\r\n.\r\n'
mailserver = ('smtp.qq.com', 465)
heloCommand = b'HELO qq.com\r\n'
loginCommand = b'AUTH login\r\n'
dataCommand = b'DATA\r\n'
quitCommand = b'QUIT\r\n'
msgsubject = b'Subject: Test E-mail\r\n'
msgtype = b"Content-Type: multipart/mixed;boundary='BOUNDARY'\r\n\r\n"
msgboundary = b'--BOUNDARY\r\n'
msgmailer = b'X-Mailer:mengqi\'s mailer\r\n'
msgMIMI = b'MIME-Version:1.0\r\n'
msgfileType = b"Content-type:application/octet-stream;charset=utf-8\r\n"
msgfilename = b"Content-Disposition: attachment; filename=''\r\n"
msgimgtype = b"Content-type:image/gif;\r\n"
msgimgname = b"Content-Disposition: attachment; filename=''\r\n"
msgtexthtmltype = b'Content-Type:text/html;\r\n'
msgimgId = b'Content-ID:<test>\r\n'
msgimgscr = b'<img src="cid:test">'
mailcontent = ''
__clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def login(self):
"""
輸入用戶名和授權碼登陸QQ郵箱
"""
print("正在發送登錄請求……")
time.sleep(1)
self.__sslclientSocket.send(self.loginCommand)
recv2 = self.__sslclientSocket.recv(1024).decode('utf-8')
if recv2[:3] !='334':
print('登錄請求發送失敗:334 reply not received from server.')
time.sleep(2)
print('正在重試……')
self.login()
print("登錄請求發送成功……")
self.__username = input("請輸入用戶名:")
self.__password = input("請輸入授權碼:")
print("正在登錄……")
time.sleep(1)
username = b'%s\r\n' % base64.b64encode(self.__username.encode('utf-8'))
self.__sslclientSocket.send(username)
recv = self.__sslclientSocket.recv(1024).decode('utf-8')
password = b'%s\r\n' % base64.b64encode(self.__password.encode('utf-8'))
self.__sslclientSocket.send(password)
recv = self.__sslclientSocket.recv(1024).decode('utf-8')
if recv[:3] != '235':
print('登錄失敗:賬號或密碼錯誤,請使用授權碼登錄. 235 reply not received from server.',recv)
time.sleep(2)
print('正在重試……')
self.login()
print("登錄成功")
time.sleep(1)
def socketconnet(self):
"""
使用socket套接字連接qq郵箱服務器,并設置ssl驗證
"""
print("正在連接服務器……")
time.sleep(1)
self.__sslclientSocket = ssl.wrap_socket(self.__clientSocket, cert_reqs=ssl.CERT_NONE,
ssl_version=ssl.PROTOCOL_SSLv23)
self.__sslclientSocket.connect(self.mailserver)
recv = self.__sslclientSocket.recv(1024).decode('utf-8')
if recv[:3] != '220':
print('服務器連接失敗:220 reply not received from server.')
time.sleep(2)
print('正在重試……')
self.socketconnet()
print("成功連接服務器……")
time.sleep(1)
print("正在請求服務器響應……")
time.sleep(1)
self.__sslclientSocket.send(self.heloCommand)
recv1 = self.__sslclientSocket.recv(1024).decode('utf-8')
if recv1[:3] != '250':
print('服務器響應失敗:250 replay not received from server')
time.sleep(2)
print('正在重試……')
self.socketconnet()
print("成功請求服務器響應……")
time.sleep(1)
def sender(self):
mailsenderCommand = b'MAIL FROM:<%s>\r\n' % self.__username.encode('utf-8')
self.__sslclientSocket.send(mailsenderCommand)
def recipient(self):
self.__recipient = input("請輸入收件人郵箱:")
time.sleep(1)
mailrecipientCommand = b'RCPT TO:<%s>\r\n' % self.__recipient.encode('utf-8')
self.__sslclientSocket.send(mailrecipientCommand)
recv = self.__sslclientSocket.recv(1024).decode('utf-8')
if recv[:3] != '250':
print("收件人郵箱錯誤:250 replay not received from server")
time.sleep(1)
self.recipient()
def senddata(self):
self.__sslclientSocket.send(self.dataCommand)
recv = self.__sslclientSocket.recv(1024).decode('utf-8')
if recv[:3] != '354':
time.sleep(1)
self.senddata()
def sendsubject(self):
subject = input("請輸入郵件主題:")
time.sleep(1)
self.msgsubject = b'Subject: %s\r\n' % subject.encode('utf-8')
self.__sslclientSocket.send(self.msgsubject)
self.__sslclientSocket.send(self.msgmailer)
self.__sslclientSocket.send(self.msgtype)
self.__sslclientSocket.send(b'Content-Transfer-Encoding:7bit\r\n\r\n')
def writemail(self):
self.__sslclientSocket.send(b'\r\n\r\n' + self.msgboundary)
self.__sslclientSocket.send(b'Content-Type: text/html;charset=utf-8\r\n')
self.__sslclientSocket.send(b'Content-Transfer-Encoding:7bit\r\n\r\n')
self.mailcontent=input("請輸入郵件正文:\n")
time.sleep(1)
self.__sslclientSocket.sendall(b'%s\r\n'%self.mailcontent.encode('utf-8'))
def addfile(self):
filepath=input("請輸入文件路徑:")
time.sleep(1)
# filepath=filepath.replace('\\','/')
if os.path.isfile(filepath):
filename = os.path.basename(filepath)
#print(filename)
time.sleep(0.1)
self.__sslclientSocket.send(b'\r\n\r\n' + self.msgboundary)
self.__sslclientSocket.send(self.msgfileType)
self.msgfilename = b"Content-Disposition: attachment; filename='%s'\r\n" % filename.encode('utf-8')
self.__sslclientSocket.send(self.msgfilename)
#print(self.msgfilename)
self.__sslclientSocket.send(b'Content-Transfer-Encoding:base64\r\n\r\n')
self.__sslclientSocket.send(self.msg)
#print(1)
time.sleep(0.1)
fb = open(filepath,'rb')
while True:
filedata = fb.read(1024)
# print(filedata)
if not filedata:
break
self.__sslclientSocket.send(base64.b64encode(filedata))
time.sleep(1)
fb.close()
#print(2)
time.sleep(0.1)
def addimg(self):
self.mailcontent = input("請輸入郵件正文:")
time.sleep(1)
filepath = input("請輸入圖片路徑:")
time.sleep(1)
# filepath = filepath.replace('\\', '/')
if os.path.isfile(filepath):
# print(1)
time.sleep(0.1)
filename = os.path.basename(filepath)
randomid = filename.split('.')[1]+str(random.randint(1000, 9999))
# print(randomid)
time.sleep(0.1)
self.msgimgId = b'Content-ID:%s\r\n' % randomid.encode('utf-8')
self.__sslclientSocket.send(b'\r\n\r\n' + self.msgboundary)
self.__sslclientSocket.send(self.msgimgtype)
self.__sslclientSocket.send(self.msgimgId)
self.msgimgname = b"Content-Disposition: attachment; filename='%s'\r\n" % filename.encode('utf-8')
self.__sslclientSocket.send(self.msgfilename)
# print(self.msgimgId)
time.sleep(0.1)
self.__sslclientSocket.send(b'Content-Transfer-Encoding:base64\r\n\r\n')
self.__sslclientSocket.send(self.msg)
fb = open(filepath, 'rb')
while True:
filedata = fb.read(1024)
# print(filedata)
if not filedata:
break
self.__sslclientSocket.send(base64.b64encode(filedata))
time.sleep(0.1)
fb.close()
# print(1)
time.sleep(0.1)
self.__sslclientSocket.send(b'\r\n\r\n' + self.msgboundary)
self.__sslclientSocket.send(self.msgtexthtmltype)
self.__sslclientSocket.send(b'Content-Transfer-Encoding:8bit\r\n\r\n')
msgimgscr = b'<img src="cid:%s">'%randomid.encode('utf-8')
# print(1)
time.sleep(0.1)
self.__sslclientSocket.send(msgimgscr)
# print(msgimgscr)
time.sleep(0.1)
self.__sslclientSocket.sendall(b'%s' % self.mailcontent.encode('utf-8'))
# print(msgimgscr)
time.sleep(0.1)
def sendmail(self):
# self.addimg()
# print(1)
# time.sleep(1)
# self.addfile()
# print(2)
# self.__sslclientSocket.send(self.endmsg)
bool_addimg = input("是否添加圖片<Y/N>:")
bool_addfile = input("是否添加附件<Y/N>:")
if bool_addimg.lower() == 'y':
if bool_addfile.lower() == 'y':
self.addimg()
print(1)
self.addfile()
print(2)
self.__sslclientSocket.send(self.endmsg)
else:
self.addimg()
self.__sslclientSocket.send(self.endmsg)
else:
if bool_addfile.lower() == 'y':
self.writemail()
self.addfile()
self.__sslclientSocket.send(self.endmsg)
else:
self.writemail()
self.__sslclientSocket.send(self.endmsg)
def quitconnect(self):
self.__sslclientSocket.send(self.quitCommand)
if __name__ == '__main__':
try:
sendmail = SendMail()
sendmail.socketconnet()
sendmail.login()
sendmail.sender()
sendmail.recipient()
sendmail.senddata()
sendmail.sendsubject()
sendmail.sendmail()
time.sleep(1)
print("發送成功!")
sendmail.quitconnect()
except Exception:
print(Exception)
finally:
exit(0)
收獲和結論
網上幾乎沒有pythonSocket編程發送郵件的內容,也可能我沒找到,好多東西是借鑒C語言Socket編程發送郵件和基礎的MIME協議寫出來的,其實這些功能使用smtplib模塊完全可以解決,而且非常完美
我這個只是實現了基本的outlook的基本功能,但也收獲不少,貼兩張圖吧
本次收獲如下
- tcp/ip協議三次握手很重要,
- 代碼執行速度比網速快的多
- Outlook發送郵件速度慢是有原因的
- 自己造的輪子并沒有大神造的好用(純粹為了完成作業)
- 能不能把實驗室網速提快點