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

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的基本功能卢厂,但也收獲不少乾蓬,貼兩張圖吧

程序運(yùn)行圖
收到的郵件樣式

本次收獲如下

  • tcp/ip協(xié)議三次握手很重要,
  • 代碼執(zhí)行速度比網(wǎng)速快的多
  • Outlook發(fā)送郵件速度慢是有原因的
  • 自己造的輪子并沒(méi)有大神造的好用(純粹為了完成作業(yè))
  • 能不能把實(shí)驗(yàn)室網(wǎng)速提快點(diǎn)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末慎恒,一起剝皮案震驚了整個(gè)濱河市任内,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌融柬,老刑警劉巖死嗦,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異丹鸿,居然都是意外死亡越走,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門靠欢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)廊敌,“玉大人,你說(shuō)我怎么就攤上這事门怪÷獬海” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵掷空,是天一觀的道長(zhǎng)肋殴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)坦弟,這世上最難降的妖魔是什么护锤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮酿傍,結(jié)果婚禮上烙懦,老公的妹妹穿的比我還像新娘。我一直安慰自己赤炒,他們只是感情好氯析,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布亏较。 她就那樣靜靜地躺著,像睡著了一般掩缓。 火紅的嫁衣襯著肌膚如雪雪情。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天你辣,我揣著相機(jī)與錄音巡通,去河邊找鬼。 笑死绢记,一個(gè)胖子當(dāng)著我的面吹牛扁达,可吹牛的內(nèi)容都是我干的正卧。 我是一名探鬼主播蠢熄,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼炉旷!你這毒婦竟也來(lái)了签孔?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤窘行,失蹤者是張志新(化名)和其女友劉穎饥追,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體罐盔,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡但绕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惶看。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捏顺。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖纬黎,靈堂內(nèi)的尸體忽然破棺而出幅骄,到底是詐尸還是另有隱情,我是刑警寧澤本今,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布拆座,位于F島的核電站,受9級(jí)特大地震影響冠息,放射性物質(zhì)發(fā)生泄漏挪凑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一逛艰、第九天 我趴在偏房一處隱蔽的房頂上張望躏碳。 院中可真熱鬧,春花似錦瓮孙、人聲如沸唐断。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)脸甘。三九已至恳啥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丹诀,已是汗流浹背钝的。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铆遭,地道東北人硝桩。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像枚荣,于是被迫代替她去往敵國(guó)和親碗脊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容