1. 程式人生 > >python基礎教程(第三版)學習筆記(十四)

python基礎教程(第三版)學習筆記(十四)

第十四章 網路程式設計
 

鑑於Python提供的網路工具眾多,這裡只能簡要地介紹它的網路功能。 本章首先概述Python標準庫中的一些網路模組。然後討論SocketServer和相關的類,並介紹 地介紹同時處理多個連線的各種方法。最後,簡單地說一說Twisted,這是一個使用Python編寫網 絡程式的框架,功能豐富而成熟。
 

14.1 幾個網路模組
 

14.1.1 模組 socket
 

網路程式設計中的一個基本元件是套接字(socket)。套接字基本上是一個資訊通道,兩端各有一 個程式。在Python中,大多數網路程式設計都隱藏了模組socket的基本工作原理,不與套接字直接互動。
 

套接字分為兩類:伺服器套接字和客戶端套接字。建立伺服器套接字後,讓它等待連線請求 的到來。這樣,它將在某個網路地址(由IP地址和埠號組成)處監聽,直到客戶端套接字建立 連線。隨後,客戶端和伺服器就能通訊了。
 

客戶端套接字處理起來通常比伺服器端套接字容易些,因為伺服器必須準備隨時處理客戶端 連線,還必須處理多個連線;而客戶端只需連線,完成任務後再斷開連線即可。
 

本章後面將介紹 如何使用SocketServer等類和Twisted框架進行伺服器端程式設計。
 

套接字是模組socket中socket類的例項。函式格式為:
 

socket(family,type[,protocol])


 

例項化套接字時最多可指定三個引數:一個地址族 (family,預設為socket.AF_INET);一個是socket型別是流套接字(typ,預設設定socket.SOCK_STREAM,)還是資料報套接字 (socket.SOCK_DGRAM);一個是協議型別(protocol,使用預設值0就好)。建立普通套接字時,不用提供任何引數。
 

地址族(又稱協議族):
 

| Family引數 | 描述 |
| --------------- | ---------------------------------- |
| socket.AF_UNIX | 只能夠用於單一的Unix系統程序間通訊 |
| socket.AF_INET | 伺服器之間網路通訊 ipv4 |
| socket.AF_INET6 | IPv6 |
 

socket型別:
 

| Type引數 | 描述 |
| ------------------ | ------------------------------------------------------------ |
| socket.SOCK_STREAM | 流式socket , 當使用TCP時選擇此引數 |
| socket.SOCK_DGRAM | 資料報式socket ,當使用UDP時選擇此引數 |
| socket.SOCK_RAW | 原始套接字,普通的套接字無法處理ICMP、IGMP等網路報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由使用者構造IP頭Type引數 |
 

協議型別 :
 

| protocol引數 | 描述 |
| ------------------ | ------------------------------------------------------------ |
| socket.IPPROTO_RAW | 相當於protocol=255,此時socket只能用來發送IP包,而不能接收任何的資料。傳送的資料需要自己填充IP包頭,並且自己計算校驗和 |
| socket.IPPROTO_IP | 相當於protocol=0,此時用於接收任何的IP資料包。其中的校驗和和協議分析由程式自己完成 |
 

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

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

為傳輸資料,套接字提供了兩個方法:send和recv(表示receive)。要傳送資料,可呼叫方 法send並提供一個字串;要接收資料,可呼叫recv並指定最多接收多少個位元組的資料。如果不 確定該指定什麼數字,1024是個不錯的選擇。
 

以下是兩個最簡單的客戶端程式和伺服器程式。


 

```python

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


```
 

```python

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


```
 

結果:
 

```

C:\Users\xx\AppData\Local\Programs\Python\Python37\python.exe E:/pythonProjects/gui
b'Thank you for connecting'

Process finished with exit code 0



```
 

 14.1.2 模組 urllib 和 urllib2
 

 1、開啟遠端檔案
 

幾乎可以像開啟本地檔案一樣開啟遠端檔案,差別是隻能使用讀取模式,以及使用模組 urllib.request中的函式urlopen,而不是open(或file)。
 

```python

from urllib.request import urlopen
webpage = urlopen('http://www.python.org')
w=webpage.read(200)
print(w)


```
 

結果:
 

```

C:\Users\xx\AppData\Local\Programs\Python\Python37\python.exe E:/pythonProjects/print.py
b'<!doctype html>\n<!--[if lt IE 7]> <html class="no-js ie6 lt-ie7 lt-ie8 lt-ie9"> <![endif]-->\n<!--[if IE 7]> <html class="no-js ie7 lt-ie8 lt-ie9"> <![endif]-->\n<!--[if IE 8]> <h'

Process finished with exit code 0



```
 

urlopen返回的類似於檔案的物件支援方法close、read、readline和readlines,還支援迭 代等。
 

 2、獲取遠端檔案
 

函式urlopen返回一個類似於檔案的物件,可從中讀取資料。如果要讓urllib替你下載檔案, 並將其副本儲存在一個本地檔案中,可使用urlretrieve。 。這個函式不返回一個類似於檔案的對 象,而返回一個格式為(filename, headers)的元組,其中filename是本地檔案的名稱(由urllib 自動建立),而headers包含一些有關遠端檔案的資訊 。如果要給下載的副本指定檔名, 可通過第二個引數來提供。
 

```python

urlretrieve('http://www.python.org', 'C:\python_webpage.html')


```
 

結果:
 

```

C:\Users\xx\AppData\Local\Programs\Python\Python37\python.exe E:/pythonProjects/print.py
b'<!doctype html>\n<!--[if lt IE 7]> <html class="no-js ie6 lt-ie7 lt-ie8 lt-ie9"> <![endif]-->\n<!--[if IE 7]> <html class="no-js ie7 lt-ie8 lt-ie9"> <![endif]-->\n<!--[if IE 8]> <h'
E:\python_webpage.html
Server: nginx
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
x-xss-protection: 1; mode=block
X-Clacks-Overhead: GNU Terry Pratchett
Via: 1.1 varnish
Content-Length: 48842
Accept-Ranges: bytes
Date: Mon, 12 Nov 2018 09:49:11 GMT
Via: 1.1 varnish
Age: 286
Connection: close
X-Served-By: cache-iad2145-IAD, cache-tyo19924-TYO
X-Cache: MISS, HIT
X-Cache-Hits: 0, 635
X-Timer: S1542016152.810209,VS0,VE0
Vary: Cookie
Strict-Transport-Security: max-age=63072000; includeSubDomains



Process finished with exit code 0


```
 


 

要清空這樣的臨時檔案,可呼叫函式urlcleanup且不提供任何引數, 它將負責替你完成清空工作。
 

14.1.3 其他模組
 

標準庫中一些與網路相關的模組 :
 

| 模 塊 | 描 述 |
| ------------------ | ------------------------------ |
| asynchat | 包含補充asyncore的功能 |
| asyncore | 非同步套接字處理程式 |
| cgi | 基本的CGI支援 |
| Cookie | Cookie物件操作,主要用於伺服器 |
| cookielib | 客戶端Cookie支援 |
| email | 電子郵件(包括MIME)支援 |
| ftplib | FTP客戶端模組 |
| gopherlib | Gopher客戶端模組 |
| httplib | HTTP 客戶端模組 |
| imaplib | IMAP4客戶端模組 |
| mailbox | 讀取多種郵箱格式 |
| mailcap | 通過mailcap檔案訪問MIME配置 |
| mhlib | 訪問MH郵箱 |
| nntplib | NNTP客戶端模組 |
| poplib | POP客戶端模組 |
| robotparser | 解析Web伺服器robot檔案 |
| SimpleXMLRPCServer | 一個簡單的XML-RPC伺服器 |
| smtpd | SMTP伺服器模組 |
| smtplib | SMTP客戶端模組 |
| telnetlib | Telnet客戶端模組 |
| urlparse | 用於解讀URL |
| xmlrpclib | XML-RPC客戶端支援 |
 

 14.2 SocketServer 及相關的類
 

編寫簡單的套接字伺服器並不難。然而,如果要建立的並非簡單伺服器, 還是求助於伺服器模組吧。模組SocketServer是標準庫提供的伺服器框架的基石,這個框架包括 BaseHTTPServer、SimpleHTTPServer、CGIHTTPServer、SimpleXMLRPCServer和DocXMLRPCServer等服 務器,它們在基本伺服器的基礎上添加了各種功能。要實現本模組,必須定義一個繼承於基類BaseRequestHandler的處理程式類,並且在此類中重寫handle()方法,這個方法可通過屬性self.request來訪問客戶 端套接字。
 

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

以下是極簡伺服器的SocketServer版本 的實力程式碼:
 

```python

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


```
 

客戶端程式碼不變:
 

```python

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


```
 

執行結果:
 

```

C:\Users\xx\AppData\Local\Programs\Python\Python37\python.exe E:/pythonProjects/gui
b'Thank you for connecting'

Process finished with exit code 0



```
 

14.3 多個連線
 

前面討論的伺服器解決方案都是同步的:不能同時處理多個客戶端的連線請求。如果連線持 續的時間較長,比如完整的聊天會話,就需要能夠同時處理多個連線。
 

處理多個連線的主要方式有三種:分叉(forking)、執行緒化和非同步I/O。通過結合使用 SocketServer中的混合類和伺服器類,很容易實現分叉和執行緒化 。
 

 14.3.1 使用 SocketServer 實現分叉和執行緒化
 

使用框架SocketServer建立分叉或執行緒化伺服器非常簡單,幾乎不需要任何解釋。請注意,Windows不支援分叉。
 

看示例:
 

1、分叉伺服器:
 

```python

#serve.py
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)
self.wfile.write('Thank you for connecting')
server = Server(('', 1234), Handler)
server.serve_forever()


```
 

(由於我用的是windows系統懶得安裝虛擬環境所以不能演示,清涼解)
 

 2、執行緒化伺服器:
 

```python

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)
self.wfile.write(b'Thank you for connecting')
server = Server(('', 1234), Handler)
server.serve_forever()


```
 

執行結果:
 

```

C:\Users\xx\AppData\Local\Programs\Python\Python37\python.exe E:/pythonProjects/gui
b'Thank you for connecting'

Process finished with exit code 0


```
 

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

由於windows不支援poll所以此節略去。
 

14.4 Twisted
 

Twisted是由Twisted Matrix Laboratories(http://twistedmatrix.com)開發的,這是一個事件驅 動的Python網路框架,在Twisted中, 你能實現事件處理程式,就像在GUI工具包中一樣。實際上,Twisted與多個常用 的GUI工具包(Tk、GTK、Qt和wxWidgets)配合得天衣無縫。本節介紹一些基本概念,並演示如何使用Twisted完成一些簡單的網路程式設計任務。掌握這些基 本概念後,你就可參考Twisted文件 。
 

 14.4.1 下載並安裝 Twisted
 

windows下安裝,執行cmd,在命令提示符下輸入:
 

```dockerfile

pip install twisted[tls]


```
 

即安裝完畢。
 

也可以用PyCharm安裝,如出現:
 

```

AttributeError: module 'pip' has no attribute 'main'


```
 

錯誤,就找到PyCharm安裝目錄下的helpers/packaging_tool.py檔案,用以下程式碼替換其中的do_install和do_uninstall 函式 。
 

```python

def do_install(pkgs):
try:
#import pip
try:
from pip._internal import main
except Exception:
from pip import main
except ImportError:
error_no_pip()
return main(['install'] + pkgs)


def do_uninstall(pkgs):
try:
#import pip
try:
from pip._internal import main
except Exception:
from pip import main
except ImportError:
error_no_pip()
return main(['uninstall', '-y'] + pkgs)


```
 

 14.4.2 編寫 Twisted 伺服器
 

Twisted採用的是基於事件的方法。要編寫簡單的伺服器,只需實現處理如下情形的事件處理程式: 客戶端發起連線,有資料到來,客戶端斷開連線(以及眾多其他的事件)。專用類可在基本類的 基礎上定義更細緻的事件,如包裝“資料到來”事件,收集換行符之前的所有資料再分派“資料 行到來”事件。
 

以下程式碼清單所示的是以上伺服器的Twisted版本。在這個版本中,包含一些設定工作:需要例項化Factory,並 設定其屬性protocol,讓它知道該使用哪種協議(這裡是一個自定義協議)與客戶端通訊。 接下來,開始監聽指定的埠,讓工廠通過例項化協議物件來處理連線。為此,呼叫了模組 reactor中的函式listenTCP。最後,通過呼叫模組reactor中函式run啟動這個伺服器。
 

```python

#finger02.py
from twisted.internet import reactor
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
class SimpleLogger(LineReceiver):
def connectionMade(self):
print('Got connection from', self.transport.client)
def connectionLost(self, reason):
print(self.transport.client, 'disconnected')
def lineReceived(self, line):
print(line)
factory = Factory()
factory.protocol = SimpleLogger
reactor.listenTCP(1234, factory)
reactor.run()


```
 

模組twisted.protocols.basic包含幾個預定義的協議,其中一個就是 LineReceiver。它實現了dataReceived,並在每收到一整行後呼叫事件處理程式lineReceived。
 

```

C:\Users\xx\AppData\Local\Programs\Python\Python37\python.exe E:/pythonProjects/twisted/finger_02.py
Got connection from ('192.168.3.21', 54041)
('192.168.3.21', 54041) disconnected
Got connection from ('192.168.3.21', 54283)
('192.168.3.21', 54283) disconnected
Got connection from ('192.168.3.21', 54408)
Got connection from ('192.168.3.21', 54432)


```
 

關於twisted更詳細的資訊請參見:
 

https://twistedmatrix.com/documents/current/core/howto/tutorial/intro.html