天天看點

Python-Net程式設計網絡程式設計ICP/UDP協定FTP程式設計Mail程式設計

網絡程式設計

  • 網絡:
  • 網絡協定: 一套規則
  • 網絡模型:
    • 七層模型-七層-理論
      • 實體層
      • 資料鍊路層
      • 網絡層
      • 傳輸層
      • 會話層
      • 表示層
      • 應用層
    • 四層模型-實際應用
      • 鍊路層
      • 引用層
  • 每一層都有相應的協定負責交換資訊或者協同工作
  • TCP/IP 協定族
  • IP位址:負責在網絡上唯一定位一個機器
    • IP位址分ABCDE類
    • 是由四個數字段組成,每個數字段的取值是0-255
    • 192.168.xxx.xxx: 區域網路ip
    • 127.0.0.1: 本機
    • IPv4, IPv6
  • 端口
    • 範圍: 0-65535
      • 知名端口:0-1023
      • 非知名端口:1024-

ICP/UDP協定

  • UDP: 非安全的不面向連結的傳輸
    • 安全性差
    • 大小限制64kb
    • 沒有順序
    • 速度快
  • TCP
    • 基于連結的通信
  • SOCKET程式設計
    • socket(套接字):是一個網絡通信的端點,能實作不同主機的程序通信,網絡大多基于socket通信
    • 通過IP+端口定位對方并發送消息的通信機制
    • 分為UDP和TCP
    • 用戶端Client,發起通路的一方
    • 伺服器端Server,接受通路的一方
  • UDP程式設計
    • Server端流程
      1. 建立socket,socket是負責具體通信的一個執行個體
      2. 綁定,為建立的socket指派固定的端口和ip位址
      3. 接受對方發送内容
      4. 給對方發送回報,此步驟為非必須步驟
    • Client端流程
      1. 建立通信的socket
      2. 發送内容到指定伺服器
      3. 接受伺服器給定的回報内容
    • 伺服器案例v01
      '''
      Server端流程
      1. 建立socket,socket是負責具體通信的一個執行個體
      2. 綁定,為建立的socket指派固定的端口和ip位址
      3. 接受對方發送内容
      4. 給對方發送回報,此步驟為非必須步驟
      '''
      
      # socket子產品負責socket程式設計
      import socket
      
      # 模拟伺服器的函數
      def serverFunc():
          # 1. 建立socket
      
          # socket.AF_INET: 使用ipv4協定族
          # socket.SOCK_DGRAM: 使用UDP通信
          sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      
          # 2. 綁定ip和port
          # 127.0.0.1: 這個ip位址代表的是機器本身
          # 7852: 随機指定的端口号
          # 位址是一個tuple類型,(ip, port)
          addr = ("127.0.0.1", 7852)
          sock.bind(addr)
      
          # 3. 接受對方消息
          # 等待方式為死等,沒有其他可能性
          # recvfrom接受的傳回值是一個tuple,前一項表示資料,後一項表示位址
          # 參數的含義是緩沖區大小
          # rst = sock.recvfrom(500)
          data, addr = sock.recvfrom(500)
      
          print(data)
          print(type(data))
      
          # 發送過來的資料是bytes格式,必須通過解碼才能得到str格式内容
          # decode預設參數是utf8
          text = data.decode()
          print(type(text))
          print(text)
      
          # 給對方傳回的消息
          rsp = "Ich hab keine Hunge"
      
          # 發送的資料需要編碼成bytes格式
          # 預設是utf8
          data = rsp.encode()
          sock.sendto(data, addr)
      
      if __name__ == '__main__':
          print("Starting server.........")
          serverFunc()
          print("Ending server...........")           
    • 用戶端案例v02
      import socket
      
      '''
      Client端流程
      1. 建立通信的socket
      2. 發送内容到指定伺服器
      3. 接受伺服器給定的回報内容
      '''
      
      def clientFunc():
      
          sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      
          text = "I love you"
      
          # 發送的資料必須是bytes格式
          data = text.encode()
      
          # 發送
          sock.sendto(data, ("127.0.0.1", 7852))
      
          data, addr = sock.recvfrom(200)
      
          data = data.decode()
      
          print(data)
      
      if __name__ == '__main__':
          clientFunc()
                 
    • 伺服器程式要永久運作,一般用死循環處理
    • 改造的伺服器版本v03
      '''
      Server端流程
      1. 建立socket,socket是負責具體通信的一個執行個體
      2. 綁定,為建立的socket指派固定的端口和ip位址
      3. 接受對方發送内容
      4. 給對方發送回報,此步驟為非必須步驟
      '''
      
      # socket子產品負責socket程式設計
      import socket
      
      # 模拟伺服器的函數
      def serverFunc():
          # 1. 建立socket
      
          # socket.AF_INET: 使用ipv4協定族
          # socket.SOCK_DGRAM: 使用UDP通信
          sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      
          # 2. 綁定ip和port
          # 127.0.0.1: 這個ip位址代表的是機器本身
          # 7852: 随機指定的端口号
          # 位址是一個tuple類型,(ip, port)
          addr = ("127.0.0.1", 7852)
          sock.bind(addr)
      
          # 3. 接受對方消息
          # 等待方式為死等,沒有其他可能性
          # recvfrom接受的傳回值是一個tuple,前一項表示資料,後一項表示位址
          # 參數的含義是緩沖區大小
          # rst = sock.recvfrom(500)
          data, addr = sock.recvfrom(500)
      
          print(data)
          print(type(data))
      
          # 發送過來的資料是bytes格式,必須通過解碼才能得到str格式内容
          # decode預設參數是utf8
          text = data.decode()
          print(type(text))
          print(text)
      
          # 給對方傳回的消息
          rsp = "Ich hab keine Hunge"
      
          # 發送的資料需要編碼成bytes格式
          # 預設是utf8
          data = rsp.encode()
          sock.sendto(data, addr)
      
      if __name__ == '__main__':
          import time
          while 1:
              try:
                  serverFunc()
              except Exception as e:
                  print(e)
      
              time.sleep(1)           
  • TCP程式設計
    • 面向連結的傳輸,即每次傳輸之前需要先建立一個連結
    • 用戶端和服務端兩個程式需要編寫
    • Server端的編寫流程
      1. 建立socket負責具體通信,這個socket其實隻負責接受對方的請求,真正通信的是連結後重建立立的socket
      2. 綁定端口和位址
      3. 監聽接入的通路socket
      4. 接受通路的socket,可以了解接受通路即建立了一個通訊的連結通路
      5. 接受對方的發送内容,利用接收到的socket接受内容
      6. 如果有必要,給對方發送回報資訊
      7. 關閉連結通路
    • Client端的編寫流程
      1. 建立通信socket
      2. 連結對方,請求跟對方建立通路
      3. 發送内容到對方伺服器
      4. 接受對方的回報
    • 案例v04
      import socket
      
      def tcp_srv():
          # 1. 建立socket負責具體通信,這個socket其實隻負責接受對方的請求,真正通信的是連結後重建立立的socket
          # 需要用到兩個參數
          # AF_INET: 含義同UDP一緻
          # SOCK_STREAM: 表明使用的是TCP進行通信
          sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
          # 2. 綁定端口和位址
          # 此位址資訊是一個元組類型内容,元組分兩部分,第一部分為字元串,代表ip,第二部分為端口,是一個整數,推薦大于10000
          addr = ("127.0.0.1", 8998)
          sock.bind(addr)
          # 3. 監聽接入的通路socket
          sock.listen()
      
          while True:
              # 4. 接受通路的socket,可以了解為接受通路即建立了一個通訊的連結通路
              # accept傳回的元組第一個元素指派給skt,第二個指派給addr
              skt, addr = sock.accept()
              # 5. 接受對方的發送内容,利用接受到的socket接受内容
              # 500代表接受使用的buffersize
              # msg = skt.receive(500)
              msg = skt.recv(500)
              # 接受到的是bytes格式内容
              # 想得到str格式,需要進行解碼
              msg = msg.decode()
      
              rst = "Receive msg: {0} from {1}".format(msg, addr)
              print(rst)
              # 6. 如果有必要,給對方發送回報資訊
              skt.send(rst.encode())
      
              # 7. 關閉連結通路
              skt.close()
      
      if __name__ == '__main__':
          print("Starting tcp server........")
          tcp_srv()
          print("Ending tcp server..........")           
    • 案例v05
      import socket
      
      def tcp_clt():
          # 1. 建立通信socket
          sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
          # 2. 連結對方,請求跟對方建立通路
          addr = ("127.0.0.1", 8998)
          sock.connect(addr)
          # 3. 發送内容到對方伺服器
          msg = "I love you"
          sock.send(msg.encode())
          # 4. 接受對方的回報
          rst = sock.recv(500)
          print(rst.decode())
          # 5. 關閉連結通路
          sock.close()
      
      if __name__ == '__main__':
          tcp_clt()           

