16.1 引言python
16.1.1 什麼是客戶端/服務器架構react
服務器是一個軟件或者硬件,用於向一個或多個客戶端(客戶),提供所須要的「服務」。服務器存在的惟一目的就是等待客戶的請求,給這些客戶服務,而後在等待其餘的請求。編程
16.1.2 客戶端/服務器網絡編程服務器
在完成服務以前,服務器必需要先完成一些設置。先要建立一個通信節點,讓服務器能「監聽」請求。網絡
在網絡的世界裏,基本上也就是這樣—一旦通訊端點建立好以後,咱們在監聽的服務器就能夠進入它等待和處理客戶請求的無限循環中了。數據結構
一樣地,服務器在準備好以後,也要通知潛在的客戶,讓他們知道服務器已經準備好處理服務了,不然沒有人會提請求。架構
16.2 套接字:通訊端點框架
套接字是一種具備以前所說的「通訊端點」概念的計算機網絡數據結構。網絡化的應用程序在開始任何通信以前都必須建立套接字。就像電話的插口同樣,沒有它就徹底沒辦法通訊。socket
AF_UNIX, AF_NETLINK,AF_INETtcp
16.2.2 套接字地址:主機與端口
若是把套接字比做電話的插口--即通訊的最底層結構,那主機與端口就像區號與電話號碼的一對組合。
合法的端口號範圍爲0-65535.其中小於1024的端口號爲系統保留端口。
16.2.3 面向鏈接與無鏈接
1.面向鏈接
面向鏈接的套接字,即在通訊以前必定要創建一條鏈接。這種通訊方式也被稱爲「虛電路」或「流套接字」。面向鏈接的通訊方式提供了順序的、可靠的、不會重複的數據傳輸,並且也不會被加上數據邊界。
這也意味着,每一次要發送的信息,可能會被拆分紅多份,每一份都會很少很多地證券到達目的地。而後被從新按順序拼裝起來,傳給正在等待的應用程序。
實現這種鏈接的主要協議就是傳輸控制協議(即TCP)。要建立TCP套接字就得在建立的時候指定套接字類型爲SOCK_STREAM。
TCP套接字採用SOCK_STREAM,表達了它做爲流字套接字的特色。因爲這些套接字使用網際協議(IP)來查找網絡中的主機,因此這樣造成的整個系統,通常會由這兩個協議(TCP/IP)名的組合來描述,即TCP/IP
2.無鏈接
與虛電路徹底相反的是數據報型的無鏈接嵌套字。這意味着,無需鏈接就能夠進行通信,但這時,數據到達的順序、可靠性及不重複性就沒法保證。數據報保留數據邊界,數據即是整個發送。
使用數據報來傳輸數據,不必定按照它們發送的順序到達。甚至到達不了。
優勢是數據報不須要維持虛電路鏈接,一般能維持更好的性能,更適應某些應用場合
實現這種鏈接的主要協議就是用戶數據報協議(即UDP)。要建立UDP套接字就得在建立的時候指定套接字類型爲SOCK_DGRAM。UDP/IP
16.3 Python中的網絡編程
16.3.1socket()模塊函數
socket(socket_family, socket_type, protocol=0)
socket_family不是AF_VNIX就是AF_INET
socket_type能夠是SOCK_STREAM或者SOCK_DGRAM
protocol通常爲0
tcpsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
一樣的,建立一個UDP/IP套接字
upstock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
使用from socket import* 縮短代碼
16.3.2套接字對象(內建)方法
16.3.3建立一個TCP服務器
SocketServer模塊是一個基於socket模塊的高級別的套接字通信模塊,它支持新的線程中處理客戶端的請求。
#-*-coding:utf-8-*- from socket import * from time import ctime HOST = '' PORT = 20000 BUFSIZ = 1024 ADDR = (HOST, PORT) tcpSerSock = socket(AF_INET, SOCK_STREAM) tcpSerSock.bind(ADDR) tcpSerSock.listen(5) while True: print 'waiting for connecting...' tcpCliSock, addr = tcpSerSock.accept() print '...connected from:', addr while True: data = tcpCliSock.recv(BUFSIZ) if not data: break tcpCliSock.send('[%s] %s' %(ctime(), data)) tcpCliSock.close() tcpSerSock.close()
16.3.4 建立TCP客戶端
from socket import * HOST = 'localhost' PORT = 20000 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()
「友好地」退出的一個方法就是把服務器的無限循環放在一個try-except 語句的try 子句當中,並捕獲EOFError 和KeyboardInterrupt 異常。在異常處理子句中,調用close()函數關閉服務器的套接字。
16.3.5 運行咱們的客戶端與TCP服務器
16.3.6 建立一個UDP服務器
#-*-coding:utf-8-*- from socket import * from time import ctime HOST = '' PORT = 20000 BUFSIZ = 1024 ADDR = (HOST, PORT) udpSerSock = socket(AF_INET, SOCK_DGRAM) udpSerSock.bind(ADDR) while True: print 'waiting for connecting...' data, addr = udpSerSock.recvfrom(BUFSIZ) udpSerSock.sendto('[%s] %s' %(ctime(), data),addr) print '...received from and return to:', addr udpSerSock.close()
16.3.7 建立一個UDP客戶端
from socket import * HOST = 'localhost' PORT = 20000 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服務器和客戶端
16.3.9 Socket模塊屬性
數據屬性
AF_UNIX,AF_INET,AF_INET6
SO_STREAM,SO_DGRAM
異常
error 套接字相關錯誤
herror 主機和地址相關的錯誤
gaierror 地址相關的錯誤
timeout 超時
函數
socket() 用指定的地址家族,套接字類型,和協議類型建立一個套接字對象
16.4 SocketServer模塊
SocketSever是標準庫中一個高級別的模塊,用於簡化實現網絡客戶端和服務器所需的大量的樣板代碼。該模塊中,已經實現了一些可供使用的類。
16.4.1建立一個SocketServerTCP服務器
#-*-coding:utf-8-*- from SocketServer import (TCPServer as TCP, StreamRequestHandler as SRH) from time import ctime HOST = '' PORT = 20000 ADDR = (HOST, PORT) class MyRequestHander(SRH): def handle(self): print '...connected from:', self.client_address self.wfile.write('[%s] %s' %(ctime(),self.rfile.readline())) #建立TCP服務器 tcpServ = TCP(ADDR, MyRequestHander) print 'waiting for connection' tcpServ.serve_forever()
16.4.2建立SocketServerTCP客戶端
from socket import * HOST = 'localhost' PORT = 20000 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.3 執行TCP服務器和客戶端
16.5 Twisted 框架介紹
16.5.1建立一個Twisted Reactor TCP服務器
#-*-coding:utf-8-*- from twisted.internet import protocol,reactor from time import ctime PORT = 21332 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客戶端
from twisted.internet import protocol, reactor HOST = 'localhost' PORT = 21332 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執行
16.6 相關模塊
16.7 練習
16-1 套接字。面向鏈接和無鏈接有什麼區別?
面向鏈接是順序的、可靠的、不會重複的數據傳輸
無鏈接中數據到達的順序,可靠性及重複性沒法保證
16-2 客戶端/服務器架構。用你本身的語言描述這個架構,並給出幾個例子。
服務器就是用來等待客戶的請求,服務客戶,客戶端就是請求鏈接服務器,發送數據
16-3套接字。TCP 和UDP 中,哪種服務器在接受鏈接後,把鏈接交給不一樣的套接字處理與客戶的通信。
TC
16-4 修改TCP(tsTclnt.py)和UDP(tsUclnt.py)客戶端,讓服務器的名字不要在代碼裏寫死,要容許用戶指定一個主機名和端口,只有在兩個值都沒有輸入的時候,才使用默認值。
#-*-coding:utf-8-*- from socket import * from time import ctime HOST = raw_input("Please input the host:") PORT = int(raw_input("please enter the port:")) BUFSIZ = 1024 ADDR = (HOST, PORT) tcpSerSock = socket(AF_INET, SOCK_STREAM) tcpSerSock.bind(ADDR) tcpSerSock.listen(5) while True: print 'waiting for connecting...' tcpCliSock, addr = tcpSerSock.accept() print '...connected from:', addr while True: data = tcpCliSock.recv(BUFSIZ) if not data: break tcpCliSock.send('[%s] %s' %(ctime(), data)) tcpCliSock.close() tcpSerSock.close()
from socket import * HOST = raw_input("Please input the host:") PORT = int(raw_input("please input the port:")) 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-6 日期時間服務。使用socket.getservbyname()函數獲得UDP 協議中,「daytime」服務所對應的端口。請參考getservbyname() 函數的文檔, 查閱詳細的語法。( 即:socket.getservbyname.__doc__)。如今,寫一個程序發送一個隨便什麼數據過去,等待回答。一旦你收到了服務器的信息,顯示到屏幕上。
getservbyname("daytime","udp") 運行顯示錯誤permission denied.
16-7 半雙工聊天。建立一個簡單的,半雙工的聊天程序。「半雙工」的意思是當建立一個鏈接,服務啓動的時候,只有一我的能夠打字,另外一我的只有在等到有消息通知他輸入消息時,才能說話。一旦消息發送出去後,要等到有回覆了才能發送下一條消息。一我的是服務端,另外一我的是客戶端。
#-*-coding:utf-8-*- from socket import * from time import ctime HOST = '' PORT = 20000 BUFSIZ = 1024 ADDR = (HOST, PORT) tcpSerSock = socket(AF_INET, SOCK_STREAM) tcpSerSock.bind(ADDR) tcpSerSock.listen(5) while True: print 'waiting for connecting...' tcpCliSock, addr = tcpSerSock.accept() print '...connected from:', addr while True: data = tcpCliSock.recv(BUFSIZ) if not data: break print data data1 = raw_input(">>") tcpCliSock.send(data1) tcpCliSock.close() tcpSerSock.close()
from socket import * HOST = 'localhost' PORT = 20000 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()