14.1.1 socket子產品
在網絡程式設計中德一個基本元件就是套接字。套接字主要是兩個程式之間的資訊通道。
套接字包括兩個:伺服器套接字和客戶機套接字。建立一個伺服器套接字後,讓它等待連接配接。這樣它就在某個網絡位址處監聽。
一個套接字就是一個socket子產品中socket類的執行個體。它的執行個體化需要3個參數:第一個參數是位址族(預設是socket.AF_INET);第2個參數是流(socket.SOCK_STREAM,預設值)或資料報(socket.SOCK_DGRAM)套接字。第三個參數是使用的協定(預設是0)。
伺服器端套接字使用bind方法後,再調用listen方法去監聽這個給定的位址。用戶端套接字使用connect方法連接配接到伺服器,在connect方法中使用的位址與bind方法中的位址相同。在這種情況下,一個位址就是一個格式為(host,port)的元組,其中host是主機名,port是端口号。listen方法隻有一個參數,即伺服器未處理的連接配接的長度。
伺服器端套接字開始監聽後,它就可以接受用戶端的連接配接。這個步驟使用accept方法來完成。這個方法會阻塞直到用戶端連接配接,然後該方法就傳回一個格式為(client,address)的元組,client是一個用戶端套接字,address是一個前面解釋過的位址。伺服器能處理用戶端到它滿意的程度,然後調用另一個accept方法開始等待下一個連接配接。
套接字有兩個方法:send和recv(勇于接收),用于傳輸。可以使用字元串參數調用send以發送資料,用一個所需的位元組數做參數調用recv來接收資料。如果不能确定使用哪個數字比較好,那麼1024.
一個小型伺服器
import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.bind((host,port))
s.listen(5)
while True:
c, addr = s.accept()
print 'Got connection from', addr
c.send('Thank you for connecting')
c.close()
一個小型客戶機
s = socket.gethostname()
port = 1234
s.connect((host,port))
print s.recv(1024)
14.1.2 urllib和urllib2子產品
在能使用的各種網絡工作庫中,功能最強大的是urllib和urllib2.它們能讓通過網絡通路檔案,就像那些檔案存在于你的電腦上一樣。通過一個簡單的函數調用,幾乎可以把任何URL所指向的東西用做程式的輸入。
這兩個子產品的功能差不多,但urllib2更好一些。如果需要使用http驗證或者cookie或者要為自己的協定寫擴充程式的話,那麼urllib2是好的選擇。
1.打開遠端檔案
可以像打開本地一樣打開遠端檔案,不同之處是可以使用隻讀模式,使用的是來自urllib子產品的uriopen,而不是open
>>>from urllib import urlopen
>>>webpage = urlopen('http://www.python.org')
urlopen傳回的類檔案對象支援close,read,readline,readlines方法,當然也支援疊代。
假設想要提取在前面打開的python頁中“About”連結的URL,那麼就可以用正規表達式來實作。
>>>import re
>>>text = webpage.read()
>>>m = re.search('<a href="([^"]+)" .?>about</a>',text,re.IGNORECASE)
>>>m.group(1)
'/about/'
2.擷取遠端檔案
函數urlopen提供一個能從中讀取資料的類檔案對象。如果希望urllib為你下載下傳檔案并在本地檔案中存儲一個檔案的副本,那麼可以使用urlretrieve。urlretrieve傳回一個元組(filename,headers)而不是類檔案對象,filename是本地檔案的名字(由urllib自動建立),headers包含一些遠端檔案的資訊,如果想要為下載下傳的副本指定檔案名,可以在urlretrieve函數的第2個參數中給出。
urlretrieve('http://www.python.org','C:\\python_webpage.html')
這個語句擷取python的首頁并把它存儲在html中,如果沒有指定檔案名,檔案就會放在臨時的位置,用open函數可以打開它,但如果完成了對它的操作,就可以删除它以節省硬碟空間。要清理臨時檔案,可以調用urlcleanup函數,但不要提供參數,該函數會負責清理工作。
14.2 SocketServer和它的朋友們
SocketServer子產品是标準庫中很多伺服器架構的基礎,這些伺服器架構包括BaseHTTPServer,SimpleHTTPServer,CGIHTTPServer,SimpleXMLRPCServer和DocXMLRPCServer,所有的這些伺服器架構都為基礎伺服器增加了特定的功能。
SocketServer包含了4個基本的類:針對TCP套接字流的TCPServer;針對UDP資料報套接字的UDPServer;以及針對性不強的UnixStreamServer和UnixDatagramServer。可能不會用到後3個。
為了寫一個使用SocketServer架構的伺服器,大部分代碼會在一個請求處理程式,每當伺服器收到一個請求時,就會執行個體化一個請求處理程式,并且它的各種處理方法會在處理請求時被調用。具體調用哪個方法取決于特定的伺服器和使用的處理程式類,這樣可以把它們子類化,使得伺服器調用自定義的處理程式集。基本的BaseRequestHandler類把所有的操作都放到了處理器的一個叫做handle的方法中,這個方法會被伺服器調用。然後這個方法就會通路屬性self.request中的用戶端套接字。如果使用的是流,那麼可以使用StreamRequestHandler類,建立了其他兩個新屬性,self.rfile(用于讀取)和self.wfile(用于寫入)然後就能使用這些類檔案對象和客戶機進行通信。
SocketServer架構中德其他類實作了對HTTP伺服器的基本支援,其中包括運作CGI腳本。
一個基于SocketServer的小型伺服器
from SocketServer import TCPServer,StreamRequestHandler
class Handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
self.wfile.write('Thank you for connecting')
server = TCPServer(('',1234),Handler)
server.serve_forever()
14.3 多連接配接
到目前為止讨論的伺服器解決方案都是同步的:即一次隻能連結一個客戶機并處理它的請求。如果每個請求隻是花費很少的時間,比如,一個完整的聊天會話,那麼同時能處理多個連接配接就很重要。
有3種主要的方法能實作這個目的:分叉、線程以及異步I/O。通過對SocketServer伺服器使用混入類,派生程序和線程很容易處理。
分叉占用資源,并且如果有太多的用戶端時分叉不能很好分叉
線程處理能導緻同步問題。
Twisted是一個非常強大的異步網絡變成架構。
14.3.1 使用SocketServer進行分叉和線程處理
windows不支援分叉
使用了分叉技術的伺服器
from SocketServer import TCPServer,ForkingMinIn,StreamRequestHandler
class Server(ForkingMixIn,TCPServer): pass
server = Server(('',1234),Handler)
一個使用了線程處理的伺服器
from SocketServer import TCPServer,ThreadingMixIn,StreamRequestHandler
class Server(ThreadingMixIn,TCPServer): pass
print 'Got connection from',addr
14.3.2 帶有select和poll的異步I/O
當一個伺服器與一個用戶端通信時,來自用戶端的資料可能是不連續的。如果使用分叉或線程處理,那就不是問題。當一個程式在等待資料,另一個并行的程式可以繼續處理它們自己的用戶端。另外的處理方法是隻處理在給定時間内真正要進行通信的用戶端。不需要一直監聽----隻要監聽一會兒,然後把它放到其他用戶端的後面。
這是asyncore/asynchar架構和Twisted架構采用的方法,這種功能的基礎是select函數,如果poll函數可用,那也可以是它,這兩個函數都來自select子產品,這兩個函數中,poll的伸縮性更好,但它隻能在unix系統中使用(windows不可用)。
select函數需要3個序列作為它的必選參數,此外還有一個可選的以秒為機關的逾時時間作為第4個參數。這些序列式檔案描述符整數。這些就是我們等待的連接配接。3個序列用于輸入、輸出、以及異常情況。如果沒有給定逾時時間,select會阻塞,直到其中的一個檔案描述符已經為行動做好了準備;如果給定了逾時時間,select最多阻塞給定的逾時時間,如果給定的逾時時間是0,那麼就給出了一個連續的poll(即不阻塞)。select的傳回值是3個序列,每個代表相應參數的一個活動子集。比如傳回的第1個序列是一個輸入檔案描述符的序列,其中有一些可以讀取的東西。
序列能包含檔案對象(在windows中行不通)或者套接字。伺服器是個簡單的記錄器,它輸出來自客戶機的所有資料。可以使用Telnet連接配接它來進行測試。嘗試用多個Telnet去連接配接來驗證伺服器能同時為多個用戶端服務。
使用了select的簡單伺服器
import socket,select
inputs = [s]
rs.ws,es = select.select(inputs,[],[])
for r in rs:
if r is s:
print 'Got connection from' , addr
inputs.append(c)
else:
try:
data = r.recv(1024)
disconnected = not data
except socket.error:
disconnected = True
if disconnected:
print r.getpeername(),'disconnected'
inputs.remove(r)
print data
poll方法使用起來比select簡單。在調用poll時,會得到一個poll對象。然後就可以使用pol對象的register方法注冊一個檔案描述符。注冊後就可以使用unregister方法移除注冊的對象。注冊了一些對象以後,就可以調用poll方法并得到一個格式清單,其中fd是檔案描述符,event則告訴你發生了什麼。這是一個位掩碼,意思是它是一個整數,這個整數的每個位對應不同的事件。那些不同的事件是select子產品的常量。
使用poll的簡單伺服器
import socket.select
fdmap = {s.fileno(): s}
p = select.poll()
p.register(s)
events = p.poll()
for fd, event in events:
if fd = s.fileno():
c,addr = s.accept()
p.register(c)
fdmap[c.fileno()] = c
elif event & select.POLLIN:
data = fdmap[fd].recv(1024)
if not data:
print fdmap[fd].getpeername(),'disconnected'
p.unregister(fd)
del fdmap[fd]
14.4 Twisted
from twisted.internet import reactor
from twisted.internet.protocol import Protocol.Factory
class SimpleLogger(Protocol):
def connectionMade(self):
print 'Got connection from',self.transport.client
def connectionLost(self,reason):
print self.transport.client,'disconnected'
def dataReceived(self,data):
factory = Factory()
factory.protocol = Simplelogger
reactor.listenTCP(1234,factory)
reactor.run()
如果用telnet 連接配接到此伺服器并進行測試的話,那麼每行可能隻輸出一個字元取決于緩沖或類似的東西。當然可以使用sys.stdout.write來代替print。
一個使用了LineReceiver協定改進的記錄伺服器
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
class SimpleLogger(LineReceiver):
def SimpleLogger(LineReceiver):
def lineReceived(self,line):
print line
factory.protocol = SimpleLogger
本文轉自潘闊 51CTO部落格,原文連結:http://blog.51cto.com/pankuo/1661448,如需轉載請自行聯系原作者