FTP程式設計

  • FTP(FileTransferProtoacal)檔案傳輸協定
  • 用途:定制一些特殊的上傳下載下傳檔案的服務
  • 使用者分類:登入FTP伺服器必須有一個賬号
    • Real賬戶:注冊賬戶
    • Guest賬戶:可能臨時對某一類人的行為進行授權
    • Anonymous賬戶:匿名賬戶,允許任何人
  • FIP工作流程
    1. 用戶端連結遠端主機上的FTP伺服器
    2. 用戶端輸入使用者名和密碼(或者“anonymous”和電子郵件位址)
    3. 用戶端和伺服器進行各種檔案傳輸和資訊查詢操作
    4. 用戶端從遠端FTP伺服器退出,結束傳輸
  • FTP檔案表示
    • 分三段表示FTP伺服器上的檔案
    • HOST:主機位址,類似于 ftp.mozilla.org, 以 ftp 開頭
    • DIR:目錄,表示檔案所在本地的路徑,例如 pub/android/focus/1.1-RC1
    • File:檔案名稱,例如 Klar-1.1-RC1.apk
    • 如果想完整精确表示ftp上某一檔案,需要上述三部分組合到一起
    • 案例v06
      # 需要導入相應包,主要是ftplib
      import ftplib # 關于FTP的操作都在這個包裡邊
      import os
      import socket
      
      # 三部分精确表示在ftp伺服器上的某一個檔案
      # 好多公開ftp伺服器通路會出錯或者沒有反應
      HOST = "ftp.acc.umu.se"
      DIR = 'Public/EFLIB/'
      FILE = 'README'
      
      # 1. 用戶端連結遠端主機上的FTP伺服器
      try:
          f = ftplib.FTP()
          # 通過設定調試級别可以友善調試
          f.set_debuglevel(2)
          # 連結主機位址
          f.connect(HOST)
      except Exception as e:
          print(e)
          exit()
      print("***Connected to host {0}".format(HOST))
      
      
      
      # 2. 用戶端輸入使用者名和密碼(或者“anonymous”和電子郵件位址)
      try:
          # 登入如果沒有輸入使用者資訊,則預設使用匿名登入
          f.login()
      except Exception as e:
          print(e)
          exit()
      print("***Logged in as 'anonymous'")
      
      
      # 3. 用戶端和伺服器進行各種檔案傳輸和資訊查詢操作
      try:
          # 更改目前目錄到指定目錄
          f.cwd(DIR)
      except Exception as e:
          print(e)
          exit()
      print("*** Changed dir to {0}".format(DIR))
      
      try:
          # 從FTP伺服器上下載下傳檔案
          # 第一個參數是ftp指令
          # 第二個參數是回調函數
          # 此函數的意思是,執行RETR指令,下載下傳檔案到本地後,運作回調函數
          f.retrbinary('RETR {0}'.format(FILE), open(FILE, 'wb').write)
      except Exception as e:
          print(e)
          exit()
      
      # 4. 用戶端從遠端FTP伺服器退出,結束傳輸
      f.quit()           

