十二、Python-網絡編程

一、套接字
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
相關文章
相關標籤/搜索