[PYTHON] 核心編程筆記(16.Python網絡編程)

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('%s\r\n' % 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包含了些網絡應用程序服務器所需要的高級別模塊,提供了完整的進程和線程版本

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章