Python 網絡編程說明第2/2頁
更新時間:2009年08月25日 01:10:10 作者:
socket 是網絡連接端點。
例如,對于一個聊天室來說,因為有多個連接需要同時被處理,所以很顯然,阻塞或同步的方法是不合適的,這就像買票只開了一個窗口,佷多人排隊等一樣。那么我們如何解決這個問題呢?主要有三種方法:forking、threading、異步I/O。
Forking和threading的方法非常簡單,通過使用SocketServer服務類的min-in類就可以實現。forking只適用于類Unix平臺;threading需要注意內存共享的問題。
異步I/O如果底層的方法來實現是有點困難的。要簡單點,我們可以考慮使用標準庫中的框架或Twisted(Twisted是一個非常強大的異步網絡編程的框架)。
一、用ScoketServer實現Forking和threading
下面我們使用兩個例子來分別創(chuàng)建forking服務器和threading服務器。
Forking 服務器:
from SocketServer import TCPServer, ForkingMixIn, StreamRequestHandler
class Server(ForkingMixIn, 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()
threading服務器:
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()
二、使用select實現異步I/O
所謂異步I/O,打個比方,就是如果一大群人都想你聽他說話,那么你就給他們每人一分鐘的時間說,大家輪流說,沒說完的待會兒輪到時再繼續(xù)說。也就是一個時間片的方法。
要實現異步I/O,我們可以通過使用框架asyncore/asynchat或Twisted,它們都是基于select函數或poll函數(poll只適于類Unix系統(tǒng))的。select和poll函數都來自select模塊。
select 函數要求三個必須序列作為參數和一個可選的以秒為單位的超時值。序列中是表示文件描述符的整數值,它們是我們要等待的連接。這三個序列是關于輸入、輸出和 異常條件的。如果超時值沒有給出的話,select將處于阻塞狀態(tài)(也就是等待)直到有文件描述符準備動作。如果超時值給出了,那么select只阻塞給 定的時間。如果超時值是0的話,那么將不阻塞。select返回的值是一個由三個序列組成的元組,它們分別代表相應參數的活動的子集。例如,第一個序列返 回的是用于讀的輸入文件描述符構成的序列。
序列可以包含文件對象(不適于Windows)或socket。下面這個例子創(chuàng)建一個使用 select去服務幾個連接的服務器(注意:服務端的socket自身也提供給了select,以便于它能夠在有新的連接準備接受時發(fā)出信號通知)。這個 服務器只是簡單地打印接受自客戶端的數據。你可以使用telnet(或寫一個基于socket的簡單的客戶端)來連接測試它。
select server
import socket, select
s = socket.socket()
host = socket.gethostname()
port = 1234
s.bind((host, port))
s.listen(5)
inputs = [s]
while True:
rs, ws, es = select.select(inputs, [], [])
for r in rs:
if r is s:
c, addr = s.accept()
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)
else:
print data
三、Twisted
Twisted 是針對Python的一個事件驅動的網絡框架,最初是為了網絡游戲而開發(fā)的,但是現在被應用于各類網絡軟件。用Twisted,你可以實現事件處理器,非 常類似用GUI工具包(Tk, GTK, Qt, wxWidgets)。這部分我將介紹一些基本的概念和演示如何使用Twisted來做一些相對簡單的 網絡編程。Twisted是非常強大的框架并提供了大量的支持,如:Web服務器和客戶端、 SSH2, SMTP, POP3, IMAP4, AIM, ICQ, IRC, MSN,Jabber, NNTP, DNS等等。
早先我們所寫的基于socket的服務器,它們都有一個顯示的事件循環(huán):尋找新的連接和新的數據;基于SocketServer的服務器有一個隱含的循環(huán):尋找連接和為連接創(chuàng)建處理器。但時處理器仍然時顯示的讀數據。
而 Twisted使用了更多的基于事件的方式。要寫一個基本的服務器,你要實現事件處理器,它處理諸如一個新的客戶端連接、新的數據到達和客戶端連接中斷等 情況。在Twisted中,你的事件處理器定義在一個protocol中;你也需要一個factory,當一個新的連接到達時它能夠構造這個 protocol對象,但是如果你僅僅想創(chuàng)建一個自定義的Protocol類的實例的話,你可以使用來自Twisted的factory,Factory 類在模塊twisted.internet.protocol中。當你寫你的protocol時,使用 twisted.internet.protocol模塊中的Protocol作為你的父類。當你得到一個連接時,事件處理器 connectionMade被調用;當你丟失了一個連接時,connectionLost被調用。從客戶端接受數據使用處理器 dataReceived。但是你不能使用事件處理策略向客戶端發(fā)送數據;要向客戶端發(fā)送數據,你可以使用self.transport,它有一個 write方法。它也有一個client屬性,其中包含了客戶端的地址(主機名和端口)。
下面這個例子是一個Twisted版的服務器。 其中實例化了Factory并設置了它的protocol屬性以便它知道使用哪個protocol與客戶端通信(這就是所謂的你的自定義 protocol)。然后你使用factory開始監(jiān)聽指定的端口,factory通過實例化的protocol對象處理連接。監(jiān)聽使用reactor模 塊中的listenTCP函數。最后,你通過調用reactor模塊中的run函數來開始服務器。
from twisted.internet import reactor
from twisted.internet.protocol import Protocol, Factory
# 定義你Protocol類
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):
print data
# 實例化Factory
factory = Factory()
# 設置factory的protocol屬性以便它知道使用哪個protocol與客戶端通信(這就是所謂的你的自定義
# protocol)
factory.protocol = SimpleLogger
# 監(jiān)聽指定的端口
reactor.listenTCP(1234, factory)
# 開始運行主程序
reactor.run()
為 你的處理目的而寫一個自定義的protocol是很容易的。模塊twisted.protocols.basic中包含了幾個有用的已存在的 protocol,其中的LineReceiver執(zhí)行dataReceived并在接受到了一個完整的行時調用事件處理器lineReceived。如 果當你在接受數據時除了使用lineReceived,還要做些別的,那么你可以使用LineReceiver定義的名為rawDataReceived 事件處理器。下面是一使用LineReceiver的服務器例子:
from twisted.internet import reactor
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
class SimpleLogger(LineReceiver):
def connectionMade(self):
print 'Got connection from', self.transport.client
def connectionLost(self, reason):
print self.transport.client, 'disconnected'
def lineReceived(self, line):
print line
factory = Factory()
factory.protocol = SimpleLogger
reactor.listenTCP(1234, factory)
reactor.run()
urllib和urllib2
urllib 和urllib2的工作大同小異,它們讓你能夠通過網絡訪問文件,就像訪問自己電腦上的一樣。通過簡單的函數調用,URL所定位的資源就可以被你作為輸入 使用到你的程序中。如果再配以re模塊,那么你就能夠下載Web頁面、提取信息、自動創(chuàng)建你所尋找的東西的報告。
urllib2更流行一些。對于簡單的下載任務,urllib比較好。如果你需要HTTP驗證或cookies,或你想寫一些擴展去處理你自己的協(xié)議的話,那么urllib2是正確的選擇。
一、打開遠程文件
打開遠程文件的操作和本地差不多,不同的是只能使用讀模式,并且使用urllib模塊的urlopen:
>>> from urllib import urlopen
>>> webpage=urlopen('http://www.python.org')
如果你在線的話,變量webpage現在就包含了一個關聯Web頁:http://www.python.org的文件類對象。
注意:如果你當前沒有聯網,而你又想練習一下urllib的話,你可以用如下形式訪問本地文件:
localpage=urlopen(r'file:c:\test.txt')
由urlopen返回的文件類對象支持close,read,readline,readlines等方法。
下面的代碼抽取出了Python官方主頁中“Documentation”鏈接的URL:
>>> import re
>>> text = webpage.read()
>>> m = re.search('<a href="([^"]+)">Documentation</a>', text, re.IGNORECASE)
>>> m.group(1)
'http://docs.python.org/'
二、獲取遠程文件
urlopen 函數給你一個文件類對象,你可以讀取它。如果你使用urlib時只關心下載文件并存儲一個復本到本地文件的話,你可以使用urlretrieve替而代 之。urlretrieve返回一個元組(filename, headers),filename是本地文件(復本)的名字(它由urllib自動創(chuàng) 建),headers包含關于遠程文件的一些信息。
如果你想為復本指定一個名字的話,你可以提供第二個參數:
urlretrieve('http://www.python.org', 'C:\\python_webpage.html')
這 將獲取Python官方主頁并存儲到本地C:\python_webpage.html中。如果你不指定復本的文件名,那么文件將放到一個臨時的地方,你 能夠使用open函數打開它,如果你要清除這些臨時的復本,你可以調用urlcleanup函數而不帶任何參數,它將為你完成清除工作。
一、套接字
套接字是為特定網絡協(xié)議(例如TCP/IP,ICMP/IP,UDP/IP等)套件對上的網絡應用程序提供者提供當前可移植標準的對象。它們允許程序接受并進行連接,如發(fā)送和接受數據。為了建立通信通道,網絡通信的每個端點擁有一個套接字對象極為重要。
套接字為BSD UNIX系統(tǒng)核心的一部分,而且他們也被許多其他類似UNIX的操作系統(tǒng)包括Linux所采納。許多非BSD UNIX系統(tǒng)(如ms-dos,windows,os/2,mac os及大部分主機環(huán)境)都以庫形式提供對套接字的支持。
三種最流行的套接字類型是:stream,datagram和raw。stream和datagram套接字可以直接與TCP協(xié)議進行接口,而raw套接字則接口到IP協(xié)議。但套接字并不限于TCP/IP。
二、套接字模塊
套接字模塊是一個非常簡單的基于對象的接口,它提供對低層BSD套接字樣式網絡的訪問。使用該模塊可以實現客戶機和服務器套接字。要在python 中建立具有TCP和流套接字的簡單服務器,需要使用socket模塊。利用該模塊包含的函數和類定義,可生成通過網絡通信的程序。一般來說,建立服務器連接需要六個步驟。
第1步是創(chuàng)建socket對象。調用socket構造函數。
socket=socket.socket(familly,type)
family的值可以是AF_UNIX(Unix域,用于同一臺機器上的進程間通訊),也可以是AF_INET(對于IPV4協(xié)議的TCP和 UDP),至于type參數,SOCK_STREAM(流套接字)或者 SOCK_DGRAM(數據報文套接字),SOCK_RAW(raw套接字)。
第2步則是將socket綁定(指派)到指定地址上,socket.bind(address)
address必須是一個雙元素元組,((host,port)),主機名或者ip地址+端口號。如果端口號正在被使用或者保留,或者主機名或ip地址錯誤,則引發(fā)socke.error異常。
第3步,綁定后,必須準備好套接字,以便接受連接請求。
socket.listen(backlog)
backlog指定了最多連接數,至少為1,接到連接請求后,這些請求必須排隊,如果隊列已滿,則拒絕請求。
第4步,服務器套接字通過socket的accept方法等待客戶請求一個連接:
connection,address=socket.accept()
調用accept方法時,socket會進入'waiting'(或阻塞)狀態(tài)??蛻粽埱筮B接時,方法建立連接并返回服務器。accept方法返回一個含有倆個元素的元組,形如(connection,address)。第一個元素(connection)是新的socket對象,服務器通過它與客戶通信;第二個元素(address)是客戶的internet地址。
第5步是處理階段,服務器和客戶通過send和recv方法通信(傳輸數據)。服務器調用send,并采用字符串形式向客戶發(fā)送信息。send方法返回已發(fā)送的字符個數。服務器使用recv方法從客戶接受信息。調用recv時,必須指定一個整數來控制本次調用所接受的最大數據量。recv方法在接受數據時會進入'blocket'狀態(tài),最后返回一個字符串,用它來表示收到的數據。如果發(fā)送的量超過recv所允許,數據會被截斷。多余的數據將緩沖于接受端。以后調用recv時,多余的數據會從緩沖區(qū)刪除。
第6步,傳輸結束,服務器調用socket的close方法以關閉連接。
建立一個簡單客戶連接則需要4個步驟。
第1步,創(chuàng)建一個socket以連接服務器 socket=socket.socket(family,type)
第2步,使用socket的connect方法連接服務器 socket.connect((host,port))
第3步,客戶和服務器通過send和recv方法通信。
第4步,結束后,客戶通過調用socket的close方法來關閉連接。
三、一個簡單的服務器和客戶端通信的例子
服務器:
import socket
s=socket.socket()
s.bind(('xxx.xxx.xxx.xxx',xxxx)) #ip地址和端口號
s.listen(5)
cs,address = s.accept()
print 'got connected from',address
cs.send('byebye')
ra=cs.recv(512)
print ra
cs.close()
客戶端:
import socket
s=socket.socket()
s.connect(('xxx.xxx.xxx.xxx',xxxx)) #與服務器程序ip地址和端口號相同
data=s.recv(512)
s.send('hihi')
s.close()
print 'the data received is',data
運行:
在本機測試(windows環(huán)境下,可以將ip地址改為本機ip,端口號在1024以上,windows將1024以下的為保留),運行--CMD--進入命令行模式
先python 服務器程序,后python 客戶端程序即可。
或者啟動服務器程序后,用telnet ip地址 端口號,也可以得到同樣結果。
--------------------------------------------------------------------------------
讓server持續(xù)接受連接
server.py
import socket
s=socket.socket()
s.bind(('192.168.43.137',2000))
s.listen(5)
while 1:
cs,address = s.accept()
print 'got connected from',address
cs.send('hello I am server,welcome')
ra=cs.recv(512)
print ra
cs.close()
測試兩個一個程序中兩個socket并存是否可行
client.py
import socket
s=socket.socket()
s.connect(('192.168.43.137',2000))
data=s.recv(512)
print 'the data received is\n ',data
s.send('hihi I am client')
sock2 = socket.socket()
sock2.connect(('192.168.43.137',2000))
data2=sock2.recv(512)
print 'the data received from server is\n ',data2
sock2.send('client send use sock2')
sock2.close()
s.close()
網絡編程框架2009年04月12日 星期日 上午 10:39twisted是python里面公認的很牛的網絡編程框架。學python網絡編程的如果不學twisted,估計也就只能算是了解python網絡編 程吧,就如同開發(fā)網站要用django是一樣的,二者都是python下有名的框架。twisted是基于單線程的事件驅動的網絡引擎。關于它的學習資料 比較少,而且中文的就更少了,所以學習twisted一定要硬著頭皮看英文文檔,也就是它的twisted documentation,在這里基本可以找到你所需要的所有基礎知識。尤其是core documentation 和example里面都講了很多示例,這些示例如果都通通的運行一遍,那么你的twisted已經可以算入門了。
我主要是用twisted的工廠和協(xié)議框架編寫了一個內部的內容分發(fā)網絡的Tracker服務器,不是基于標準bt協(xié)議的,如果要學習,最好還是按照標準BT協(xié)議。前面也給了網址。至于如何使用twisted,我會在后續(xù)文章詳細介紹。
本文先介紹twisted的兩種工作方式,reactor 和 application方式。
The reactor is the core of the event loop within Twisted -- the loop which drives applications using Twisted. The reactor provides basic interfaces to a number of services, including network communications, threading, and event dispatching.
reactor是twisted事件循環(huán)的核心,它提供了一些服務的基本接口,像網絡通信、線程和事件的分發(fā)。
詳細的關于reactor的介紹見twisted core documentation里面的Low-Level Twisted一章的第一節(jié)Reactor Overview.里面詳細介紹了各種reactor的安裝和使用。
我所知道的reactor有以下幾個
reactor platform Usage
IOCPReactor win32 from twisted.internet import iocpreactor iocpreactor.reactor.install()
from twisted.internet import reactor
selectReactor win32, posix from twisted.internet import reactor
pollReactor posix from twisted.internet import pollreactor
pollreactor.install()
from twisted.internet import reactor
epollReactor linux2.6 from twisted.internet import epollreactor
epollreactor.install()
from twisted.internet import reactor
kqueueReactor BSD系列 from twisted.internet import kqreactor
kqreactor.install()
from twisted.internet import reactor
以上幾種就是使用最多的幾種reactor了,除了kqueueReactor我沒有使用過以外,其他的都使用過了。都能正常工作。建議編程序的時候實現根據不同的平臺選擇最佳的reactor。
系統(tǒng)默認使用的是selectreactor。
下面給出一個小例子:
from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor
### Protocol Implementation
# This is just about the simplest possible protocol
class Echo(Protocol):
def dataReceived(self, data):
"""As soon as any data is received, write it back."""
self.transport.write(data)
def main():
f = Factory()
f.protocol = Echo
reactor.listenTCP(8000, f)
reactor.run()
if __name__ == '__main__':
main()