[PYTHON] 核心編程筆記(16.P
- 2020 年 1 月 8 日
- 筆記
16.1 介紹
16.1.1 什麼是客戶/伺服器架構?
硬體的客戶/伺服器架構
軟體客戶/伺服器架構
16.1.2 客戶/伺服器網路編程
16.2 套接字: 通訊端點
16.2.1 什麼是套接字?
套接字是一種具有通訊端點感念的電腦網路數據結構
16.2.2 套接字地址:主機與埠
主機和埠類似區號和電話號碼的一對組合
合法的埠號範圍是0到65535,小於1024的埠號為系統保留埠
16.2.3 面向連接與無連接
面向連接(TCP)
套接字只有兩種一種是面向連接套接字,即在通訊之前一定要建立一條連接,這種通訊方式提供了順序的,可靠的不會重複的數據傳輸,每一份要發送的資訊都會拆分成多份,每份都會不多不少的到達目的地後重新按順序拼裝起來,傳給正在等待的應用程式
實現這種連接的主要協議就是傳輸控制協議(即TCP)
要創建TCP套接字就得在創建的時候指定套接字類型為SOCK_STREAM,表示為流套接字
無連接(UDP)
與虛電路相反的數據報型是無連接套接字,即無需建立連接就可以進行通訊,這意味著數據到達的順序,可靠性及數據不重複性就無法保證,數據會保留在數據邊界,數據不會像TCP協議那樣被拆封為小塊
使用數據報來傳輸數據就像郵政服務,郵件包裹不一定會按照他們發送的順序到達,而且可能還到達不了,而且還可能被重傳
由於面向連接套接字提供一些維持虛電路連接的開銷,數據報較他來說基本上沒有負擔,所以它能更好的×××能,適合於某些應用場合
實現這種連接的主要協議就是用戶數據報協議(即UDP)
要創建UDP套接字就得在創建的時候指定套接字類型為SOCK_DGRAM,即datagram數據報
由於這些套接字使用Internet協議來查找網路中的主機,這樣形成的整個系統一般都會由這兩對協議(TCP/IP)和(UDP/IP)來提及
16.3 Python中的網路編程
本節我們主要使用socket模組,模組中的socket()函數被用來創建套接字,其有自己的一套函數來提供基於套接字的網路傳輸
16.3.1 socket()模組函數:
創建套接字語法:
socket(socket_family,socket_type,protocol=0)
socket_family可以是AF_UNIX或AF_INET,soket_type可以是SOCK_STREAM或SOCK_DGRAM,protocal一般不填,默認為0
創建一個TCP/IP套接字,需要調用socket.socket()
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
創建一個UDP/IP套接字,需要調用socket.socket()
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
使用from socket import * 將socket模組里的所有屬性帶到命名空間里
當我們創建了套接字對象後,所有的交互豆漿通過對該套接字對象的方法進行調用
16.3.2 套接字對象(內建)方法
函數描述
s.bind()綁定地址(主機,埠號對)到套接字
s.listen()開始TCP監聽
s.accept()被動接受TCP伺服器連接,(阻塞式)等待連接到來
s.connect()主動初始化TCP伺服器連接
s.connect_ex()connect()函數的擴展版本,出錯時返回出錯碼,而不是拋異常公共用途的套接字函數
s.recv()接收TCP數據
s.send()發送TCP數據
s.sendall()完整發送TCP數據
s.recvfrom()接收UDP數據
s.sendto()發送UDP數據
s.getpeername()連接到當前套接字的遠端地址
s.getsockname()當前套接字的地址
s.getsockopt()返回指定套接字的參數
s.setsockopt()設置指定套接字的參數
s.close()關閉套接字
s.setblocking()設置套接字的阻塞與非阻塞模式
s.settimeout()設置阻塞套接字操作的超時時間
s.gettimeout()得到阻塞套接字操作的超市時間
面向文件的套接字的函數
s.fileno()曹姐字的文件描述符
s.makefile()創建一個與該套接字關聯的文件
16.3.3 創建一個TCP伺服器
ss.socket()#創建伺服器套接字
ss.bind()#把地址綁定到套接字上
ss.listen()#監聽連接
inf_loop()#伺服器無限循環
cs=ss.accept()#接受客戶的連接
comm_loop()#通訊循環
cs.recv()/cs.send()#對話(接收與發送)
cs.close()#關閉客戶套接字
ss.close()#關閉伺服器套接字(可選)
所有套接字都用socket().socket()函數創建,伺服器需要"坐在某個埠上"等待請求,所以需要綁定到一個本地地址上,TCP伺服器負責監聽連接,設置完,伺服器就可以進行無限循環了
默認伺服器會調用accept()阻塞式函數等待連接,來之前程式一直會處於掛起狀態
一旦接收到一個連接,accept()函數就會返回一個單獨的客戶的套接字用於後續通訊.
例,tsTserv.py文件會創建一個TCP服務程式,這個程式會把客戶發過來的字元串加上一個時間戳(格式:'[時間]數據')返回給客戶
# vi tsTserv.py -------------------------------- #!/usr/bin/env python #coding: UTF-8 from socket import * from time import ctime HOST = '' #綁定IP PORT = 21567 #埠號 BUFSIZ = 1024 #緩衝1K ADDR = (HOST,PORT) tcpSerSock = socket(AF_INET, SOCK_STREAM) tcpSerSock.bind(ADDR) #套接字 tcpSerSock.listen(5) #最大連接數 while True: print 'waiting for connection...' tcpCliSock,addr = tcpSerSock.accept() print '...connected from: ', addr while True: data = tcpCliSock.recv(BUFSIZ) if not data: break tcpCliSock.send('[%s] %s'%(ctime(),data)) print [ctime()],':',data tcpCliSock.close() tcpSerSock.close() --------------------------------
16.3.4 創建TCP客戶端
cs = socket()#創建客戶套接字
cs.connect()#嘗試連接伺服器
comm_loop#通訊循環
cs.send()/cs.recv()#對話(發送/接收)
cs.close()#關閉客戶套接字
所有套接字都由socket.socket()函數創建,在客戶有了套接字之後,可以調用connect()函數去連接伺服器,連接伺服器後,就可以與伺服器對話,對話結束可關閉套接字結束連接
例,程式連接到伺服器,提示用戶輸入要傳輸的數據,然後顯示伺服器返回的加了時間戳的結果
# vi tsTclnt.py ------------------------------- #!/usr/bin/env python from socket import * HOST = '192.168.8.18' PORT = 21567 BUFSIZ = 1024 ADDR = (HOST, PORT) tcpCliSock = socket(AF_INET, SOCK_STREAM) tcpCliSock.connect(ADDR) while True: data = raw_input('>') if not data: break tcpCliSock.send(data) data = tcpCliSock.recv(BUFSIZ) if not data: break print data tcpCliSock.close() -------------------------------
16.3.5 運行我們的客戶端與伺服器程式
# python tsTserv.py ----------------------------- waiting for connection... ----------------------------- # python tsTclnt.py ---------------------------------- >send test data [Thu Dec 19 12:46:36 2013] send test data >hi [Thu Dec 19 12:46:51 2013] hi >how are you? [Thu Dec 19 12:47:08 2013] how are you? ----------------------------------- # python tsTserv.py -------------------------------------- waiting for connection... ...connected from: ('192.168.8.19', 56868) ['Thu Dec 19 12:46:36 2013'] : send test data ['Thu Dec 19 12:46:51 2013'] : hi ['Thu Dec 19 12:47:08 2013'] : how are you? ---------------------------------------
核心提示:
"友好地"退出的一個方法就是把伺服器無限循環放在一個try-except語句中try子句中,並捕獲EOFError和KeyboardInterrupt異常,在異常處理子句中,調用close()函數關閉伺服器的套接字
例:
# vi tsTserv.py -------------------------------- root@ubuntu:~/python# vi tsTserv.py #!/usr/bin/env python #coding: UTF-8 from socket import * from time import ctime HOST = '' #綁定IP PORT = 21567 #埠號 BUFSIZ = 1024 #緩衝1K ADDR = (HOST,PORT) tcpSerSock = socket(AF_INET, SOCK_STREAM) tcpSerSock.bind(ADDR) #套接字 tcpSerSock.listen(5) #最大連接數 try: while True: print 'waiting for connection...' tcpCliSock,addr = tcpSerSock.accept() print '...connected from: ', addr while True: data = tcpCliSock.recv(BUFSIZ) if not data: break tcpCliSock.send('[%s] %s'%(ctime(),data)) print [ctime()],':',data except EOFError,KeyboardInterrupt: tcpCliSock.close() tcpSerSock.close() -------------------------------- # vi tsTclnt.py ------------------------------- #!/usr/bin/env python from socket import * HOST = '192.168.8.18' PORT = 21567 BUFSIZ = 1024 ADDR = (HOST, PORT) tcpCliSock = socket(AF_INET, SOCK_STREAM) tcpCliSock.connect(ADDR) try: while True: data = raw_input('>') if not data: break tcpCliSock.send(data) data = tcpCliSock.recv(BUFSIZ) if not data: break print data except EOFError,KeyboardInterrupt: tcpCliSock.close() -------------------------------
16.3.6 創建一個UDP伺服器
ss = socket()#創建一個伺服器套接字 ss.bind()#綁定伺服器套接字 inf_loop:#伺服器無限循環 cs = ss.recvfrom()/ss.sendto()#對話(接收與發送) ss.close()#關閉伺服器套接字 例,創建一個能接收客戶的消息,在消息前加一個時間戳後返回的UDP伺服器 # vi tsUserv.py ----------------------------- #!/usr/bin/env python from socket import * from time import ctime HOST = '' PORT = 21567 BUFSIZ = 1024 ADDR = (HOST,PORT) udpSerSock = socket(AF_INET, SOCK_DGRAM) udpSerSock.bind(ADDR) while True: print 'waiting for message...' data, addr = udpSerSock.recvfrom(BUFSIZ) udpSerSock.sendto('[%s] [%s]' %(ctime(), data),addr) print '...received from and returned to:', addr udpSerSock.close() ----------------------------- UDP和TCP伺服器的另一個重要區別是,由於數據報套接字是無連接的,所以無法把客戶的鏈接將誒另外的套接字進行後續通訊,這些伺服器只是接受消息,需要的話,給客戶返回一個結果就可以了 16.3.7 創建一個UDP客戶端 cs = socket()#創建客戶套接字 comm_loop:#通訊循環 cs.sendto()/cs.recvfrom()#對話(發送/接收) cs.close()#關閉客戶套接字 在套接字對象創建好之後,我們就進入一個與伺服器的對話循環,在通訊結束後,套接字就被關閉了 例,創建一個UDP客戶端,程式會提示用戶輸入要傳給伺服器的資訊,顯示伺服器返回的加了時間戳的結果 # vi tsUclnt.py ------------------------------------- #!/usr/bin/env python from socket import * from time import ctime HOST = '' PORT = 21568 BUFSIZ = 1024 ADDR = (HOST,PORT) udpSerSock = socket(AF_INET, SOCK_DGRAM) udpSerSock.bind(ADDR) while True: print 'waiting for message...' data, addr = udpSerSock.recvfrom(BUFSIZ) udpSerSock.sendto('[%s] %s' %(ctime(), data),addr) print '...received from and returned to:', addr print data udpSerSock.close() ------------------------------------- # vi tsUclnt.py ----------------------------------------- #!/usr/bin/env python from socket import * HOST = '192.168.8.18' PORT = 21568 BUFSIZ = 1024 ADDR = (HOST, PORT) udpCliSock = socket(AF_INET,SOCK_DGRAM) while True: data = raw_input('> ') if not data: break udpCliSock.sendto(data, ADDR) data, ADDR = udpCliSock.recvfrom(BUFSIZ) if not data: break print data udpCliSock.close() -----------------------------------------
16.3.8 執行UDP伺服器和客戶端
# python tsUserv.py --------------------------------- waiting for message... ---------------------------------- # python tsUclnt.py --------------------------------- > hi [Thu Dec 19 17:25:01 2013] hi > test1 [Thu Dec 19 17:25:03 2013] test1 > test2 [Thu Dec 19 17:25:05 2013] test2 ---------------------------------- # python tsUserv.py ---------------------------------- waiting for message... ...received from and returned to: ('192.168.8.19', 46359) hi waiting for message... ...received from and returned to: ('192.168.8.19', 46359) test1 waiting for message... ...received from and returned to: ('192.168.8.19', 46359) test2 waiting for message... -----------------------------------
16.3.9 套接字模組屬性
屬性名字描述
AF_UNIX,AF_INET,AF_INET6Python支援的套接字家族
SO_STREAM,SO_DGRAM套接字類型(TCP=流,UDP=數據報)
has_ipv6標識是否支援IPV6的標誌變數
異常
error套接字相關錯誤
herror主機和地址相關的錯誤
gaierror地址相關錯誤
timeout超時
函數
socket()用指定的地址家族,套接字類型和協議類型(可選)創建一個套接字對象
socketpair()用指定的地址家族,套接字類型和協議類型(可選)創建一個套接字對象
fromfd()用一個已經打開的額文件描述符創建一個套接字對象
數據屬性
ssl()在套接字初始化一個安全套接字層(SSL),不做整數驗證
getaddrinfo()得到地址資訊
getfqdn()返回完整的域的名字
gethostname()得到當前主機名
gethostbyname()由主機名得到對應的ip地址
gethostbyname()_ex()gethostbyname()擴展版本,返回主機名,主機所有的別名和IP地址列表
gethostbyaddr()由IP地址得到DNS資訊,返回一個類似gethostbyname()_ex()的三元組
getprotobyname()由協議名(如"tcp")得到對應的號碼
getservbyname()/由服務名得到對應的埠號或相反
getservbyport()兩個函數中,協議名都是可選的
ntohl()/htohs()把一個整數由網路位元組序轉為主機位元組序
htonl()/htons()把一個整數由主機位元組序轉為網路位元組序
inet_aton()/把IP地址轉為32位×××,以及反向函數(僅對IPV4有效)
inet_ptoa()
inet_pton()/把IP地址轉為二進位格式以及反響函數(僅對IPV4有效)
inet_ntop()
getdefaulttimeout()/得到/設置默認的套接字超時時間,單位秒(浮點數)
setdefaulttimeout()
16.4 SocketServer模組
SocketServer是標準庫中一個高級別的模組,用於簡化網路客戶與伺服器的實現,模組中,已經實現了一些可供使用的類
SocketServer模組的類
類描述
BaseServer包含伺服器的核心功能與混合(mix-in)類的鉤子功能,這個類用於派生,不要直接生成
這個類的類對象,可以考慮使用TCPServer和IDPServer
TCPServer/基本的網路同步TCP/UDP伺服器
UDPServer
UnixStreamServer/基本的基於文件同步TCP/UDP伺服器
UnixDatagramServer
ForkingMixIn/實現了核心的進程化或執行緒化的功能,用於與伺服器類進行混合(mix-in),以提供一些非同步特性.
ThreadingMixIn不要直接生成這個類的對象
ForkingTCPServer/ForkingMixIn和TCPServer/UDPServer的組合
ForkingUDPServer
ThreadingTCPServer/ThreadingMixIn和TCPServer/UDPServer組合
ThreadingUDPServer
BaseRequestHandler包含處理服務請求的核心功能,只用於派生新的類,不要直接生成這個類的對象
可以考慮使用StreamRequestHandler或DatagramRequestHandler
StreamRequestHandler/TCP/UDP伺服器的請求處理類的一個實現
DatagramRequestHandler
16.4.1 創建一個SocketServerTCP伺服器
使用SocketServer里的TCPServer和StreamRequestHandler類創建一個時間戳TCP伺服器
# vi tsTservSS.py --------------------------------------------- #!/usr/bin/env python from SocketServer import (TCPServer as TCP,StreamRequestHandler as SRH) from time import ctime HOST = '' PORT = 21567 ADDR = (HOST, PORT) class MyRequestHandler(SRH): def handle(self): print "...connected from: ", self.client_address self.wfile.write('[%s] %s'% (ctime(), self.rfile.readline())) tcpServ = TCP(ADDR, MyRequestHandler) print 'waiting for connection...' tcpServ.serve_forever() ---------------------------------------------
16.4.2 創建SocketServerTCP客戶端
例,這是一個時間戳TCP客戶端,它知道如何與SocketServer里StreamRequestHandler對象進行
# vi tsTclntSS.py ------------------------------------ #!/usr/bin/env python from socket import * HOST = 'localhost' PORT = 21567 BUFSIZ = 1024 ADDR = (HOST, PORT) while True: tcpCliSock = socket(AF_INET, SOCK_STREAM) tcpCliSock.connect(ADDR) data = raw_input('> ') if not data: break tcpCliSock.send('%srn' % data) data = tcpCliSock.recv(BUFSIZ) if not data: break print data.strip() tcpCliSock.close() ------------------------------------
16.4.2 執行TCP伺服器和客戶端
# python tsTservSS.py --------------------------------------- waiting for connection... ...connected from: ('127.0.0.1', 40712) ...connected from: ('127.0.0.1', 40713) ...connected from: ('127.0.0.1', 40714) ...connected from: ('127.0.0.1', 40715) ...connected from: ('127.0.0.1', 40716) ...connected from: ('127.0.0.1', 40717) ---------------------------------------- # python tsTclntSS.py --------------------------------------- > test1 [Fri Dec 20 04:44:02 2013] test1 > test2 [Fri Dec 20 04:44:16 2013] test2 > test3 [Fri Dec 20 04:44:18 2013] test3 ----------------------------------------
注:連接伺服器連接了2次
16.5 Twisted框架介紹
Twisted是一個完全事件驅動的網路框架,它允許你使用和開發完全非同步的網路應用程式和協議
16.5.1 創建一個Twisted Reactor TCP伺服器
例,這是一個使用Twisted Internet類的時間戳TCP伺服器
# vi tsTservTW.py ---------------------------------- #!/usr/bin/env python from twisted.internet import protocol, reactor from time import ctime PORT = 21567 class TSServProtocol(protocol.Protocol): def connectionMade(self): clnt = self.clnt = self.transport.getPeer().host print '...connected from: ',clnt def dataReceived(self,data): self.transport.write('[%s] %s' %(ctime(), data)) factory = protocol.Factory() factory.protocol = TSServProtocol print 'waiting for connection...' reactor.listenTCP(PORT, factory) reactor.run() ----------------------------------
16.5.2 創建一個Twisted Reactor TCP客戶端
# vi tsTclntTW.py -------------------------------- #!/usr/bin/env python from twisted.internet import protocol, reactor HOST = 'localhost' PORT = 21567 class TSClntProtocol(protocol.Protocol): def sendData(self): data = raw_input("> ") if data: print '...sending %s...' % data self.transport.write(data) else: self.transport.loseConnection() def connectionMade(self): self.sendData() def dataReceived(self, data): print data self.sendData() class TSClntFactory(protocol.ClientFactory): protocol = TSClntProtocol clientConnectionLost = clientConnectionFailed = lambda self, connector, reason: reactor.stop() reactor.connectTCP(HOST, PORT, TSClntFactory()) reactor.run() --------------------------------
16.5.3 執行TCP伺服器和客戶端
Twisted客戶顯示的內容與我們之前的客戶類似:
# python tsTservTW.py --------------------------- waiting for connection... ---------------------------- # python tsTclntTW.py ----------------------------------- > test1 ...sending test1... [Fri Dec 20 10:36:10 2013] test1 > test2 ...sending test2... [Fri Dec 20 10:36:15 2013] test2 ------------------------------------ # python tsTservTW.py ---------------------------------- waiting for connection... ...connected from: 127.0.0.1 -----------------------------------
注: "connection from" 輸出沒有其他的資訊,因為我們只詢問伺服器的transport對象的getPeer()函數要了主機地址的資訊
16.6 相關模組
網路/套接字編程相關模組
模組描述
socket底層網路介面,本章討論過
anycore/為能非同步處理客戶請求的網路應用程式提供底層功能
select在單執行緒網路伺服器程式中,管理多個套接字連接
SocketServer包含了些網路應用程式伺服器所需要的高級別模組,提供了完整的進程和執行緒版本