[PYTHON] 核心編程筆記(16.P

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包含了些網路應用程式伺服器所需要的高級別模組,提供了完整的進程和執行緒版本