Mail程式設計

電子郵件的曆史

  • 起源
    • 1969 Leonard K. 教授發給同僚的 “LO”
    • 1971 美國國防部自主的阿帕網(Arpanet)的通訊機制
    • 通訊位址裡用@
    • 1987年中國的第一份電子郵件
    “Across the Great Wall we can reach every corner in the world”
  • 管理程式
    • Euroda使郵件普及
    • Netscape,outlook,forxmail後來居上
    • Hotmail使用浏覽器發送郵件
  • 參考資料

郵件工作流程

  • MUA(MailUserAgent) 郵件使用者代理
  • MTA(MailTransferAgent) 郵件傳輸代理
  • MDA(MailDeliveryAgent) 郵件投遞代理
  • [email protected],老師,北京海澱
  • [email protected],學生,上海江岸區
  • 流程
    1. MUA->MTA,郵件已經在伺服器上了
    2. qq MTA->........-> sina MTA, 郵件在新浪的伺服器上
    3. sina MTA-> sina MDA, 此時郵件已經在你的郵箱裡了
    4. sina MDA -> MUA(Foxmail/Outlook), 郵件下載下傳到本地電腦
  • 編寫程式
    • 發送: MUA->MTA with SMTP: SimpleMailTransferProtocal, 包含MTA->MTA
    • 接受: MDA->MUA with POP3 and IMAP: PostOfficeProtocal v3 and InternetMessageAccessProtocal v4
  • 準備工作
    • 注冊郵箱(以qq郵箱為例)
    • 第三方郵箱需要特殊設定,以qq郵箱為例
      • 進入設定中心
      • 取得授權碼
  • Python for mail
    • SMTP協定負責發送郵件
      • 使用email子產品建構郵件
        • 純文字郵件
        • 案例v07
          # 導入相應的包
          import smtplib
          from email.mime.text import MIMEText
          # MIMEText三個主要參數
          # 1. 郵件内容
          # 2. MIME子類型,在此案例我們用plain表示text類型
          # 3. 郵件編碼格式
          
          msg = MIMEText("Hello, i am xxxx", "plain", "utf-8")
          
          # 發送email位址
          from_addr = "[email protected]"
          # 此處密碼是經過申請設定後的授權碼
          from_pwd = "ajwvzqdlfigahiae"  # 授權碼
          
          # 收件人資訊
          # 此時使用qq郵箱
          to_addr = "[email protected]"
          
          # 輸入SMIP伺服器位址
          # 此處根據不同的郵件服務商有不同的值
          # 現在基本任何一家郵件服務商,如果采用第三方收發郵件,都需要開啟授權選項
          # 騰訊qq郵箱所用的SMTP位址是 smtp.qq.com
          
          smtp_srv = "smtp.qq.com"
          
          try:
              # 兩個參數
              # 第一個是伺服器位址,但一定是bytes格式,是以需要編碼
              # 第二個參數是伺服器的接受通路端口
              srv = smtplib.SMTP_SSL(smtp_srv.encode(), 465) # SMTP 協定預設端口25
              # 登入郵箱發送
              srv.login(from_addr, from_pwd)
              # 發送郵件
              # 三個參數
              # 1. 發送位址
              # 2. 接受位址,必須是list格式
              # 3. 發送内容,作為字元串發送
              srv.sendmail(from_addr, [to_addr], msg.as_string())
              srv.quit()
          except Exception as e:
              print(e)           
      • HTML格式郵件發送
        • 準備HTML代碼作為内容
        • 把郵件的subtype設為html
        • 發送
        • 案例v08
          from email.mime.text import MIMEText
          
          mail_content = """
                  <!DOCTYPE html>
                  <html lang="en">
                  <head>
                      <meta charset="UTF-8">
                      <title>Title</title>
                  </head>
                  <body>
                  
                  <h1> 這是一封HTML格式郵件</h1>
                  
                  </body>
                  </html>
                  """
          
          msg = MIMEText(mail_content, "html", "utf-8")
          
          # 建構發送者位址和登入資訊
          from_addr = "[email protected]"
          # 此處密碼是經過申請設定後的授權碼
          from_pwd = "ajwvzqdlfigahiae"  # 授權碼
          
          # 建構郵件接受者資訊
          to_addr = "[email protected]"
          
          smtp_srv = "smtp.qq.com"
          
          try:
              import smtplib
          
              srv = smtplib.SMTP_SSL(smtp_srv.encode(), 465)
          
              srv.login(from_addr, from_pwd)
              srv.sendmail(from_addr, [to_addr], msg.as_string())
              srv.quit()
          
          except Exception as e:
              print(e)           
      • 發送帶附件的郵件
        • 可以把郵件看作是一個文本郵件和一個附件的合體
        • 一封郵件如果涉及多個部分,需要使用MIMEMultipart格式建構
        • 添加一個MIMEText正文
        • 添加一個MIMEBase或者MIMEText作為附件
        • 案例v09
          from email.mime.text import MIMEText # 建構附件使用
          from email.mime.multipart import MIMEBase, MIMEMultipart # 建構基礎郵件使用
          
          
          mail_mul = MIMEMultipart()
          # 建構郵件正文
          mail_text = MIMEText("Hello, i am xxxx", "plain", "utf-8")
          # 把建構好的郵件正文附加入郵件中
          mail_mul.attach(mail_text)
          
          # 建構附加
          # 建構附件,需要從本地讀入附件
          # 打開一個本地檔案
          # 以rb格式打開
          with open("02.html", "rb") as f:
              s = f.read()
              # 設定附件的MIME和檔案名
              m = MIMEText(s, 'base64', "utf-8")
              m["Content-Type"] = "application/octet-stream"
              # 需要注意
              # 1. attachment後分号為英文狀态
              # 2. filename 後面需要用引号包裹,注意與外面引号錯開
              m["Content-Disposition"] = "attachment; filename='02.html'"
              # 添加到MIMEMultipart
              mail_mul.attach(m)
          
          
          # 發送email位址
          from_addr = "[email protected]"
          # 此處密碼是經過申請設定後的授權碼
          from_pwd = "ajwvzqdlfigahiae"  # 授權碼
          
          # 收件人資訊
          # 此時使用qq郵箱
          to_addr = "[email protected]"
          
          # 輸入SMIP伺服器位址
          # 此處根據不同的郵件服務商有不同的值
          # 現在基本任何一家郵件服務商,如果采用第三方收發郵件,都需要開啟授權選項
          # 騰訊qq郵箱所用的SMTP位址是 smtp.qq.com
          
          smtp_srv = "smtp.qq.com"
          
          try:
              import  smtplib
              # 兩個參數
              # 第一個是伺服器位址,但一定是bytes格式,是以需要編碼
              # 第二個參數是伺服器的接受通路端口
              srv = smtplib.SMTP_SSL(smtp_srv.encode(), 465) # SMTP 協定預設端口25
              # 登入郵箱發送
              srv.login(from_addr, from_pwd)
              # 發送郵件
              # 三個參數
              # 1. 發送位址
              # 2. 接受位址,必須是list格式
              # 3. 發送内容,作為字元串發送
              srv.sendmail(from_addr, [to_addr], mail_mul.as_string())
              srv.quit()
          except Exception as e:
              print(e)           
      • 添加郵件頭,抄送等資訊
        • mail["From"] 表示發送者資訊,包括姓名和郵件
        • mail["To"] 表示接受者資訊,包括姓名和郵件位址
        • mail["Subject"] 表示摘要或者主題資訊
        • 案例v10
          from email.mime.text import MIMEText
          from email.header import Header
          
          msg = MIMEText("Hello world", "plain", "utf-8")
          # 用utf-8編碼是因為很可能内容包含非英文字元
          header_from = Header("從A發送出去的<[email protected]>", "utf-8")
          msg['From'] = header_from
          
          # 填寫接受者資訊
          header_to = Header("去往B<[email protected]>", "utf-8")
          msg['To'] = header_to
          
          header_sub = Header("這是主題", "utf-8")
          msg['Subject'] = header_sub
          
          # 建構發送者位址和登入資訊
          from_addr = "[email protected]"
          from_pwd = "ajwvzqdlfigahiae"
          
          # 建構郵件接受者資訊
          to_addr = "[email protected]"
          
          smtp_srv = "smtp.qq.com"
          
          try:
              import smtplib
          
              srv = smtplib.SMTP_SSL(smtp_srv.encode(), 465)
          
              srv.login(from_addr, from_pwd)
              srv.sendmail(from_addr, [to_addr], msg.as_string())
              srv.quit()
          
          except Exception as e:
              print(e)           
      • 同時支援html和text格式
        • 建構一個MIMEMultipart格式郵件
        • MIMEMultipartd額subtype設定成alternative格式
        • 添加HTML和Text郵件
        • 案例v11
          from email.mime.text import  MIMEText
          from email.mime.multipart import  MIMEMultipart
          
          # 建構一個MIMEMultipart郵件
          msg = MIMEMultipart("alternative")
          
          # 建構一個HTML郵件内容
          html_content = """
                      <!DOCTYPE html>
                      <html lang="en">
                      <head>
                          <meta charset="UTF-8">
                          <title>Title</title>
                      </head>
                      <body>
                      <h1> 這是一封HTML格式郵件</h1>
                      </body>
                      </html>
                  """
          #
          msg_html = MIMEText(html_content, "html", "utf-8")
          msg.attach(msg_html)
          
          
          msg_text = MIMEText("just text content", "plain", "utf-8")
          msg.attach(msg_text)
          
          
          
          # 發送email位址
          from_addr = "[email protected]"
          from_pwd = "ajwvzqdlfigahiae"
          
          # 收件人資訊:
          # 此處使用我的qq郵箱
          to_addr = "[email protected]"
          
          # 輸入SMTP伺服器位址:
          # 此位址根據每隔郵件服務商有不同的值,這個是發信郵件服務商的smtp位址
          # 我用的是qq郵箱發送,此處應該填寫騰訊qq郵箱的smtp值,即smtp.163.com,
          # 需要開啟授權碼,
          smtp_srv = "smtp.qq.com"
          
          try:
              import smtplib
              # 加密傳輸
              #server = smtplib.SMTP_SSL(smtp_srv.encode(), 465) # SMTP協定預設端口是25
              # qq郵箱要求使用 TLS加密傳輸
              server = smtplib.SMTP(smtp_srv.encode(), 25) # SMTP協定預設端口是25
              server.starttls()
              # 設定調試級别
              # 通過設定調試等級,可以清楚的看到發送郵件的互動步驟
              server.set_debuglevel(1)
              # 登入發送郵箱
              server.login(from_addr, from_pwd)
              server.sendmail(from_addr, [to_addr], msg.as_string())
              server.quit()
          except Exception as e:
              print(e)           
      • 使用smtplib子產品發送郵件
    • POP3協定負責接受郵件
      • 本質上是MDA到MUA的一個過程
      • 從 MDA 下載下傳下來的是一個完整的郵件結構體,需要解析才能得到每個具體可讀的内容
      • 步驟:
        1. 用poplib下載下傳郵件結構體原始内容
          1. 準備相應的内容(郵件位址,密碼,POP3執行個體)
          2. 身份認證
          3. 一般會先得到郵箱内郵件的整體清單
          4. 根據相應序号,得到某一封信的資料流
          5. 利用解析函數進行解析出相應的郵件結構體
        2. 用email解析郵件的具體内容
      • 案例v12
        # 導入相關包
        # poplib負責從MDA到MUA下載下傳
        import poplib
        
        # 以下包負責相關郵件結構解析
        from email.parser import Parser
        from email.header import decode_header
        from email.utils import parseaddr
        
        # 得到郵件的原始内容
        # 這個過程主要負責從MDA到MUA的下載下傳并使用Parse粗略解析
        def getMsg():
            # 準備相應的資訊
            email = "[email protected]"
            # 郵箱的授權碼
            pwd = "ajwvzqdlfigahiae"
        
            # pop3伺服器位址
            pop3_srv = "pop.qq.com" # 端口995
        
            # ssl代表是安全通道
            srv = poplib.POP3_SSL(pop3_srv)
        
            # user代表email位址
            srv.user(email)
            # pass_代表密碼
            srv.pass_(pwd)
        
            # 以下操作根據具體業務具體使用
            # stat傳回郵件數量和占用空間
            # 注意stat傳回一個tuple格式
            msgs, counts = srv.stat()
            print("Messages: {0}, Size: {1}".format(msgs, counts))
        
            # list傳回所有郵件編号清單
            # mails是所有郵件編号清單
            rsp, mails, octets = srv.list()
            # 可以檢視傳回的mails清單類似[b'1 82923', b'2 2184', ...]
            print(mails)
        
        
            # 擷取最新一封郵件,注意,郵件索引号是從1開始, 最新代表索引号最高
            index = len(mails)
            # retr負責傳回一個具體索引号的一封信的内容,此内容不具有可讀性
            # lines 存儲郵件的最原始文本的每一行
            rsp, lines, octets = srv.retr(index)
        
            # 獲得整個郵件的原始文本
            msg_count = b'\r\n'.join(lines).decode("utf-8")
            # 解析出郵件整個結構體
            # 參數是解碼後的郵件整體
            msg = Parser().parsestr(msg_count)
        
            #關閉連結
            srv.quit()
        
            return msg
        
        
        # 詳細解析得到的郵件内容
        # msg代表是郵件的原始内容
        # idnent代表的是郵件嵌套的層級
        def parseMsg(msg, indent=0):
            '''
            1. 郵件完全可能是有嵌套格式
            2. 郵件隻有一個From,To,Subject之類的資訊
            :param msg:
            :param indent: 描述郵件裡面有幾個郵件MIMEXXX類型的内容,展示的時候進行相應縮進
            :return:
            '''
        
            # 想辦法提取出頭部資訊
            # 隻有在第一層的郵件中才會有相關内容,
            # 此内容隻有一個
            if indent == 0:
                for header in ['From', "To", 'Subject']:
                    # 使用get可以避免如果沒有相關關鍵字報錯的可能性
                    # 如果沒有 關鍵字”From“, 我們使用 msg["From"]會報錯
                    value = msg.get(header, '')
                    if value:
                        # Subject中的内容直接解碼就可以,他是字元串類型
                        if header == 'Subject':
                            value = decodeStr(value)
                        # 如果是From和To字段,則内容大概是 "我的郵箱<[email protected]>“這種格式
                        else:
                            hdr, addr = parseaddr(value)
                            name = decodeStr(hdr)
                            # 最終傳回形如  "我的郵箱<[email protected]>的格式
                            value = "{0}<{1}>".format(name, addr)
                    print("{0}, {1}: {2}".format(indent, header, value))
        
            # 下面代碼關注郵件内容本身
            # 郵件内容中,有可能是multipart類型,也有可能是普通郵件類型
            # 下面的解析使用遞歸方式
            if (msg.is_multipart()):
                # 如果是multipart類型,則調用遞歸解析
        
                # 得到多部分郵件的一個基礎郵件部分
                parts = msg.get_payload()
                # enumerate 函數是内置函數
                # 作用是将一個清單,此處是parts,生成一個有索引和parts原内容構成的新的清單
                # 例如 enumerate(['a', 'b', 'c']) 結果是:  [(1,'a'), (2, 'b'), (3, 'c')]
                for n,part in enumerate(parts):
                    # 一個字元串乘以一個數字的意思是對這個字元串進行n倍擴充
                    # 比如 ”aa" * 2 -> "aaaa"
                    print("{0}spart: {1}".format(' '*indent, n))
                    parseMsg(part, indent+1)
            else: # 基礎類型
                # get_content_type是系統提供函數,得到内容類型
                content_type = msg.get_content_type()
                # text/plain 或者 text/html是固定值
                if content_type == 'text/plain' or content_type == 'text/html':
                    content = msg.get_payload(decode=True)
                    charset = guessCharset(msg)
                    if charset:
                        content = content.decode(charset)
                    print("{0}Text: {1}".format(indent, content))
        
                else: #不是文本内容,則應該是附件
                    print('{0}Attachment: {1}'.format(indent, content_type))
        
        def decodeStr(s):
            '''
            s代表一封郵件中From,To,Subject中的任一項
            對s進行解碼,解碼是編碼的逆過程
            :param s:
            :return:
            '''
            value, charset = decode_header(s)[0]
            # charset完全可能為空
            if charset:
                # 如果指定編碼,則用指定編碼格式進行解碼
                value = value.decode(charset)
        
            return value
        
        def guessCharset(msg):
            '''
            猜測郵件的編碼格式
            :param msg:
            :return:
            '''
            # 調用現成的函數
            charset = msg.get_charset()
        
            if charset is None:
                # 找到内容類型,并轉換成小寫
                content_type = msg.get("Content-Type", "").lower()
                pos = content_type.find("charset=")
                if pos >= 0:
                    # 如果包含chraset,則内容形如 charset=xxxx
                    charset = content_type[pos+8:].strip()
        
            return  charset
        
        
        
        
        if __name__ == "__main__":
            # 得到郵件的原始内容
            msg = getMsg()
            print(msg)
            # 精确解析郵件内容
        parseMsg(msg, 0)