【python3的進階之路二】因特網客戶端編程
一、文件傳輸
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的進階之路二】因特網客戶端編程