1、客戶端/服務器架構
什么是客戶端/服務器架構?對於不同的人來說,它意味着不同的東西,這取決於你問誰以及描述的是軟件還是硬件系統。在這兩種情況中的任何一種下,前提都很簡單:服務器就是一系列硬件或軟件,為一個或多個客戶端(服務的用戶)提供所需的“服務”。它存在唯一目的就是等待客戶端的請求,並響應它們(提供服務),然后等待更多請求。另一方面,客戶端因特定的請求而聯系服務器,並發送必要的數據,然后等待服務器的回應,最后完成請求或給出故障的原因。服務器無限地運行下去,並不斷地處理請求;而客戶端會對服務進行一次性請求,然后接收該服務,最后結束它們之間的事務。客戶端在一段時間后可能會再次發出其他請求,但這些都被當作不同的事務。
2、客戶端/服務器編程
2.1套接字
套接字的起源可以追溯到20 世紀70 年代,它是加利福尼亞大學的伯克利版本UNIX(稱為BSD UNIX)的一部分。因此,有時你可能會聽過將套接字稱為伯克利套接字或BSD 套接字。套接字最初是為同一主機上的應用程序所創建,使得主機上運行的一個程序(又名一個進程)與另一個運行的程序進行通信。這就是所謂的進程間通信(Inter Process Communication,IPC)。有兩種類型的套接字:基於文件的和面向網絡的。UNIX 套接字是我們所講的套接字的第一個家族,並且擁有一個“家族名字”AF_UNIX(又名AF_LOCAL,在POSIX1.g 標准中指定),它代表地址家族(address family):UNIX。包括Python 在內的大多數受歡迎的平台都使用術語地址家族及其縮寫AF;其他比較舊的系統可能會將地址家族表示成域(domain)或協議家族(protocol family),並使用其縮寫PF 而非AF。類似地,AF_LOCAL(在2000~2001 年標准化)將代替AF_UNIX。然而,考慮到后向兼容性,很多系統都同時使用二者,只是對同一個常數使用不同的別名。Python 本身仍然在使用AF_UNIX。因為兩個進程運行在同一台計算機上,所以這些套接字都是基於文件的,這意味着文件
系統支持它們的底層基礎結構。這是能夠說得通的,因為文件系統是一個運行在同一主機上的多個進程之間的共享常量。
第二種類型的套接字是基於網絡的,它也有自己的家族名字AF_INET,或者地址家族:因特網。另一個地址家族AF_INET6 用於第6 版因特網協議(IPv6)尋址。此外,還有其他的地址家族,這些要么是專業的、過時的、很少使用的,要么是仍未實現的。在所有的地址家族之中,目前AF_INET 是使用得最廣泛的。
Python 2.5 中引入了對特殊類型的Linux 套接字的支持。套接字的AF_NETLINK 家族允許使用標准的BSD 套接字接口進行用戶級別和內核級別代碼之間的IPC。針對Linux 的另一種特性(Python 2.6 中新增)就是支持透明的進程間通信(TIPC)協議。TIPC 允許計算機集群之中的機器相互通信,而無須使用基於IP 的尋址方式。Python 對TIPC 的支持以AF_TIPC 家族的方式呈現。
2.2套接字地址:主機-端口對
有效的端口號范圍為0~65535(盡管小於1024 的端口號預留給了系統)。
2.3面向連接的套接字
TCP 套接字,必須使用SOCK_STREAM 作為套接字類型。
2.4無連接的套接字
實現這種連接類型的主要協議是用戶數據報協議(更為人熟知的是其縮寫UDP)。為了創建UDP 套接字,必須使用SOCK_DGRAM 作為套接字類型。
3、python中的網絡編程
3.1socket 模塊
socket模塊屬性
套接字創建:
socket(socket_family, socket_type, protocol=0) tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ud pSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
socket對象方法:
服務器創建:
ss = socket() # 創建服務器套接字 ss.bind() # 套接字與地址綁定 ss.listen() # 監聽連接 inf_loop: # 服務器無限循環 cs = ss.accept() # 接受客戶端連接 comm_loop: # 通信循環 cs.recv()/cs.send() # 對話(接收/發送) cs.close() # 關閉客戶端套接字 ss .close() # 關閉服務器套接字#(可選)
為服務器實現一個智能的退出方案時,建議調用close()方法。
客戶端創建:
cs = socket() # 創建客戶端套接字 cs.connect() # 嘗試連接服務器 comm_loop: # 通信循環 cs.send()/cs.recv() # 對話(發送/接收) cs .close() # 關閉客戶端套接字
I/O多路復用
舉一個例子,模擬一個tcp服務器處理30個客戶socket。
假設你是一個老師,讓30個學生解答一道題目,然后檢查學生做的是否正確,你有下面幾個選擇:
1. 第一種選擇: 按順序逐個檢查,先檢查A,然后是B,之后是C、D。。。這中間如果有一個學生卡主,全班都會被耽誤。這種模式就好比,你用循環挨個處理socket,根本不具有並發能力。
2. 第二種選擇:你 創建30個分身,每個分身檢查一個學生的答案是否正確。 這種類似於為每一個用戶創建一個進程或者線程處理連接。
3. 第三種選擇,你 站在講台上等,誰解答完誰舉手。這時C、D舉手,表示他們解答問題完畢,你下去依次檢查C、D的答案,然后繼續回到講台上等。此時E、A又舉手,然后去處理E和A。。。
這種就是IO復用模型,Linux下的select、poll和epoll就是干這個的。將用戶socket對應的fd注冊進epoll,然后epoll幫你監聽哪些socket上有消息到達,這樣就避免了大量的無用操作。此時的socket應該采用 非阻塞模式。
這樣,整個過程只在調用select、poll、epoll這些調用的時候才會阻塞,收發客戶消息是不會阻塞的,整個進程或者線程就被充分利用起來,這就是 事件驅動,所謂的reactor模式。

