Python Socket编程:利用SMTP发送MIME协议邮件

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:\r\n'
    msgimgscr = b''
    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''%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("是否添加图片:")
        bool_addfile = input("是否添加附件:")
        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的基本功能,但也收获不少,贴两张图吧

Python Socket编程:利用SMTP发送MIME协议邮件_第1张图片
程序运行图
Python Socket编程:利用SMTP发送MIME协议邮件_第2张图片
收到的邮件样式

本次收获如下

  • tcp/ip协议三次握手很重要,
  • 代码执行速度比网速快的多
  • Outlook发送邮件速度慢是有原因的
  • 自己造的轮子并没有大神造的好用(纯粹为了完成作业)
  • 能不能把实验室网速提快点

你可能感兴趣的:(Python Socket编程:利用SMTP发送MIME协议邮件)