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是個不錯的選擇。

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

#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()
#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

Python基礎教程(第3版).pdf

1、開啟遠端檔案

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

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包含一些有關遠端檔案的資訊 。如果要給下載的副本指定檔名, 可通過第二個引數來提供。

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版本 的實力程式碼:

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()

客戶端程式碼不變:

#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、分叉伺服器:

#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、執行緒化伺服器:

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,在命令提示符下輸入:

pip install twisted[tls]

即安裝完畢。

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

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

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

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啟動這個伺服器。

#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更詳細的資訊請參見: