1. 程式人生 > >【python3的進階之路二】因特網客戶端編程

【python3的進階之路二】因特網客戶端編程

網絡流 message world! 3.6 login 三元組 移除 元組 類對象

一、文件傳輸

1.1 文件傳輸因特網協議

最流行的協議包括文件傳輸協議(FTP)、UNIX到UNIX復制協議(UUCP)、用於Web的超文本傳輸協議(HTTP)。另外,還有(UNIX下的)遠程文件復制命令rcp(以及更安全、靈活的scp和rsync)。
HTTP主要用於基於Web的文件下載以及訪問Web服務,一般客戶端無須登錄就可以訪問服務器上的文件和服務。大部分HTTP文件傳輸請求都用於獲取網頁(即將網頁文件下載到本地)。
而scp和rsync需要用戶登錄到服務器主機。在傳輸文件之前必須驗證客戶端的身份,否則不能上傳或下載文件。FTP與scp/rsync相同,它也可以上傳或下載文件,並采用了UNIX的多用戶概念,用戶需要輸入有效的用戶名和密碼。但FTP也允許匿名登錄。

1.2 文件傳輸協議

文件傳輸協議(File Transfer Protocol,FTP)主要用於匿名下載公共文件,也可以用於在兩臺計算機之間傳輸文件,特別實在使用Windows進行工作,而文件存儲系統使用UNIX的情況下。
FTP要求輸入用戶名和密碼才能訪問遠程FTP服務器,但也允許沒有賬號的用戶匿名登錄。不過管理員要先設置FTP服務器以允許匿名用戶登錄。這時,匿名用戶的用戶名是“anonymous”,密碼一般是用戶的電子郵件地址。與向特定的登錄用戶傳輸文件不通過,這相當於公開某些目錄讓大家訪問。但與登錄用戶相比,匿名用戶只能使用有限的幾個FTP明令。

技術分享圖片

由上圖展示的這個協議,其工作流程如下:
1、客戶端連接遠程主機上的FTP服務器
2、客戶端輸入用戶名和密碼(或“anonymous”和電子郵件地址)
3、客戶端進行各種文件傳輸和信息查詢操作
4、客戶端從遠程FTP服務器退出,結束傳輸
有時,由於網絡兩邊計算機的崩潰或網絡的問題,會導致整個傳輸在完成之前中斷。如果客戶端超過15分鐘(900秒)還沒有響應,FTP連接就會超時並中斷。
在底層,FTP只使用TCP,而不是UDP。另外,可以將FTP看作客戶端/服務器編程中的特殊情況。因為這裏的客戶端和服務器都使用倆個套接字來通信:一個是控制和命令端口(21號端口),另一個是數據端口(有時是20號端口)。
這裏說的“有時”是因為FTP由倆種模式:主動和被動。只有主動模式下服務器才使用數據端口。在服務器把20號端口設置為數據端口後,它“主動”連接客戶端的數據端口。而在被動模式下,服務器只是告訴客戶端隨機的數據端口號,客戶端必須主動建立數據連接。在這種模式下,FTP服務器在建立數據連接時是“被動”的。最後,現在已經有了一種擴展的被動模式來支持第6版本的因特網協議(IPv6)地址——詳見RFC 2428

1.3 Python和FTP

回顧流程:
1、連接到服務器
2、登錄
3、發出服務請求(希望得到響應)
4、退出
在使用Python的FTP支持時,所需要做的只是導入ftplib模塊,並實例化一個ftplib.FTP類對象。所有的FTP操作(如登錄、傳輸文件和註銷等)都要使用這個對象完成。

from ftplib import FTP

f = FTP(some.ftp.server)
f.login(anonymous, [email protected])
.
.
.
f.quit()

1.4 ftplib.FTP類的方法

方法 描述
login(self, user, password, acct) 登錄到FTP服務器,所有參數都是可選的
pwd(self) 獲取當前工作目錄
cwd(self, dirname) 在服務器上設置當前目錄
dir(self, args) 生成LIST命令返回的目錄列表,將其打印到標準輸出。可選參數是要列出的目錄(默認為當前服務器目錄)。可以使用多個參數將非標準選項傳遞給LIST命令。如果最後一個參數是一個函數,它將被用作回調函數retrlines(); 默認打印到 sys.stdout。此方法返回None。
nlst(self, args) 與dir()類似,但返回一個文件名列表,而不是顯示這些文件名
retrlines(self, cmd, callback) 給定FTP命令(如“RETR filename”),用於下載文件。可選的回調函數cb用於處理文件的每一行
retribinary(self, cmd, fp, blocksize, callback, rest) 與retrlines類似,只是這個指令處理二進制文件。回調函數用於處理每一塊(塊大小默認8KB)
storlines(self, cmd, fp, callback) 給定FTP命令(如“STOR filename”),用來上傳文本文件。要給定一個文件對象f
storbinary(self, cmd, fp, blocksize, callback, rest) 與storlines()類似,只是這個指令處理二進制文件。要給定一個文件對象f,上傳塊大小bs默認為8KB
rename(self, fromname, toname) 文件重命名
delete(self, filename) 刪除名為dirname的遠程文件。如果成功,則返回響應的文本,否則會引發error_perm權限錯誤或 error_reply其他錯誤。
mkd(self, dirname) 在服務器上創建一個新目錄
rmd(self, dirname) 刪除服務器上名為dirname的目錄
quit(self) 關閉連接並退出
close(self) 單方面關閉連接

FTP對象更多信息:https://docs.python.org/3/library/ftplib.html

1.5 客戶端FTP程序示例

import ftplib
import os
import socket

HOST = ftp.sjtu.edu.cn   # 不可用ftp://ftp.sjtu.edu.cn/
DIRN = logs/rsync
FILE = fedora-epel.trace

def main():
    try:
        f = ftplib.FTP(HOST)
    except (socket.error, socket.gaierror) as e:
        print(ERROR:cannot reach %s % HOST)
        return
    print(*** Connected to host %s % HOST)

    try:
        f.login()
    except ftplib.error_perm:
        print(ERROR: cannot login anoymously)
        f.quit()
        return
    print("*** Logged in as ‘anonymous‘")

    try:
        f.cwd(DIRN)
    except ftplib.error_perm:
        print(ERROR: cannot CD to %s % DIRN)
        f.quit()
        return
    print(*** Changed to %s folder % f.pwd())

    try:
        f.retrbinary(RETR %s % FILE, open(FILE, r))
    except ftplib.error_perm:
        print(ERROR: cannot read file %s % FILE)
        os.unlink(FILE)
    else:
        print(*** Downloaded %s to CWD % FILE)
    f.quit()

if __name__ == __main__:
    main()

說明:
第一到七行

代碼前幾行導入要用的模塊(主要用於抓取異常對象),並設置一些常量

第九行到四十一行

創建一個FTP對象,嘗試連接到FTP服務器(第十到十五行),然後返回。如果發生任何錯誤九退出。接著嘗試用“anonymous”登錄,如果不行九結束(第十七到二十三行)。下一步就是轉到發布目錄(第二十五到三十一行),最後下載文件(第三十三掃四十一行)。
在第三十四行,向retrbinary()傳遞一個回調函數,沒接收到一塊二進制數據的時候都會調用這個回調函數。這個函數就是船艦文件的本地版本時需要用到的文件對象的write()方法。傳輸結束時,python解釋器會自動關閉這個文件對象,因此不會丟失數據。雖然方便,但要盡量做到在資源不再被使用的時候九立即釋放,而不是依賴其他代碼來完成釋放操作。這裏應該把開放的文件對象保存到一個變量(如變量loc),然後把loc.write傳給ftp.retrbinary()。
完成傳輸後,調用loc.close()。如果由於某些原因無法保存文件,則移除空的文件夾來避免弄亂文件系統(第三十七行)。在os.unlink(FILE)倆側添加一些錯誤檢查代碼,以應對文件不存在的情況。

第四十二到四十三行

運行獨立腳本的慣用方法

1.6 FTP的其他內容

Python同時支持主動和被動模式。註意,在Python2.0及以前版本中,被動模式默認時關閉的;在Python2.1及以後版本中,默認時打開的。
以下是一些典型的FTP客戶端類型:

  • 命令行客戶端程序: 使用一些FTP客戶端程序(如/bin/ftp或NcFTP)進行FTP傳輸,用戶可以在命令中交互式執行FTP傳輸
  • GUI客戶端程序: 與命令客戶端程序相似,但它是一個GUI程序,如WS_FTP、Filezilla、CuteFTP、Fetch、SmartFTP
  • Web瀏覽器: 除了使用HTTP之外,大多數Web瀏覽器(也稱為客戶端)可以進行FTP傳輸。URL/URI的第一部分就用來表示所使用的協議,如“http://blahblah”。這就告訴瀏覽器要使用HTTP作為指定網站傳輸數據的協議。通過修改協議部分,就可以發送使用FTP的請求,如“ftp://blahblah”,這與使用HTTP的網頁URL很像(“blahblah”可以展開為“host/path?attributes”)。如果要登錄,用戶可以把登錄信息(以明文方式)放在URL裏,如“ftp://user:password@host/path?attr1=vall&attr2=val2…”
  • 自定義應用程序: 自己編寫的用於FTP文件傳輸的程序。這些式用於特殊目的的應用程序,一般這種程序不允許用戶與服務器交互

