Python Socket編程:利用SMTP發(fā)送MIME協(xié)議郵件

2017年12月31日更新

代碼更新為最新的OOP代碼,測試環(huán)境Python3.6,成功發(fā)送郵件。

Socket編程簡介

寫完程序也還是不理解什么事Socket編程,但在知乎里看到的一個問題里面的回答很不錯,這里分享一下:Socket編程簡介

SMTP簡介

SMTP(Simple Mail Transfer Protocol)即簡單郵件傳輸協(xié)議,它是一組用于由源地址到目的地址傳送郵件的規(guī)則,由它來控制信件的中轉方式。SMTP協(xié)議屬于TCP/IP協(xié)議簇,它幫助每臺計算機在發(fā)送或中轉信件時找到下一個目的地。通過SMTP協(xié)議所指定的服務器,就可以把E-mail寄到收信人的服務器上了,整個過程只要幾分鐘。SMTP服務器則是遵循SMTP協(xié)議的發(fā)送郵件服務器,用來發(fā)送或中轉發(fā)出的電子郵件。
它使用由TCP提供的可靠的數(shù)據(jù)傳輸服務把郵件消息從發(fā)信人的郵件服務器傳送到收信人的郵件服務器。跟大多數(shù)應用層協(xié)議一樣,SMTP也存在兩個 端:在發(fā)信人的郵件服務器上執(zhí)行的客戶端和在收信人的郵件服務器上執(zhí)行的服務器端。SMTP的客戶端和服務器端同時運行在每個郵件服務器上。當一個郵件服 務器在向其他郵件服務器發(fā)送郵件消息時,它是作為SMTP客戶在運行。
SMTP協(xié)議與人們用于面對面交互的禮儀之間有許多相似之處。首先,運行在發(fā)送端郵件服務器主機上的SMTP客戶,發(fā)起建立一個到運行在接收端郵件服務 器主機上的SMTP服務器端口號25之間的TCP連接。如果接收郵件服務器當前不在工作,SMTP客戶就等待一段時間后再嘗試建立該連接。SMTP客戶和服務器先執(zhí)行一些應用層握手操作。就像人們在轉手東西之前往往先自我介紹那樣,SMTP客戶和服務器也在傳送信息之前先自我介紹一下。 在這個SMTP握手階段,SMTP客戶向服務器分別指出發(fā)信人和收信人的電子郵件地址。彼此自我介紹完畢之后,客戶發(fā)出郵件消息。

MIME簡介

阮一峰的文章給了我很多靈感,具體請看MIME筆記

代碼

這里以QQ郵箱為例,之前用過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):
        """
        輸入用戶名和授權碼登陸QQ郵箱
        """
        print("正在發(fā)送登錄請求……")
        time.sleep(1)
        self.__sslclientSocket.send(self.loginCommand)
        recv2 = self.__sslclientSocket.recv(1024).decode('utf-8')
        if recv2[:3] !='334':
            print('登錄請求發(fā)送失?。?34 reply not received from server.')
            time.sleep(2)
            print('正在重試……')
            self.login()
        print("登錄請求發(fā)送成功……")
        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('服務器連接失?。?20 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('服務器響應失?。?50 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("發(fā)送成功!")
        sendmail.quitconnect()
    except Exception:
            print(Exception)
    finally:
        exit(0)

收獲和結論

網(wǎng)上幾乎沒有pythonSocket編程發(fā)送郵件的內容,也可能我沒找到,好多東西是借鑒C語言Socket編程發(fā)送郵件和基礎的MIME協(xié)議寫出來的,其實這些功能使用smtplib模塊完全可以解決,而且非常完美
我這個只是實現(xiàn)了基本的outlook的基本功能,但也收獲不少,貼兩張圖吧

程序運行圖
收到的郵件樣式

本次收獲如下

  • tcp/ip協(xié)議三次握手很重要,
  • 代碼執(zhí)行速度比網(wǎng)速快的多
  • Outlook發(fā)送郵件速度慢是有原因的
  • 自己造的輪子并沒有大神造的好用(純粹為了完成作業(yè))
  • 能不能把實驗室網(wǎng)速提快點
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容