Python網路程式設計之一:網路程式設計(《Python基礎教程-第3版》讀書筆記)

晴朗_不積跬步無以至千里發表於2020-12-21

Python網路程式設計之一:網路程式設計簡介

Python提供了強大的網路程式設計支援,有很多庫實現了常見的網路協議以及基於這些協議的抽象層,讓你
能夠專注於程式的邏輯,而無需關心通過線路來傳輸位元的問題。

一、常用的網路模組

1、模組 socket

低階別的網路服務支援基本的 Socket,它提供了標準的 BSD Sockets API,可以訪問底層作業系統Socket介面的全部方法。

Socket又稱"套接字",應用程式通常通過"套接字"向網路發出請求或者應答網路請求,使主機間或者一臺計算機上的程式間可以通訊。

1.1、socket()函式

1、格式:
socket.socket(family,type,proto)
2、引數:
family(地址族): 套接字家族可以是 AF_UNIX 或者 AF_INET
type(套接字型別): 根據是面向連線的還是非連線分為SOCK_STREAM(流套接字)或SOCK_DGRAM(資料包套接字)
protocol(協議): 一般不填預設為0

1.2、套接字

1、所謂套接字(Socket),就是對網路中不同主機上的應用程式之間進行雙向通訊的端點的抽象。一個套接字就是網路上程式通訊的一端,提供了應用層程式利用網路協議交換資料的機制。從所處的地位來講,套接字上聯應用程式,下聯網路協議棧,是應用程式通過網路協議進行通訊的介面,是應用程式與網路協議根進行互動的介面。

2、套接字分為兩類:伺服器套接字和客戶端套接字。建立伺服器套接字後,讓它等待連線請求
的到來。這樣,它將在某個網路地址(由IP地址和埠號組成)處監聽,直到客戶端套接字建立
連線。隨後,客戶端和伺服器就能通訊了。客戶端套接字處理起來通常比伺服器端套接字容易些,因為伺服器必須準備隨時處理客戶端連線,還必須處理多個連線;而客戶端只需連線,完成任務後再斷開連線即可。

3、Socket 物件(內建)方法

伺服器端套接字:

函式,描述
s.bind()繫結地址(host,port)到套接字, 在AF_INET下,以元組(host,port)的形式表示地址。
s.listen()開始TCP監聽。backlog指定在拒絕連線之前,作業系統可以掛起的最大連線數量。該值至少為1,大部分應用程式設為5就可以了。
s.accept()被動接受TCP客戶端連線,(阻塞式)等待連線的到來

客戶端套接字:

函式,描述
s.connect()主動初始化TCP伺服器連線,。一般address的格式為元組(hostname,port),如果連線出錯,返回socket.error錯誤。
s.connect_ex()connect()函式的擴充套件版本,出錯時返回出錯碼,而不是丟擲異常

公共用途的套接字函式:

函式,描述
s.recv()接收TCP資料,資料以字串形式返回,bufsize指定要接收的最大資料量。flag提供有關訊息的其他資訊,通常可以忽略。
s.send()傳送TCP資料,將string中的資料傳送到連線的套接字。返回值是要傳送的位元組數量,該數量可能小於string的位元組大小。
s.sendall()完整傳送TCP資料,完整傳送TCP資料。將string中的資料傳送到連線的套接字,但在返回之前會嘗試傳送所有資料。成功返回None,失敗則丟擲異常。
s.recvfrom()接收UDP資料,與recv()類似,但返回值是(data,address)。其中data是包含接收資料的字串,address是傳送資料的套接字地址。
s.sendto()傳送UDP資料,將資料傳送到套接字,address是形式為(ipaddr,port)的元組,指定遠端地址。返回值是傳送的位元組數。
s.close()關閉套接字
s.getpeername()返回連線套接字的遠端地址。返回值通常是元組(ipaddr,port)。
s.getsockname()返回套接字自己的地址。通常是一個元組(ipaddr,port)
s.setsockopt(level,optname,value)設定給定套接字選項的值。
s.getsockopt(level,optname[.buflen])返回套接字選項的值。
s.settimeout(timeout)設定套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛建立套接字時設定,因為它們可能用於連線的操作(如connect())
s.gettimeout()返回當前超時期的值,單位是秒,如果沒有設定超時期,則返回None。
s.fileno()返回套接字的檔案描述符。
s.setblocking(flag)如果 flag 為 False,則將套接字設為非阻塞模式,否則將套接字設為阻塞模式(預設值)。非阻塞模式下,如果呼叫 recv() 沒有發現任何資料,或 send() 呼叫無法立即傳送資料,那麼將引起 socket.error 異常。
s.makefile()建立一個與該套接字相關連的檔案
1.3、實現原理