二、網絡新聞

2.1 Usenet與新聞組

Usenet新聞系統式一個全球存檔的“電子公告板”。各個主題的新聞組一應俱全,新聞組可以面向全球,也可以只面向某個特定區域。老的Usenet使用UUCP作為其網絡傳輸機制,在20世紀80年代中期出現了另一個網絡協議TCP/IP,之後大部分網絡流量轉向使用TCP/IP。

2.2 網絡新聞傳輸協議

作為客戶端/服務器架構的另一個例子,NNTP與FTP的操作方式相似,但更簡單。FTP中,登錄、傳輸數據和控制需要使用不同的端口,而NNTP只使用一個標準端口119來通信。用戶向服務器發送一個請求,服務器就做出相應的響應。

技術分享圖片

2.3 Python和NNTP

NNTP協議流程:
1、連接到服務器
2、登錄(根據需要)
3、發出服務請求
4、退出

from nntplib import NNTP

n = NNTP(your.nntp.server)
r,c,f,l,g = n.group(comp.lang.python)
.
n.quit()

登錄後需要調用group()方法來選擇一個感興趣的新聞組。該方法返回服務器的回復、文章的數量、第一篇和最後一篇文章的ID、新聞組的名稱。

2.4 nntplib.NNTP類方法

方法 描述
group(self, name) 選擇一個組的名字,返回一個元組(rsp,ct,fst,lst,group),分別表示服務器響應信息、文章數量、第一個和最後一個文章的編號、組名,所有數據都是字符串。(返回的group與傳進去的name應該式相同的)
xhdr(self, hdr, str, file) 發送XHDR命令。該命令沒有在RFC中定義,但是是一個常見的擴展。的報頭參數是一個報頭的關鍵字,例如‘subject‘。該字符串參數應具有的形式‘first-last‘,其中第一和最後一個是第一個和最後的文章編號,以搜索。返回一對(response, list),其中list是對的列表(id, text),其中id是商品編號(作為字符串),text是該文章請求的標題的文本。如果提供了文件參數,則XHDR命令的輸出將存儲在文件中。如果文件是一個字符串,然後該方法將打開一個名稱為文件對象,寫入並關閉它。如果file是一個文件對象,那麽它將開始調用write()它來存儲命令輸出的行。如果提供了文件,則返回的列表是一個空列表。
body(self,message_spec, file) 發送一個BODY命令,其中id的含義與以前相同stat()。如果提供了文件參數,則主體存儲在文件中。如果file是一個字符串,那麽該方法將打開一個具有該名稱的文件對象,寫入並關閉它。如果文件是一個文件對象,那麽它將開始調用write()它來存儲正文的行。返回head()。如果提供了文件,則返回的列表是一個空列表。
head(self,message_spec, file) 發送一個HEAD命令,其中id的含義與以前相同stat()。返回一個元組(response, number, id, list),其中前三個元素與for相同stat(),list是文章標題列表(一個沒有解釋的行列表,沒有尾隨換行符)。
article(self,message_spec, file) 發送一個ARTICLE命令,其中id的含義與以前相同stat()。返回head()。
stat(self,message_spec) 發送一個STAT命令,其中id是消息ID(用‘<‘and 括起來‘>‘)或商品編號(作為字符串)。返回一個triple (response, number, id),其中number是商品編號(作為字符串),id是消息ID(用‘<‘和括起來‘>‘)。
next(self) 把文章指針移到下一篇文章,返回與stat()相似的元組
last(self) 把文章指針移到最後一篇文章,返回與stat()相似的元組
post(self,data) 使用該POST命令發布文章。的文件參數是其使用其讀直至EOF一個打開的文件對象readline()的方法。它應該是一篇格式良好的新聞文章,包括必需的標題。該post()方法自動轉義以.。開頭的行。
quit(self) 關閉連接並退出

2.5 客戶端程序NNTP示例

import nntplib
import socket

HOST = your.nntp.server
GRNM = comp.lang.python
USER = wesley
PASS = youllNeverGuess