方法:
windows python:
提供: select
Mac Python:
提供: select
Linux Python:
提供: select、poll、epoll
服務器:
#!/usr/bin/python 'test TCP server' from socket import * from time import ctime import select import sys HOST = '' PORT = 21567 BUFSIZ = 1024 ADDR = (HOST, PORT) tcpSerSock = socket(AF_INET, SOCK_STREAM) tcpSerSock.bind(ADDR) tcpSerSock.listen(5) input = [tcpSerSock, sys.stdin] #input是一個列表,初始有歡迎套接字以及標准輸入 while True: print 'waiting for connection...' tcpCliSock, addr = tcpSerSock.accept() print '...connected from:',addr input.append(tcpCliSock) #將服務套接字加入到input列表中 while True: readyInput,readyOutput,readyException = select.select(input,[],[]) #從input中選擇,輪流處理client的請求連接(tcpSerSock),client發送來的消息(tcpCliSock),及服務器端的發送消息(stdin) for indata in readyInput: if indata==tcpCliSock: #處理client發送來的消息 data = tcpCliSock.recv(BUFSIZ) print data if data=='88': input.remove(tcpCliSock) break else: #處理服務器端的發送消息 data = raw_input('>') if data=='88': tcpCliSock.send('%s' %(data)) input.remove(tcpCliSock) break tcpCliSock.send('[%s] %s' %(ctime(), data)) if data=='88': break tcpCliSock.close() tcpSerSock.close()
客戶端:
#!/usr/bin/python 'test tcp client' from socket import * from time import ctime import select import sys HOST = 'localhost' PORT = 21567 BUFSIZ = 1024 ADDR = (HOST, PORT) tcpCliSock = socket(AF_INET, SOCK_STREAM) tcpCliSock.connect(ADDR) input = [tcpCliSock,sys.stdin] while True: readyInput,readyOutput,readyException = select.select(input,[],[]) for indata in readyInput: if indata==tcpCliSock: data = tcpCliSock.recv(BUFSIZ) print data if data=='88': break else: data = raw_input('>') if data=='88': tcpCliSock.send('%s' %(data)) break tcpCliSock.send('[%s] %s' %(ctime(), data)) if data=='88': break tcpCliSock.close()
3.2 socketserver 模塊
SocketServer內部使用 IO多路復用 以及 “多線程” 和 “多進程” ,從而實現並發處理多個客戶端請求的Socket服務端。即:每個客戶端請求連接到服務器時,Socket服務端都會在服務器是創建一個“線程”或者“進程” 專門負責處理當前客戶端的所有請求。
該模塊中的類
基本用法:
創建SocketServer TCP 服務器
# 請求處理程序MyRequestHandler,作為SocketServer # 中StreamRequestHandler 的一個子類,並重寫了它的handle()方法,該方法在基類Request 中 # 默認情況下沒有任何行為。 # def handle(self): # pass # 當接收到一個來自客戶端的消息時,它就會調用handle()方法。而StreamRequestHandler # 類將輸入和輸出套接字看作類似文件的對象,因此我們將使用readline()來獲取客戶端消息, # 並利用write()將字符串發送回客戶端。 from socketserver import TCPServer as TCP,StreamRequestHandler as SRH from time import ctime HOST='' PORT='21567' ADDR=(HOST,PORT) class MyRequestHandle(SRH): def handle(self): print('...connected from:{0}',self.client_address) self.wfile.write('[%s]%s'%(ctime(),self.rfile.readline())) tcpSever=TCP(ADDR,MyRequestHandle) print('waiting for connection') tcpSever.serve_forever()
創建TCP客戶端
from socket import * HOST='localhost' PORT='21567' BUFSIZ=1024 ADDR=(HOST,PORT) while True: tcpClisocket=socket(ADDR,SOCK_STREAM) tcpClisocket.connect(ADDR) data=input('>') if not data: break # 因為這里使用的處理程序類對待套 # 接字通信就像文件一樣,所以必須發送行終止符(回車和換行符) # 而服務器只是保留並重用這里發送的終止符。當得到從服務器返回的消息時,用strip() # 函數對其進行處理並使用由print聲明自動提供的換行符。 tcpClisocket.send('%s\r\n'%data) data=tcpClisocket.recv(BUFSIZ) if not data: break print(data.strip()) tcpClisocket.close()
ThreadingTCPServer
ThreadingTCPServer實現的Soket服務器內部會為每個client創建一個 “線程”,該線程用來和客戶端進行交互。
參照:http://www.cnblogs.com/wupeiqi/articles/5040823.html
- 創建一個繼承自 SocketServer.BaseRequestHandler 的類
- 類中必須定義一個名稱為 handle 的方法
- 啟動ThreadingTCPServer
# 啟動服務端程序 # 執行 TCPServer.__init__ 方法,創建服務端Socket對象並綁定 IP 和 端口 # 執行 BaseServer.__init__ 方法,將自定義的繼承自SocketServer.BaseRequestHandler 的類 MyRequestHandle賦值給 self.RequestHandlerClass # 執行 BaseServer.server_forever 方法,While 循環一直監聽是否有客戶端請求到達 ... # 當客戶端連接到達服務器 # 執行 ThreadingMixIn.process_request 方法,創建一個 “線程” 用來處理請求 # 執行 ThreadingMixIn.process_request_thread 方法 # 執行 BaseServer.finish_request 方法,執行 self.RequestHandlerClass() 即:執行 自定義 MyRequestHandler 的構造方法(自動調用基類BaseRequestHandler的構造方法,在該構造方法中又會調用 MyRequestHandler的handle方法) # 服務器 # #!/usr/bin/env python # # -*- coding:utf-8 -*- import SocketServer class MyServer(SocketServer.BaseRequestHandler): def handle(self): # print self.request,self.client_address,self.server conn = self.request conn.sendall('歡迎致電 10086,請輸入1xxx,0轉人工服務.') Flag = True while Flag: data = conn.recv(1024) if data == 'exit': Flag = False elif data == '0': conn.sendall('通過可能會被錄音.balabala一大推') else: conn.sendall('請重新輸入.') if __name__ == '__main__': server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer) server.serve_forever()
ForkingTcpSever
ForkingTCPServer和ThreadingTCPServer的使用和執行流程基本一致,只不過在內部分別為請求者建立 “線程” 和 “進程”。
3.3 Twisted模塊
Twisted 是一個完整的事件驅動的網絡框架,利用它既能使用也能開發完整的異步網絡應用程序和協議。它提供了大量的支持來建立完整的系統,包括網絡協議、線程、安全性和身份驗證、聊天/ IM、DBM 及RDBMS 數據庫集成、Web/因特網、電子郵件、命令行參數、GUI 集成工具包等。與SocketServer 類似,Twisted 的大部分功能都存在於它的類中。
Twisted Reactor TCP服務器
基於protocol 類創建TSServProtocol,重寫connectionMade()和dataReceived()方法,當一個客戶端連接到服務器時就會執行connectionMade()方法,而當服務器接收到客戶端通過網絡發送的一些數據時就會調用dataReceived()方法。
reactor 會作為該方法的一個參數在數據中傳輸,這樣就能在無須自己提取它的情況下訪問它。在服務器代碼的最后部分中,創建了一個協議工廠。它之所以被稱為工廠,是因為每次得到一個接入連接時,都能“制造”協議的一個實例。然后在reactor 中安裝一個TCP 監聽器,以此檢查服務請求。當它接收到一個請求時,就會創建一個TSServProtocol 實例來處理那個客戶端的事務。
from twisted.internet import protocol,reactor from time import ctime PORT=21567 class TSServProtocal(protocol.Protocol): def connectionMade(self): clnt=self.clnt=self.transport.getPeer().host print('...conected from:',clnt) def dataReceived(self, data): self.transport.write('[%s]%s'%(ctime(),data)) factory=protocol.Factory() factory.protocol=TSServProtocal print('waiting for conneciton') reactor.listenTCP(PORT,factory) reactor.run()
Twisted Reactor TCP客戶端
from twisted.internet import protocol, reactor HOST='localhost' PORT=21567 class TSClntProtocal(protocol.Protocol): def sendData(self): data=input('>') if data: print('...sending%...'%data) else: self.transport.loseConnection() def connectionMade(self): self.sendData() def dataReceived(self, data): print(data) class TSClntFactory(protocol.Factory): protocol=TSClntProtocal clientConnectionLost=clientConnectionFailed=\ lambda self,connector,reason:reactor.stop() reactor.connectTCP(HOST,PORT,TSClntFactory()) reactor.run()