1、伺服器套接字先呼叫方法 bind ,再呼叫方法 listen 來監聽特定的地址。然後,客戶端套接字就可連線到伺服器了,辦法是呼叫方法 connect 並提供呼叫方法 bind 時指定的地址(在伺服器端,可使用函式 socket.gethostname 獲取當前機器的主機名)。這裡的地址是一個格式為 (host, port)的元組,其中 host 是主機名(如 www.example.com ),而 port 是埠號(一個整數)。方法 listen 接受一個引數——待辦任務清單的長度(即最多可有多少個連線在佇列中等待接納,到達這個數量後將開始拒絕連線)。

2、伺服器套接字開始監聽後,就可接受客戶端連線了,這是使用方法 accept 來完成的。這個方法將阻斷(等待)到客戶端連線到來為止,然後返回一個格式為 (client, address) 的元組,其中client 是一個客戶端套接字,而 address 是前面解釋過的地址。伺服器能以其認為合適的方式處理客戶端連線,然後再次呼叫 accept 以接著等待新連線到來。這通常是在一個無限迴圈中完成的。

3、最簡單的客戶端程式和伺服器程式

伺服器端:

import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.bind((host, port))
s.listen(5)
while True:
    c, addr = s.accept()
    print('Got connection from', addr)
    str = 'Thank you for connecting'
    str = str.encode()
    c.send(str)
    c.close()

在這裡插入圖片描述

客戶端:

import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.connect((host, port))
print(s.recv(1024))

在這裡插入圖片描述

分析:
1、如果在同一臺機器上執行它們(先執行伺服器程式),伺服器程式將列印一條收到連線請求的訊息,然後客戶端程式將列印它從伺服器那裡收到的訊息。
2、在伺服器還在執行時,可執行多個客戶端

2、模組 urllib 和 urllib2

在可供使用的網路庫中, urllib 和 urllib2 可能是投入產出比最高的兩個。它們讓你能夠通過網路訪問檔案,就像這些檔案位於你的計算機中一樣。只需一個簡單的函式呼叫,就幾乎可將統一資源定位符(URL)可指向的任何動作作為程式的輸入。

模組 urllib 和 urllib2 的功能差不多,但 urllib2 更好一些。對於簡單的下載, urllib 綽綽有餘。如果需要實現HTTP身份驗證或Cookie,抑或編寫擴充套件來處理自己的協議, urllib2 可能是更好的選擇。

2.1、開啟遠端檔案

使用模組urllib.request 中的函式 urlopen開啟網頁檔案,並且讀取其中的HTML結構:

from urllib.request import urlopen

webpage = urlopen('http://www.python.org')

#遍歷http://www.python.org網頁的HTNL結構
for line in webpage:
    print(line)

結果:
在這裡插入圖片描述
原網頁檢查結果:
在這裡插入圖片描述
分析:函式 urlopen 返回一個類似於檔案的物件,可從中讀取資料。並且支援方法 close 、 read 、 readline 和 readlines ,還支援迭代等。

2.2、下載遠端檔案

下載http://www.python.org網頁HTML檔案:

from urllib.request import urlretrieve

#下載http://www.python.org網頁HTML檔案
urlretrieve('http://www.python.org', r'F:\開發工具\python\project\wangluo\python_webpage.html')

結果:
在這裡插入圖片描述
分析:urlretrieve(filename, headers)函式中,filename是本地檔案的名稱(由 urllib自動建立,就是網址),headers是headers 包含一些有關遠端檔案的資訊(這裡是指儲存的路徑及檔名)

2.3、一些實用的函式
函式,描述
quote(string[, safe])返回一個字串,其中所有的特殊字元(在URL中有特殊意義的字元)都已替換為對URL友好的版本(如將~替換為 7E % )。如果要將包含特殊字元的字串用作URL,這很有用。引數safe是一個字串(預設為 ‘/’ ),包含不應像這樣對其進行編碼的字元。
quote_plus(string[, safe])類似於 quote ,但也將空格替換為加號。
unquote(string)與 quote 相反。
unquote_plus(string)與 quote_plus 相反。
urlencode(query[, doseq])將對映(如字典)或由包含兩個元素的元組(形如 (key,value) )組成的序列轉換為“使用URL編碼的”字串。這樣的字串可用於CGI查詢中(詳細資訊請參閱Python文件)。

3、其他模組

Python庫等地方還包含很多與網路相關的模組。

Python標準庫中的一些與網路相關的模組:
在這裡插入圖片描述

二、SocketServer 及相關的類

高階別的網路服務模組 SocketServer, 它提供了伺服器中心類,可以簡化網路伺服器的開發。

模組 SocketServer 是標準庫提供的伺服器框架的基石,這個框架包括BaseHTTPServer 、SimpleHTTPServer 、 CGIHTTPServer 、 SimpleXMLRPCServer 和 DocXMLRPCServer 等伺服器,它們在基本伺服器的基礎上新增了各種功能。