def main():
    try:
        n = nntplib.NNTP(HOST)
        #, user = USER, password = PASS)
    except socket.gaierror as e:
        print(ERROR: cannot reach host %s % HOST)
        print(%s % eval(str(e))[1])
        return
    except nntplib.NNTPermanentError as e:
        print(ERROR:access denied on %s % HOST)
        print(%s % str(e))
        return
    print(*** Connected to host %s % HOST)

    try:
        rsp, ct, fst, lst, grp = n.group(GRNM)
    except nntplib.NNTPTemporaryError as ee:
        print(ERROR: cannot load group %s % GRNM)
        print(%s % str(e))
        print(Server may require authentication)
        print(Uncomment/edit login line above)
        n.quit()
        return
    except nntplib.NNTPTemporaryError as ee:
        print(ERROR: group %s unavailable % GRNM)
        print(%s % str(e))
        n.quit()
        return
    print(*** Found newsgroup %s % GRNM)

    rng = %s-%s % (lst, lst)
    rsp, frm = n.xhdr(from, rng)
    rsp, sub = n.xhdr(subject,rng)
    rsp, dat = n.xhdr(date, rng)
    print(‘‘‘*** Found last article (#%s)
    From:%s
    Subject:%s
    Date:%s
    ‘‘‘ % (lst, frm[0][1], sub[0][1], dat[0][1]))

    rsp, anum, mid, data = n.body(lst)
    displayFirst20(data)
    n.quit()

def displayFirst20(data):
    print(*** First (<=20) meaningful lines:\n)
    count = 0
    lines = (line.rstrip() for line in data)
    lastBlank = True
    for line in lines:
        if line:
            lower = line.lower()
            if(lower.startswith(>) and not             lower.startswith(>>>)) or             lower.startswith(|) or             lower.startswith(in article) or             lower.endswith(writes:) or             lower.endswith(wrote:):
                continue
        if not lastBlank or (lastBlank and line):
            print(%s % line)
            if line:
                count += 1
                lastBlank = False
            else:
                lastBlank = True
        if count == 20:
            break

if __name__ == __main__:
    main()

說明:
第九行到三十七行:
嘗試連接NNTP主機服務器,如果失敗就退出。接著嘗試讀取指定的新聞組。同樣,如果新聞組不存在,或服務器沒有保存這個新聞組,或需要身份驗證,就退出
第三十九到五十一行:
這一部分讀取並顯示一些頭消息(第三十九到第四十七行)。程序會讀取作者、主題、日期這些數據並顯示給用戶。每次調用xhdr()方法時,都要給定想要提取消息頭的文章的範圍。因為這裏只想獲取一條消息,所以範圍就是“X-X”,其中X是最新一條消息的號碼。xhdr()方法返回一個長度為2的元組,其中包含了服務器的響應(rsp)和指定範圍的消息頭的列表。
最後一部分是下載文章的內容(第四十九到五十一行)。先調用body()方法,然後至多顯示前20個有意義的行,最後從服務器註銷,完成處理。
第五十三到七十六行:
該函數接受文章的一些內容,並做一些預處理,如把計數器清0,創建一個生成器表達式對文章內容的所有行做一些處理,然後“假裝”剛碰到並顯示了一行空行(第五十五到五十七行)。
由於不想顯示引用的文本和引用文本指示行,而在第六十到六十七使用了一個大if語句。只有在當前行不是空行時,才做這個檢查。檢查的時候,會把字符串轉成小寫,這樣就能做到比較的時候不區分大小寫。
第六十八行的if語句表示只有在上一行不為空,或者上一行為空但當前不為空的時候才顯示。

三、電子郵件

3.1 電子郵件電子組件和協議

電子郵件系統的重要組件是消息傳輸代理(MTA).這是在郵件交換主機上運行的服務進程,它負責郵件的路由、隊列處理和發送工作。MTA就是郵件從發送主機到接收主機所要經過的主機和“跳板”,也稱為“消息傳輸”的“代理”。MTA要知道的倆件事:
1、如何找到消息應該到達的下一個MTA
2、如何與另一臺MTA通信

3.2 Python和SMTP

需要一個smtplib模塊和一個需要實例化的smtplib.SMTP類。流程:
1、連接到服務器
2、登錄(根據需要)
3、發出服務請求
4、退出

from smtplib import SMTP

n = SMTP(smtp.yourdomain.com)
.
.
.
n.quit()

3.4 smtplib.SMTP類方法

方法 描述
sendmail(self, from_addr, to_addr, msg, mail_options, rcpt_options) 將msg從from_addr發送至to_addr,還可以選擇性地設置ESMTP郵件(mail_options)和收件人(rcpt_options)選項
ehlo(self, name)或helo(self, name) 使用ehlo指令像ESMTP(SMTP擴展)確認你的身份/使用helo指令向SMTP服務器確認你的身份
starttls(self, keyfile, certfile, context) 讓服務器啟用TLS模式。如果給定了keyfile或certfile,則它們用來創建安全套接字
set_debuglevel(self,debuglevel) 為服務器通信設置調試級別
quit(self) 關閉連接並退出
login(self, user, password, initial_response_ok) 使用用戶名和密碼登錄SMTP服務器

3.5 Python和POP3

需要一個poplib模塊和一個需要實例化的poplib.POP3類。流程:
1、連接到服務器
2、登錄(根據需要)
3、發出服務請求
4、退出

技術分享圖片
from poplib import POP3

p = POP3(pop.python.is.cool)
p.user(...)
p.pass_(...)
...
p.quit()

3.6 poplib.POP3類方法

方法 描述
user(self, user) 向服務器發送登錄名,並顯示服務器的響應,表示服務器正在等待輸入該用戶的密碼
pass_(self, pswd) 在用戶使用user()登錄後,發送password。如果登錄失敗,則拋出異常
stat(self) 返回郵件的狀態,即一個長度為2的元組,分別表示消息的數量和消息的總大小
list(self, which) stat()的擴展,從服務器返回以三元組表示的整個消息列表,分別表示為服務器的響應、消息列表、返回消息的大小。
retr(self, which) 從服務器中得到消息的郵件,並設置其“已讀”標誌。返回一個長度為3的元組,分別為服務器的響應、消息的郵件的所有行、消息的字節數
dele(self, which) 將標記的消息刪除,大多數服務器在調用quit()後執行刪除操作
quit(self) 註銷、提交修改(如處理“已讀”和“刪除”標記等)、解鎖郵箱、終止連接,然後退出

3.7 客戶端程序SMTP和POP3示例

from smtplib import SMTP
from poplib import POP3
from time import sleep
from email.parser import Parser
from email.mime.text import MIMEText


SMTPSVR = smtp.exmail.qq.com   # SMTP服務器
POPSVR = pop.exmail.qq.com     # POP服務器
mail_user = "username"  # 用戶名
mail_pass = "password"  # 密碼

sender = sender mail       # 發件人郵箱
receivers = [receiver mail]    # 接收人郵箱

content = Hello World!
title = test msg  # 郵件主題
message = MIMEText(content, plain, utf-8)  # 內容, 格式, 編碼
message[From] = "{}".format(sender)
message[To] = ",".join(receivers)
message[Subject] = title

sendSvr = SMTP(SMTPSVR)
sendSvr.login(mail_user, mail_pass)
errs = sendSvr.sendmail(sender, receivers, message.as_string()) # 發送郵件,sendmail()的第三個參數是電子郵件消息本身
sendSvr.quit()
assert len(errs) == 0, errs
sleep(10)

recvSvr = POP3(POPSVR)
recvSvr.user(mail_user)
recvSvr.pass_(mail_pass)
rsp, msg, siz = recvSvr.retr(recvSvr.stat()[0])  # stat()方法得到可用消息列表,通過[0]符號選中第一條消息;retr()下載這條消息
msg_content = b\r\n.join(msg).decode(utf-8)
recvBody = Parser().parsestr(msg_content)

3.8 Python和IMAP4

from imaplib import IMAP4

s = IMAP4(imap.python.is.cool)
s.login(...)
...
s.close()
s.logout()

3.9 imaplib.IMAP4類的常用方法

方法 描述
close(self) 關閉當前郵箱。如果訪問權限不是只讀,則本地刪除大的郵件在服務器端也會被丟棄
login(self, user, password) 使用指定的用戶名和密碼登錄
logout(self) 從服務器註銷
fetch(self, message_set, message_parts) 獲取之前由message_set設置的電子郵件消息狀態(或使用message_parts獲取部分狀態信息)
noop(self) ping服務器,但不產生任行為
search(self, charset, *criteria) 查詢郵箱中至少匹配一塊criteria的消息。如果charset為False,則默認使用US-ASCII
select(self, mailbox, readonly) 選擇一個文件夾(默認是INBOX),如果是只讀,則不允許用戶修改其中的內容

【python3的進階之路二】因特網客戶端編程