2017年12月31日更新
代碼更新為最新的OOP代碼装获,測(cè)試環(huán)境Python3.6瑞信,成功發(fā)送郵件厉颤。
Socket編程簡(jiǎn)介
寫完程序也還是不理解什么事Socket編程穴豫,但在知乎里看到的一個(gè)問(wèn)題里面的回答很不錯(cuò),這里分享一下:Socket編程簡(jiǎn)介
SMTP簡(jiǎn)介
SMTP(Simple Mail Transfer Protocol)即簡(jiǎn)單郵件傳輸協(xié)議,它是一組用于由源地址到目的地址傳送郵件的規(guī)則逼友,由它來(lái)控制信件的中轉(zhuǎn)方式精肃。SMTP協(xié)議屬于TCP/IP協(xié)議簇,它幫助每臺(tái)計(jì)算機(jī)在發(fā)送或中轉(zhuǎn)信件時(shí)找到下一個(gè)目的地帜乞。通過(guò)SMTP協(xié)議所指定的服務(wù)器,就可以把E-mail寄到收信人的服務(wù)器上了司抱,整個(gè)過(guò)程只要幾分鐘。SMTP服務(wù)器則是遵循SMTP協(xié)議的發(fā)送郵件服務(wù)器黎烈,用來(lái)發(fā)送或中轉(zhuǎn)發(fā)出的電子郵件习柠。
它使用由TCP提供的可靠的數(shù)據(jù)傳輸服務(wù)把郵件消息從發(fā)信人的郵件服務(wù)器傳送到收信人的郵件服務(wù)器。跟大多數(shù)應(yīng)用層協(xié)議一樣照棋,SMTP也存在兩個(gè) 端:在發(fā)信人的郵件服務(wù)器上執(zhí)行的客戶端和在收信人的郵件服務(wù)器上執(zhí)行的服務(wù)器端资溃。SMTP的客戶端和服務(wù)器端同時(shí)運(yùn)行在每個(gè)郵件服務(wù)器上。當(dāng)一個(gè)郵件服 務(wù)器在向其他郵件服務(wù)器發(fā)送郵件消息時(shí)烈炭,它是作為SMTP客戶在運(yùn)行溶锭。
SMTP協(xié)議與人們用于面對(duì)面交互的禮儀之間有許多相似之處。首先符隙,運(yùn)行在發(fā)送端郵件服務(wù)器主機(jī)上的SMTP客戶趴捅,發(fā)起建立一個(gè)到運(yùn)行在接收端郵件服務(wù) 器主機(jī)上的SMTP服務(wù)器端口號(hào)25之間的TCP連接。如果接收郵件服務(wù)器當(dāng)前不在工作霹疫,SMTP客戶就等待一段時(shí)間后再嘗試建立該連接拱绑。SMTP客戶和服務(wù)器先執(zhí)行一些應(yīng)用層握手操作。就像人們?cè)谵D(zhuǎn)手東西之前往往先自我介紹那樣丽蝎,SMTP客戶和服務(wù)器也在傳送信息之前先自我介紹一下欺栗。 在這個(gè)SMTP握手階段,SMTP客戶向服務(wù)器分別指出發(fā)信人和收信人的電子郵件地址征峦。彼此自我介紹完畢之后迟几,客戶發(fā)出郵件消息。
MIME簡(jiǎn)介
阮一峰的文章給了我很多靈感栏笆,具體請(qǐng)看MIME筆記
代碼
這里以QQ郵箱為例类腮,之前用過(guò)126郵箱,用普通的25端口就能發(fā)送郵件蛉加,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):
"""
輸入用戶名和授權(quán)碼登陸QQ郵箱
"""
print("正在發(fā)送登錄請(qǐng)求……")
time.sleep(1)
self.__sslclientSocket.send(self.loginCommand)
recv2 = self.__sslclientSocket.recv(1024).decode('utf-8')
if recv2[:3] !='334':
print('登錄請(qǐng)求發(fā)送失敻滋印:334 reply not received from server.')
time.sleep(2)
print('正在重試……')
self.login()
print("登錄請(qǐng)求發(fā)送成功……")
self.__username = input("請(qǐng)輸入用戶名:")
self.__password = input("請(qǐng)輸入授權(quán)碼:")
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('登錄失敗:賬號(hào)或密碼錯(cuò)誤厂抽,請(qǐng)使用授權(quán)碼登錄. 235 reply not received from server.',recv)
time.sleep(2)
print('正在重試……')
self.login()
print("登錄成功")
time.sleep(1)
def socketconnet(self):
"""
使用socket套接字連接qq郵箱服務(wù)器需频,并設(shè)置ssl驗(yàn)證
"""
print("正在連接服務(wù)器……")
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('服務(wù)器連接失敗:220 reply not received from server.')
time.sleep(2)
print('正在重試……')
self.socketconnet()
print("成功連接服務(wù)器……")
time.sleep(1)
print("正在請(qǐng)求服務(wù)器響應(yīng)……")
time.sleep(1)
self.__sslclientSocket.send(self.heloCommand)
recv1 = self.__sslclientSocket.recv(1024).decode('utf-8')
if recv1[:3] != '250':
print('服務(wù)器響應(yīng)失斂攴铩:250 replay not received from server')
time.sleep(2)
print('正在重試……')
self.socketconnet()
print("成功請(qǐng)求服務(wù)器響應(yīng)……")
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("請(qǐng)輸入收件人郵箱:")
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("收件人郵箱錯(cuò)誤: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("請(qǐng)輸入郵件主題:")
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("請(qǐng)輸入郵件正文:\n")
time.sleep(1)
self.__sslclientSocket.sendall(b'%s\r\n'%self.mailcontent.encode('utf-8'))
def addfile(self):
filepath=input("請(qǐng)輸入文件路徑:")
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("請(qǐng)輸入郵件正文:")
time.sleep(1)
filepath = input("請(qǐng)輸入圖片路徑:")
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("發(fā)送成功昭殉!")
sendmail.quitconnect()
except Exception:
print(Exception)
finally:
exit(0)
收獲和結(jié)論
網(wǎng)上幾乎沒(méi)有pythonSocket編程發(fā)送郵件的內(nèi)容,也可能我沒(méi)找到藐守,好多東西是借鑒C語(yǔ)言Socket編程發(fā)送郵件和基礎(chǔ)的MIME協(xié)議寫出來(lái)的挪丢,其實(shí)這些功能使用smtplib模塊完全可以解決,而且非常完美
我這個(gè)只是實(shí)現(xiàn)了基本的outlook的基本功能卢厂,但也收獲不少乾蓬,貼兩張圖吧
本次收獲如下
- tcp/ip協(xié)議三次握手很重要,
- 代碼執(zhí)行速度比網(wǎng)速快的多
- Outlook發(fā)送郵件速度慢是有原因的
- 自己造的輪子并沒(méi)有大神造的好用(純粹為了完成作業(yè))
- 能不能把實(shí)驗(yàn)室網(wǎng)速提快點(diǎn)