python学习笔记(十 三)、网络编程
最近心情有點兒浮躁,難以靜下心來
?
Python提供了強大的網絡編程支持,很多庫實現了常見的網絡協議以及基于這些協議的抽象層,讓你能夠專注于程序的邏輯,而無需關心通過線路來傳輸比特的問題。
1 幾個網絡模塊
1.1 模塊socket
網絡編程中的一個基本組件是套接字(socket)。套接字基本上是一個信息通道,兩端各有一個程序。這些程序可能位于(通過網絡相連接的)不同的計算機上,通過套接字向對方發送消息。在Python中,大多數網絡編程都隱藏了模塊socket的基本工作原理,不與套接字直接交互。
套接字分為兩類:服務端套接字和客戶端套接字。創建服務端套接字后,讓它等待連接請求的到來。這樣,它將在某個網絡地址(由IP地址和端口號組成)處監聽,知道客戶端套接字建立連接,還必須處理多個連接;而客戶端套接字只需連接,完成任務后再斷開連接即可。
套接字是模塊socket中socket類的實例。實例化套接字時最多可指定三個參數:一個地址族(默認為socket.AF_INET);是流套接字(socket.SOCK_STREAM,默認設置)還是數據報套接字(socket.SOCK_DGRAM);協議(使用默認值0就好)。
服務器套接字先調用方法bind,在調研方法listen來監聽特定的地址。然后,客戶端套接字通過調用方法connect并提供bind時指定的地址來連接服務端。這里的地址是一個格式為(host, port)的元祖,其中host是主機名,port是端口號。方法listen接收一個參數——代辦任務清單的長度(即最多可有多少個連接在隊列中等待接納,到達這個數量后將開始拒絕連接)。
服務端套接字開始監聽后,就可接收客戶端連接,使用方法accept來等待連接。這個方法將阻斷(等待)到客戶端連接到來為止(有點類似與yield關鍵字),然后返回一個格式為(client, address)的元組,其中client為客戶端套接字,而address為地址。服務端能以其認為合適的方式處理客戶端連接,然后再次調用accept以等待新連接到來。
為傳輸數據,套接字提供了兩個方法:send-發送和recv-接收(表示receive),這兩個方法都是接收或發送字節流數據。
簡單的服務器:
import socket#創建套接字 s = socket.socket()#獲取主機名 host = socket.gethostname() #端口號 port = 8080 address = (host, port) #創建連接 s.bind(address)#監聽:最大連接數為5 s.listen(5) while True:#等待連接client, addr = s.accept()#接收消息str = bytes.decode(client.reve(1024))#發送消息client.send(str.encode('我是服務端!','utf-8'))簡單的客服端:
import socket s = socket.socket()#連接服務端地址 host = socket.gethostname() port = 8080 address = (host, port) #連接 s.connect(address) #接收消息 str = bytes.decode(s.reve(1024)) #發送消息 s.sent(str.encode('我是客戶端!','utf-8'))1.2 模塊urllib和urllib2
模塊urllib和urllib2,由名字可知,它們讓你能夠通過網絡訪問文件,就像這些文件位于你的計算機中一樣。只需一個簡單的函數調用,就幾乎可將統一資源定位符(URL)可指向的任何動作作為程序的輸入。這兩個模塊一般用于下載網頁、從中提取信息自動生成研究報告等。對于簡單的操作,urllib綽綽有余,如果要實現HTTP身份驗證或Cookie,或編寫擴展來處理自己的協議,urllib2可能是更好的選擇。
1.2.1 打開遠程文件
使用模塊urllib.request中的函數urlopen來打開遠程文件,只能進行讀取操作。如:
from urllib.request import urlopen
page = urlopen('www.baidu.com')
如果連接到網絡,變量page將包含一個類似于文件的對象,這個對象與你所連接的地址相關聯。urlopen返回類似于文件的對象支持方法clost、read、readline和readlines,還支持迭代等。
1.2.2 獲取遠程文件
函數urlopen返回一個類似于文件的對象,可從中讀取數據。如果要讓urllib替你下載文件,并將其副本存儲在本地,可使用urllib.request中的函數urlretrieve。這個函數返回一個格式為(filename, headers)的元祖,其中filename是本地文件的名稱(由urllib自動創建),而headers包含一些有關遠程文件的信息。如果要給下載的副本指定文件名,可通過函數urlretrieve的第二個參數來指定。
函數格式為: urlretrieve(url, filename=None, reporthook=None, data=None),函數urlretrieve將url中的內容存儲在filename中,如果filename沒有指定,下載的副本將放在某個臨時位置,可使用函數open來打開。但使用完畢后,你可能想將其刪除,以免暫用磁盤空間,可調用函數urlcleanup來替你完成清除工作。
1.2.3 其他模塊
Python標準庫提供了一些與網絡相關的模塊,如下(只列舉了一些常用的):
cgi ? 基本的CGI文件
asyncore 異步套接字處理程序
asynchat 包含補充asyncore的功能
Cookie ? ?Cookie對象操作,主要用于服務器
cookielib 客戶端Cookie支持
email ? 電子郵件支持
ftplib FTP客戶端模塊
httplib ? HTTP客戶端模塊
mailbox ? 讀取多種郵箱格式
urlparse ? 用于解讀URL
2 SocketServer及相關的類
模塊SocketServer是標準庫提供的服務器框架的基石,這個框架包括BaseHTTPServer、SimpleHTTPServer、CGIHTTPServer、SimpleXMLRPCServer和DocXMLRPCServer等服務器,它們在基本服務器的基礎上添加了各種功能。
SocketServer包含4個基本的服務器:TCPServer(支持TCP套接字流)、UDPServer(支持UDP套接字流)、UnixStreamServer和UnixDatagramServer。后面3個不常用。
使用模塊SocketServer編寫服務器時,大部分代碼都位于請求處理中。每當服務器收到客戶端的連接請求時,都將實例化一個請求處理程序,并對其調用各種處理方法來處理請求。基本請求處理程序類BaseRequestHandler將所有操作都放在一個方法中——服務器自動調用的方法handle。這個方法可通過書信self.request來訪問客戶端套接字。如果處理的是流(TCPServer時很可能如此),可使用StreamRequestHandler類,它包含另外兩個屬性:self.rfile(用于讀取)和self.wfile(用于寫入)。你可以使用這兩個類似與文件的對象來與客戶端通信。
模塊SocketServer還包含很多其他的類,它們為HTTP服務器提供基本的支持,以及XML-RPC支持。
from socketserver import TCPServer,StreamRequestHandlerclass Handle(StreamRequestHandler):def handle(self):addr = self.request.getpeername()print("addr:", addr)self.wfile.write("謝謝")server = TCPServer(('',1234), Handle) server.serve_forever()
3 多個連接
處理多個連接的主要方式有三種:分叉(forking)、線程化和異步I/O。通過結合使用SocketServer中的混合類和服務器類,很容易實現分叉和線程化。但是,分叉占用資源較多,且在客戶端很多時可伸縮性不高;而線程化可能帶來同步問題。并且Windows不支持分叉,分叉是UNIX術語。
3.1 使用SocketServer實現分叉和線程化
使用框架SocketServer創建分叉或線程化服務器非常簡單,一般僅當方法handle需要很長時間才能執行完畢是,分叉和線程化才能提供有效幫助。創建如下:
1.分叉服務器
from socketserver import TCPServer,StreamRequestHandler,ForkingMixInclass Server(ForkingMixIn, TCPServer):passclass Handle(StreamRequestHandler):def handle(self):addr = self.request.getpeername()print("addr:", addr)self.wfile.write("謝謝")server = Server(('',1234), Handle) server.serve_forever()2.線程化服務器
from socketserver import TCPServer,StreamRequestHandler,ThreadingMixInclass Server(ThreadingMixIn, TCPServer):passclass Handle(StreamRequestHandler):def handle(self):addr = self.request.getpeername()print("addr:", addr)self.wfile.write("謝謝")server = Server(('',1234), Handle) server.serve_forever()3.2 使用select和poll實現異步I/O
當服務器與客戶端通信時,來自客戶端的數據可能時斷時續。如果使用了分叉和線程化,這就不是問題:因為一個進程(線程)等待數據時,其他進程(線程)可繼續處理其客戶端。然而,另一種做法是只處理正在通信的客戶端。你甚至無需不斷監聽,只需監聽后將客戶端加入隊列即可。
這就是框架asyncore/asynchat 和 Twisted采取的方法。這種功能的基石是函數select或poll(只支持UNIX系統)。這兩個函數都位于模塊select中,其中poll的可伸縮性更高。
函數select接收三個必不可少的參數和一個可選參數,其中前三個參數為序列,而第四個參數為超時時間(單位為秒)。這些序列包含文件描述符整數(也可以是包含返回文件描述符整數的方法fileno),表示我們正在等待的連接。這三個序列分別表示需要輸入和輸出以及發生異常(錯誤等)的連接。如果沒有指定超時時間,select將阻斷(即等待)到有文件描述符準備就緒;如果指定了超時時間,select將最多阻斷指定的秒數;如果超時時間為0,select將不斷輪詢(即不阻斷)。select返回三個序列(即一個長度為3的元祖),其中每個序列都包含相應參數中處于活動狀態的文件描述符。
如下所示,一個使用select的簡單服務器:import socket, select
s = socket.socket() host = socket.gethostname() port = 8080 s.bind((host,port)) s.listen(5) inputs = [s] while True:# 方法select返回三個參數,通過序列解包賦值rs, ws, es = select.select(inputs, [], [])for r in rs:if r is s:c, addr = s.accept()print("addr:", addr)inputs.append(c)else:try:data = r.reve(1024)discon = not dataexcept socket.error:discon = Trueif discon:
# 1.getpeername():用于獲取與某個套接字關聯的外地協議地址
# 2.getsockname():用于獲取與某個套接字關聯的本地協議地址print(r.getpeername(), 'discon')inputs.remove(r)else:print(data)
方法poll 使用起來比select容易。調用poll 時,將返回一個輪詢對象。你可以使用方法register 向這個對象注冊文件描述符(或包含方法fileno的對象)。注冊后可使用方法unregister 將它們刪除。注冊對象(如套接字)后,可調用其方法poll(它接受一個可選的超時時間參數)。這將返回一個包含(fd, event)元祖的列表(可能為空),其中fd為文件描述符,event是發生的事件。event是一個位掩碼,這意味著它是一個整數,其各個位對應于不同的事件。各種事件是用select 模塊中的常量表示的,如下表。要檢查指定的位是否為1 (即是否發生了相應的事件),可使用按位與運算符( & ):
if event & select.POLLIN: pase
事件名 描述
POLLIN ? 文件描述符中有需要讀取的數據
POLLPRI ? ?文件描述符中有需要讀取的 緊急數據
POLLOUT ? ? ? ? ?文件描述符為寫入數據做好了準備
POLLERR ? 文件描述符出現了錯誤狀態
POLLHUP ? 掛起。連接已斷開
POLLNVAL 無效請求。連接未打開
下面是一個使用poll的簡單服務器:
import socket, select s = socket.socket()host = socket.gethostname() port = 8080 s.bind((host, port))fdmp = {s.fileno(): s}s.listen(5) p = select.poll() p.register(s) while True:events = p.poll()for fd, event in events:if fd in fdmp:c, addr = s.accept()print("addr:", addr)p.register(c)fdmp[c.fileno()] = celif event & select.POLLIN:data = fdmp[fd].recv(1024)if not data:print(fdmp[fd].getpeername(),"discon")p.unregister(fd)del fdmp[fd]else:print(data)4 Twisted
這是Twisted Matrix Laboratories 開發的一個框架,功能豐富而復雜,支持大多數主要的網絡協議??蚣躎wisted是異步的,因此效率和可伸縮性都非常高。對很多自定義網絡應用程序來說,使用Twisted來開發可能是最佳選擇。
轉載于:https://www.cnblogs.com/www-123456/p/10638868.html
總結
以上是生活随笔為你收集整理的python学习笔记(十 三)、网络编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java的自增自减_Java中自增和自减
- 下一篇: python怎么过滤停用词_第6天:文本