SocketServer 包含4個基本的伺服器: TCPServer (支援TCP套接字流)、 UDPServer (支援UDP資料包套接字)以及更難懂的 UnixStreamServer 和 UnixDatagramServer 。

使用模組 SocketServer 編寫伺服器時,大部分程式碼都位於請求處理器中。每當伺服器收到客戶端的連線請求時,都將例項化一個請求處理程式,並對其呼叫各種處理方法來處理請求。

具體呼叫哪些方法取決於使用的伺服器類和請求處理程式類;還可從這些請求處理器類派生出子類,
從而讓伺服器呼叫一組自定義的處理方法。

基本請求處理程式類 BaseRequestHandler 將所有操作都放在一個方法中——伺服器呼叫的方法 handle 。這個方法可通過屬性 self.request 來訪問客戶端套接字。如果處理的是流(使用 TCPServer 時很可能如此),可使用 StreamRequestHandler 類,它包含另外兩個屬性: self.rfile (用於讀取)和 self.wfile (用於寫入)。你可使用這兩個類似於檔案的物件來與客戶端通訊。

基於 SocketServer 的極簡伺服器:

from socketserver import TCPServer, StreamRequestHandler

class Handler(StreamRequestHandler):
    def handle(self):
        addr = self.request.getpeername()
        print('Got connection from', addr)
        str = 'Thank you for connecting'
        str = str.encode()
        self.wfile.write(str)

server = TCPServer(('', 1234), Handler)
server.serve_forever()

客戶端:

import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.connect((host, port))
print(s.recv(1024))

執行三次客戶端後,伺服器接收結果:

Got connection from ('192.168.3.7', 65282)
Got connection from ('192.168.3.7', 65283)
Got connection from ('192.168.3.7', 65285)

三、多個連線

處理多個連線的主要方式有三種:分叉(forking)、執行緒化和非同步I/O。

1、使用 SocketServer 實現分叉

分叉是一個UNIX術語。對程式(執行的程式)進行分叉時,基本上是複製它,而這樣得到的兩個程式都將從當前位置開始繼續往下執行,且每個程式都有自己的記憶體副本(變數等)。原來的程式為父程式,複製的程式為子程式。如果你是科幻小說迷,可將它們視為並行的宇宙:分叉操作在時間軸上建立一個分支,最終得到兩個獨立存在的宇宙(程式)。所幸程式能夠判斷它們是原始程式還是子程式(通常檢視函式 fork 的返回值),因此能夠執行不同的操作。

注意:Windows不支援分叉。

分叉伺服器:

from socketserver import TCPServer, ForkingMixIn, StreamRequestHandler
class Server(ForkingMixIn, TCPServer): pass
class Handler(StreamRequestHandler):
    def handle(self):
        addr = self.request.getpeername()
        print('Got connection from', addr)
        str = 'Thank you for connecting'
        str = str.encode()
        self.wfile.write(str)
server = Server(('', 1234), Handler)
server.serve_forever()

2、使用 SocketServer 實現執行緒化

執行緒是輕量級程式(子程式),都位於同一個程式中並共享記憶體。這減少了佔用的資源,但也帶來了一個缺點:由於執行緒共享記憶體,你必須確保它們不會彼此干擾或同時修改同一項資料,否則將引起混亂。這些問題都屬於同步問題。在現代作業系統(不支援分叉的Windows除外)中,分叉的速度其實非常快,較新的硬體能夠更好地應付其資源消耗。如果你不想處理麻煩的同步問題,分叉可能是不錯的選擇。

執行緒化伺服器:

from socketserver import TCPServer, ThreadingMixIn, StreamRequestHandler
class Server(ThreadingMixIn, TCPServer): pass
class Handler(StreamRequestHandler):
    def handle(self):
        addr = self.request.getpeername()
        print('Got connection from', addr)
        str = 'Thank you for connecting'
        str = str.encode()
        self.wfile.write(str)
server = Server(('', 1234), Handler)
server.serve_forever()

3、使用 select 和 poll 實現非同步 I/O

3.1、select

函式 select 接受三個必不可少的引數和一個可選引數,其中前三個引數為序列(第一個是所有的輸入的data,就是指外部發過來的資料;第2個是監控和接收所有要發出去的data(outgoing data);第3個監控錯誤資訊),而第四個引數為超時時間(單位為秒)。

如果沒有指定超時時間, select 將阻斷(即等待)到有檔案描述符準備就緒;如果指定了超時時間, select 將最多阻斷指定的秒數;如果超時時間為零, select將不斷輪詢(即不阻斷)。

select 返回三個序列(即一個長度為3的元組),其中每個序列都包含相應引數中處於活動狀態的檔案描述符。

使用 select 的簡單伺服器:

import socket, select
s = socket.socket()
host = socket.gethostname()
port = 1234
s.bind((host, port))
s.listen(5)
inputs = [s]
while True:
    rs, ws, es = select.select(inputs, [], [])
    for r in rs:
        if r is s:
            c, addr = s.accept()
            print('Got connection from', addr)
            inputs.append(c)
    else:
        try:
            data = c.recv(1024)
            disconnected = not data
        except socket.error:
            disconnected = True
        if disconnected:
            str = 'disconnected'
            str = str.encode()
            print(c.getpeername(), str)
            inputs.remove(c)
        else:
            print(data)
3.2、 poll

使用 poll 的簡單伺服器:

import socket, select
s = socket.socket()
host = socket.gethostname()
port = 1234
s.bind((host, port))
fdmap = {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 fdmap:
            c, addr = s.accept()
            print('Got connection from', addr)
            p.register(c)
            fdmap[c.fileno()] = c
        elif event & select.POLLIN:
            data = fdmap[fd].recv(1024)
            if not data:  # 沒有資料 --連線已關閉
                print(fdmap[fd].getpeername(), 'disconnected')
                p.unregister(fd)
                del fdmap[fd]
            else:
                print(data)

分析: module ‘select’ has no attribute ‘poll’,執行後報錯,因為Windows不支援poll。

四、Twisted

Twisted是由Twisted Matrix Laboratories(http://twistedmatrix.com)開發的,這是一個事件驅動的Python網路框架,最初是為編寫網路遊戲開發的,但現被各種網路軟體使用。

Twisted是一個功能極其豐富的框架,支援Web伺服器和客戶端、SSH2、SMTP、POP3、IMAP4、AIM、ICQ、IRC、MSN、Jabber、NNTP、DNS等

1、下載並安裝 Twisted

訪問Twisted Matrix網站(http://twistedmatrix.com),並單擊其中的一個下載連結。

2、編寫 Twisted 伺服器

如果你只想建立自定義協議類的例項,可使用Twisted自帶的工廠——模組twisted.internet.protocol 中的 Factory 類。編寫自定義協議時,將模組 twisted.internet.protocol 中的 Protocol 作為超類。有新連線到來時,將呼叫事件處理程式 connectionMade ;連線中斷時,將呼叫 connectionLost 。來自客戶端的資料是通過處理程式 dataReceived 接收的。當然,你不能使用事件處理策略來向客戶端傳送資料。這種工作是使用物件 self.transport 完成的,它包含一個 write 方法。這個物件還有一個 client 屬性,其中包含客戶端的地址(主機名和埠)。

使用Twisted建立的簡單伺服器:

from twisted.internet import reactor
from twisted.internet.protocol import Protocol, Factory
class SimpleLogger(Protocol):
    def connectionMade(self):
        print('Got connection from', self.transport.client)
    def connectionLost(self, reason):
        print(self.transport.client, 'disconnected')
    def dataReceived(self, data):
        print(data)
factory = Factory()
factory.protocol = SimpleLogger
reactor.listenTCP(1234, factory)
reactor.run()

五、小結

1、套接字和模組 socket :套接字是讓程式(程式)能夠通訊的資訊通道,這種通訊可能需要通過網路進行。模組 socket 讓你能夠在較低的層面訪問客戶端套接字和伺服器套接字。伺服器套接字在指定的地址處監聽客戶端連線,而客戶端套接字直接連線到伺服器。

2、 urllib 和 urllib2 :這些模組讓你能夠從各種伺服器讀取和下載資料,為此你只需提供指向資料來源的URL即可。模組 urllib 是一種比較簡單的實現,而 urllib2 功能強大、可擴充套件性極強。這兩個模組都通過諸如 urlopen 等函式來完成工作。

3、框架 SocketServer :這個框架位於標準庫中,包含一系列同步伺服器基類,讓你能夠輕鬆地編寫伺服器。它還支援使用CGI的簡單Web(HTTP)伺服器。如果要同時處理多個連線,必須使用支援分叉或執行緒化的混合類。

4、select 和 poll :這兩個函式讓你能夠在一組連線中找出為讀取和寫入準備就緒的連線。這意味著你能夠以迴圈的方式依次為多個連線提供服務,從而營造出同時處理多個連線的假象。另外,相比於執行緒化或分叉,雖然使用這兩個函式編寫的程式碼要複雜些,但解決方案的可伸縮性和效率要高得多。

5、Twisted:這是Twisted Matrix Laboratories開發的一個框架,功能豐富而複雜,支援大多數主要的網路協議。雖然這個框架很大且其中使用的一些成例看起來宛如天書,但其基本用法簡單而直觀。框架Twisted也是非同步的,因此效率和可伸縮性都非常高。對很多自定義網路應用程式來說,使用Twisted來開發很可能是最佳的選擇。

相關文章