一、套接字
1.1 socket模塊
套接字是網絡編程中的一個基本組件,通常包括服務器端套接字和客戶端套接字。編程
建立服務器端過程以下:服務器
1 import socket 2 3 s = socket.socket() 4 5 host = socket.gethostname() 6 port = 1234 7 try: 8 s.bind((host, port)) 9 except Exception, e: 10 print e 11 s.close() 12 13 s.listen(5) 14 while True: 15 c, addr = s.accept() 16 print 'Got connection from', addr 17 c.send('Thank you for connecting') 18 c.close()
建立客戶端過程以下:網絡
1 import socket, time 2 3 s = socket.socket() 4 5 host = socket.gethostname() 6 port = 1234 7 8 s.connect((host, port)) 9 print s.recv(1024) 10 while True: 11 command = 'hello\n' 12 s.send(command) 13 time.sleep(1)
該過程的通信方式是一問一答的形式,因此又稱爲阻塞或者同步網絡編程。
1.2 SocketServer和它的朋友們
SocketServer模塊是標準庫中不少服務器框架的基礎,這些服務器框架包括BaseHTTPServer、SimpleHTTPServer、CGIHTTPServer、SimpleXMLRPCServer和DocXMLRPCServer,全部的這些服務器框架都爲基礎服務器增長了特定的功能。
SocketServer包含4個基本的類:針對TCP套接字流的TCPServer;針對UDP數據報套接字的UDPServer;以及針對性不強的UnixStreamServer和UnixDatagramServer。
在使用SocketServer服務器框架時,每當服務器收到一個請求,就會實例化一個請求處理程序,而且它的各類處理方法(handler methods)會在處理請求時被調用。基本的BaseRequestHandler類把全部的操做都放到了處理器的一個叫作handle的方法中,這個方法會被服務器調用。而後在這個方法內就能夠訪問屬性self.request中的客戶端套接字。若是使用的是流(TCPServer),那麼可使用StreamRequestHandler類,建立它的兩個屬性,self.rfile(用於讀取)和self.wfile(用於寫入)。app
1 from SocketServer import TCPServer, StreamRequestHandler 2 3 class Handler(StreamRequestHandler): 4 5 def handle(self): 6 addr = self.request.getpeername() 7 8 print 'Got connection from', addr 9 self.wfile.write('Thank you for connecting') 10 server = TCPServer(('', 1234), Handler) 11 server.serve_forever()
二、多鏈接
同時能夠有多個客戶機鏈接服務端進行請求處理。有3個主要的方法能實現這個目的:分叉(forking)、線程(threading)以及異步I/O(asynchronous I/O)。它們有各自的缺點:分叉佔資源,若是有太多的客戶端時,分叉不能很好地分叉;線程處理能致使同步問題。
什麼是分叉:當分叉一個進程(一個運行的程序)時,基本上是複製了它,而且分叉後的兩個進程都從當前的執行點繼續運行,而且每一個進程都有本身的內存副本(好比變量)。一個進程(原來的那個)成爲父進程,另外一個(複製的)成爲子進程。由於分叉的進程是並行運行的,客戶端之間沒必要相互等待。但分叉有點消耗資源(每一個分叉出來的進程都須要本身的內存),這就存在了另外一個選擇:線程。
什麼是線程:線程是輕量級的進程或子進程,全部的線程都存在於相同的(真正的)進程中,共享內存。資源消耗的降低伴隨着一個缺陷:由於線程共享內存,因此必須確保它們的變量不會衝突,或者是在同一時間修改同一內容,這就會形成混亂。
使用SocketServer框架建立分叉或者線程服務器太簡單了,幾乎不須要解釋。注意,Windows不支持分叉。
使用分叉技術的服務器:框架
1 from SocketServer import TCPServer, ForkingMixIn, StreamRequestHandler 2 3 class Server(ForkingMixIn, TCPServer): pass 4 5 class Handler(StreamRequestHandler): 6 7 def handle(self): 8 addr = self.request.getpeername() 9 print 'Got connection from', addr 10 self.wfile.write('Thank you for connecting') 11 12 server = Server(('', 1234), Handler) 13 server.serve_forever()
使用線程處理的服務器:異步
from SocketServer import TCPServer, ThreadingMixIn, StreamRequestHandler class Server(ThreadingMixIn, TCPServer): pass class Handler(StreamRequestHandler): def handle(self): addr = self.request.getpeername() print 'Got connection from', addr self.wfile.write('Thank you for connecting') server = Server(('', 1234), Handler) server.serve_forever()
什麼是異步I/O:當一個服務器與一個客戶端通訊時,來自客戶端的數據多是不連續的。若是使用分叉或線程處理,那就不是問題。當一個程序在等待數據,另外一個並行的程序能夠繼續處理它們本身的客戶端。另外的處理方法是隻處理在給定時間內真正要進行通訊的客戶端。
這是asyncore/asynchat框架採用的方法,這種功能的基礎是select函數,若是poll函數可用,那也能夠是它,這兩個函數都來自select模塊。其中poll函數的伸縮性要更好,但只能用在UNIX系統中。
Select函數有3個序列做爲它的必選參數,還有一個可選的超時時間做爲第4個參數。這3個序列是套接字文件描述符,用於輸入、輸出以及異常狀況。若是沒有給定超時時間,select會阻塞,處於等待狀態,直到一個文件描述符已經爲行動作好了準備;若是給定超時時間,select最多阻塞給定的超時時間;若是給定的超時時間是0,select就不阻塞。Select的返回值是3個序列,一個長度爲3的元組,每一個表明相應參數的一個活動子集。socket
1 import socket, select 2 3 s = socket.socket() 4 5 host = socket.gethostname() 6 port = 1234 7 s.bind((host, port)) 8 9 s.listen(5) 10 inputs = [s] 11 while True: 12 rs, ws, es = select.select(inputs, [], []) 13 for r in rs: 14 if r is s: 15 c, addr = s.accept() 16 print 'Got connection from', addr 17 inputs.append(c) 18 else: 19 try: 20 data = r.recv(1024) 21 disconnected = not data 22 except socket.error: 23 disconnected = True 24 if disconnected: 25 print r.getpeername(), 'disconnected' 26 inputs.remove(r) 27 else: 28 print data
poll方法使用起來比select簡單。在調用poll時,會獲得一個poll對象。而後使用poll對象的register方法註冊一個文件描述符(或者是帶有fileno方法的對象)。註冊後可使用unregister方法移除註冊的對象。註冊完套接字對象以後能夠調用poll方法(帶有一個可選的超時時間參數)並獲得一個(fd, event)格式列表(多是空的),其中fd是文件描述符,event則告訴你發生了什麼。event對象是一個位掩碼,可使用按位與操做來判斷事件的類型.
select模塊中的polling事件常量
事件名 描述
POLLIN 讀取來自文件描述符的數據
POLLPRI 讀取來自文件描述符的緊急數據
POLLOUT 文件描述符已經準備好數據,寫入時不會發生阻塞
POLLERR 與文件描述符有關的錯誤狀況
POLLHUP 掛起,鏈接丟失
POLLNVAL 無效請求,鏈接沒有打開async
1 import socket, select 2 3 s = socket.socket() 4 5 host = socket.gethostname() 6 port = 1234 7 s.bind((host, port)) 8 9 fdmap = {s.fileno(): s} 10 11 s.listen(5) 12 p = select.poll() 13 p.register(s) 14 while True: 15 events = p.poll() 16 for fd, event in events: 17 if fdmap[fd] is s: 18 c, addr = s.accept() 19 print 'Got connection from', addr 20 p.register(c) 21 fdmap[c.fileno()] = c 22 elif event & select.POLLIN: 23 data = fdmap[fd].recv(1024) 24 if not data: # No data -- connection closed 25 print fdmap[fd].getpeername(), 'disconnected' 26 p.unregister(fd) 27 del fdmap[fd] 28 else: 29 print data