1、網絡知識的一些介紹
socket 是網絡鏈接端點。例如當你的Web瀏覽器請求www.jb51.net上的主頁時,你的Web瀏覽器建立一個socket並命令它去鏈接 www.jb51.net的Web服務器主機,Web服務器也對來自的請求在一個socket上進行監聽。兩端使用各自的socket來發送和 接收信息。
在使用的時候,每一個socket都被綁定到一個特定的IP地址和端口。IP地址是一個由4個數組成的序列,這4個數均是範圍 0~255中的值(例如,220,176,36,76);端口數值的取值範圍是0~65535。端口數小於1024的都是爲衆所周知的網絡服務所保留的 (例如Web服務使用的80端口);最大的保留數被存儲在socket模塊的IPPORT_RESERVED變量中。你也能夠爲你的程序使用另外的端口數 值。
不是全部的IP地址都對世界的其它地方可見。實際上,一些是專門爲那些非公共的地址所保留的(好比形如192.168.y.z或10.x.y.z)。地址127.0.0.1是本機地址;它始終指向當前的計算機。程序可使用這個地址來鏈接運行在同一計算機上的其它程序。
IP地址很差記,你能夠花點錢爲特定的IP地址註冊一個主機名或域名(好比使用www.jb51.net代替222.76.216.16)。域名服務器(DNS)處理名字到IP地址的映射。每一個計算機均可以有一個主機名,即便它沒有在官方註冊。
多少信息經過一個網絡被傳送基於許多因素,其中之一就是使用的協議。許多的協議是基於簡單的、低級協議以造成一個協議棧。例如HTTP協議,它是用在Web瀏覽器與Web服務器之間通訊的協議,它是基於TCP協議,而TCP協議又基於IP協議。
當 在你本身的兩個程序間傳送信息的時候,你一般選擇TCP或UDP協議。TCP協議在兩端間創建一個持續的鏈接,而且你所發送的信息有保證的按順序到達它們 的目的地。UDP不創建鏈接,它的速度快但不可靠。你發送的信息也可能到不了另外一端;或它們沒有按順序到達。有時候一個信息的多個複製到達接收端,即便你 只發送了一次。
2、使用地址和主機名
socket模塊提供了幾個函數用於使用主機名和地址來工做。
gethostname()返回運行程序所在的計算機的主機名:
>>> import socket
>>> socket.gethostname()
'lenovo'
gethostbyname(name) 嘗試將給定的主機名解釋爲一個IP地址。首先將檢查當前計算機是否可以解釋。若是不能,一個解釋請求將發送給一個遠程的DNS服務器(遠程的DNS服務器 還可能將解釋請求轉發給另外一個DNS服務器,直到該請求能夠被處理)。gethostbyname函數返回這個IP地址或在查找失敗後引起一個異常。
>>> socket.gethostbyname('lenovo')
'192.168.1.4'
>>> socket.gethostbyname('www.jb51.net')
'222.76.216.16'
一個擴展的形式是gethostbyname_ex(name),它返回一個包含三個元素的元組,分別是給定地址的主要的主機名、同一IP地址的可選的主機名的一個列表、關於同一主機的同一接口的其它IP地址的一個列表(列表可能都是空的)。
>>> socket.gethostbyname('www.163.com')
'60.191.81.49'
>>> socket.gethostbyname_ex('www.163.com')
('www.cache.split.netease.com', ['www.163.com'], ['60.191.81.48', '60.191.81.49
, '60.191.81.50', '60.191.81.51', '60.191.81.52', '60.191.81.53', '60.191.81.54
, '220.181.28.50', '220.181.28.51', '220.181.28.52', '220.181.28.53', '220.181.
8.54', '220.181.31.182', '220.181.31.183', '220.181.31.184'])
gethostbyaddr(address)函數的做用與gethostbyname_ex相同,只是你提供給它的參數是一個IP地址字符串:
>>> socket.gethostbyaddr('202.165.102.205')
('homepage.vip.cnb.yahoo.com', ['www.yahoo.com.cn'], ['202.165.102.205'])
getservbyname(service,protocol)函數要求一個服務名(如'telnet'或'ftp')和一個協議(如'tcp'或'udp'),返回服務所使用的端口號:
>>>socket.getservbyname('http','tcp')
80
>>>socket.getservbyname('telnet','tcp)
23
一般,非Python程序以32位字節包的形式存儲和使用IP地址。inet_aton(ip_addr)和inet_ntoa(packed)函數在這個形式和IP地址間做轉換:
>>> socket.inet_aton('222.76.216.16')
'\xdeL\xd8\x10'
>>> socket.inet_ntoa('\xdeL\xd8\x10')
'222.76.216.16'
socket 也定義了一些變量來表明保留的IP地址。INADDR_ANY和INADDR_BROADCAST是被保留的IP地址分別表明任意IP地址和廣播地 址;INADDR_LOOPBACK表明loopback設備,老是地址127.0.0.1。這些變量是32位字節數字形式的。
getfqdn([name])函數返回關於給定主機名的全域名(若是省略,則返回本機的全域名)。
3、使用低級的socket通訊
儘管Python提供了一些封裝,使得使用socket更容易,可是你也能夠直接使用socket來工做。
一、建立和銷燬socket
socket 模塊中的socket(family,type[,proto])函數建立一個新的socket對象。family的取值一般是AF_INET。type 的取值一般是SOCK_STREAM(用於定向的鏈接,可靠的TCP鏈接)或SOCK_DGRAM(用於UDP):
>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
family和type參數暗指了一個協議,可是你可使用socket的第三個可選的參數(proto的取值如IPPROTO_TCP或IPPROTO_RAW)來指定所使用的協議。代替使用IPPROTO_XX變量,你可使用函數getprotobyname:
>>> getprotobyname('tcp')
6
>>> IPPROTO_TCP
6
fromfd(fd,type[,proto]) 是一個不多被使用的函數,它用來從打開的一個文件描述符建立一個socket對象(文件描述符由文件的fileno()方法返回)。文件描述符與一個真實 的socket鏈接,而非一個文件。socket對象的fileno()方法返回關於這個socket的文件描述符。
當你使用完工 socket對象時,你應調用close()方法顯式的關閉socket以儘快釋放資源(儘管socket被垃圾回收器回收時將自動被關閉)。另外,你也 可使用shutdown(how)方法來關閉鏈接一邊或兩邊。參數0阻止socket接收數據,1阻止發送,2阻止接收和發送。
二、鏈接socket
當 兩個socket鏈接時(例如使用TCP),一端監聽和接收進來的鏈接,而另外一端發起鏈接。臨聽端建立一個socket,調用bind(address) 函數去綁定一個特定的地址和端口,調用listen(backlog)來臨聽進來的鏈接,最後調用accept()來接收這個新的,進來的鏈接,下面是在 服務器端的代碼:
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.bind(('127.0.0.1',44444))
>>> s.listen(1)
>>> q,v=s.accept() #返回socket q和地址v
注意:上面的代碼將一直處於等待直到鏈接被創建。下面咱們再打開另外一個Python解釋器,用做客戶端;而後鍵入以下代碼:
>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.connect(('127.0.0.1',44444) #發起鏈接
好了,咱們驗證一下鏈接是否創建了。咱們在服務器端鍵入如下代碼來發送一條信息:
>>> q.send('hello,i come from pythontik.com') 注:有時可能出現send() argument 1 must be string or buffer,not str 錯誤,緣由多是您的機器不支持UTF-8字符集,臨時解決方案是q.send(b' hello...')
31 #發送的字節數
在客戶端鍵入如下代碼來接收信息:
>>> s.recv(1024)
'hello,i come from pythontik.com'
你 傳遞給bind和connect的地址是一個關於AF_INET的socket的元組(ipAddress,port)。代替connect,你也能夠調 用connect_ex(address)方法。若是背後對C的connect的調用返回一個錯誤,那麼connect_ex也將返回一個錯誤(不然返回 0表明成功),代替引起一個異常。
當你調用listen時,你給了它一個參數,這個數值表示在等待隊列中容許放置的進來的鏈接總數。當等待隊列已滿時,若是有更多的鏈接到達,那麼遠程端將被告知鏈接被拒絕。在socket模塊中的SOMAXCONN變量代表了等待隊列所能容納的最大量。
accept()方法返回形如bind和connect的一個地址,表明遠程socket的地址。下面顯示變量v的值:
>>> v
('127.0.0.1', 1334)
UDP是不定向的鏈接,可是你仍然可使用給定的目的地址和端口來調用connect去關聯一個socket。
三、發送和接收數據
函 數send(string[,flags])發送給定的字符串到遠程socket。sendto(string[,flags],address)發送給 定的字符串到一個特定的地址。一般,send方法用於可靠鏈接的socket,sendto方法用於不可靠鏈接的socket,可是若是你在一個 UDP socket上調用connect來使它與一個特定的目標創建聯繫,那麼這時你也可使用send方法來代替sendto。
send和sendto都返回實際發送的字節數。當你快速發送大量的數據的時候,你可能想去確保所有信息已被髮送,那麼你可使用以下的一個函數:
def safeSend(sock,msg):
sent=0
while msg:
i=sock.send(msg)
if i==-1: #發生了錯誤
return -1
sent+=i
msg=msg[i:]
time.sleep(25)
return sent
recv(bufsize[,flags]) 方法接收一個進來的消息。若是有大量的數據在等待,它只返回前面的bufsize字節數的數據。recvfrom(bufsize[,flags])作同 樣的事,除了它使用AF_INET socket的返回值是(data,(ipAddress,port)),這便於你知道消息來自哪兒(這對於非鏈接的 socket是有用的)。
send,sendto,recv和recvfrom方法都有一個可選的參數flags,默認值爲0。你能夠經過對socket.MSG_*變量進行組合(按位或)來創建flags的值。這些值因平臺而有所不一樣,可是最通用的值以下所示:
MSG_OOB:處理帶外數據(既TCP緊急數據)。
MSG_DONTROUTE:不使用路由表;直接發送到接口。
MSG_PEEK:返回等待的數據且不把它們從隊列中刪除。
例如,若是你有一個打開的socket,它有一個消息等待被接收,你能夠接收這個消息後並不把它從進來的數據的隊列中刪除:
>>> q.recv(1024,MSG_PEEK)
'hello'
>>> q.recv(1024,MSG_PEEK) #由於沒有刪除,因此你能夠再獲得它。
'hello'
makefile([mode[,bufsize]]) 方法返回一個文件類對象,其中封裝了socket,以便於你之後將它傳遞給要求參數爲一個文件的代碼(或許你喜歡使用文件的方法來代替send和 recv)。這個可選的mode和bufsize參數的取值和內建的open函數同樣。
四、使用socket選項
socket對象的getpeername()和 getsockname()方法都返回包含一個IP地址和端口的二元組(這個二元組的形式就像你傳遞給connect和bind的)。 getpeername返回所鏈接的遠程socket的地址和端口,getsockname返回關於本地socket的相同信息。
在默認 狀況下,socket是阻塞式的,意思就是socket的方法的調用在任務完成以前是不會返回的。例如,若是存儲向外發送的數據的緩存已滿,你又企圖發送 更多的數據,那麼你對send的調用將被阻塞直到它可以將更多的數據放入緩存。你能夠經過調用setblocking(flag)方法(其中flag取值 是0,setblocking(0))來改變這個默認行爲,以使socket爲非阻塞式。當socket爲非阻塞式的時候,若是所作的動做將致使阻塞,將 會引發error異常。下面一段代碼將試圖不斷地接受新的鏈接並使用函數processRequest來處理。若是一個新鏈接無效,它將間隔半秒再試。另 一方法是在你的監聽socket上調用select或poll來檢測一個新的鏈接的到達。
別的socket的選項可使用 setsockopt(level,name,value)和getsockopt(level,name[,buflen])方法來設置和獲取。 socket表明了一個協議棧的不一樣層,level參數指定了選項應用於哪一層。level的取值以SOL_開頭(SOL_SOCKET,SOL_TCP 等等)。name代表你涉及的是哪一個選項。對於value,若是該選項要求數值的值,value只能傳入數字值。你也能夠傳遞入一個緩存(一個字符串), 但你必須使用正確的格式。對getsockopt,不指定buflen參數意味你要求一個數字值,並返回這個值。若是你提供了 buflen,getsockopt返回表明一個緩存的字符串,它的最大長度是buflen的字節數。下面的例子設置了一個socket的用於發送的緩存 尺寸爲64KB:
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.setsockopt(SOL_SOCKET,SO_SNDBUF,65535)
要獲得一個包在被路由丟棄前所能有的生命週期(TTL)和跳數,你可使用以下代碼:
>>> s.getsockopt(SOL_IP,IP_TTL)
32
五、數值轉換
因爲不一樣平臺的字節順序不同,因此當在網絡中傳輸數據時咱們使用標準的網絡字節順序。nthol(x)和ntohs(x)函數要求一個網絡字節順序的數值並把它轉換爲當前主機字節順序的相同數值,而htonl(x)和htons(x)則相反:
>>> import.socket
>>> socket.htons(20000) #轉換爲一個16位的值
8270
>>> socket.htonl(20000) #轉換爲一個32位的值
541982720
>>> socket.ntohl(541982720)
20000
使用SocketServers
SocketServers模塊爲一組socket服務類定義了一個基類,這組類壓縮和隱藏了監聽、接受和處理進入的socket鏈接的細節。
一、SocketServers家族
TCPServer和UDPServer都是SocketServer的子類,它們分別處理TCP和UDP信息。html
注意:SocketServer也提供UnixStreamServer(TCPServer的子類)和UNIXdatagramServer(UDPServer的子類),它們都如同其父類同樣除了在建立監聽socket時使用AF_UNIX代替了AF_INET。
默 認狀況下,socket服務一次處理一個鏈接,可是你可使用ThreadingMixIN和ForkingMixIn類來建立任一 SocketServer的線程和子進程。實際上,SocketServer模塊提供了一些對些有用的類來解決你的麻煩,它們 是:ForkingUDPServer、ForkingTCPServer、ThreadingUDPServer、 ThreadingTCPServer、ThreadingUnixStreamServer和 ThreadingUnixDatagramServer。
SocketServer以一般的方法處理進入的鏈接;要使它更有用,你應該 提供你本身的請求處理器類給它以便它傳遞一個socket去處理。SocketServer模塊中的BaseRequestHandler類是全部請求處 理器的父類。假設,例如你須要寫一個多線程的電子郵件服務器,首先你要建立一個MailRequestHandler,它是 BaseRequestHandler的子類,而後把它傳遞給一個新建立的SocketServer:
import SocketServer
...#建立你的MailRequestHandler
addr=('220.172.20.6',25) #監聽的地址和端口
server=SocketServer.ThreadingTCPServer(addr,MailRequestHandler)
server.serve_forever()
每 次一個新的鏈接到來時,這個server建立一個新的MailRequestHandler實例並調用它的handle()方法來處理這個新的請求。由於 server繼承自ThreadingTCPServer,對於每一個新的請求它都啓動一個單獨的線程來處理這個請求,以便於多個請求可以被同時處理。若是 用handle_request()代替server_forever,它將一個一個的處理鏈接請求。server_forever 只是反覆調用 handle_request而已。
通常來講,你只需使用socket服務之一,可是若是你須要建立你本身的子類的話,你能夠覆蓋咱們下面提到的方法來定製它。
當 服務被第一次建立的時候,__init__函數調用server_bind()方法來綁定監聽socket(self.socket)到正確的地址 (self.server_address)。而後調用server_activate()來激活這個服務(默認狀況下,調用socket的listen 方法)。
這個socket服務不作任何事情直到調用了handle_request或serve_forever方法。 handle_request調用get_request()去等待和接收一個新的socket鏈接,而後調用 verify_request(request,client_address)去看服務是否會處理這個鏈接(你能夠在訪問控制中使用這個,默認狀況下 verify_request老是返回true)。若是會處理這個請求,handle_request而後調用 process_request(request,client_address),若是 process_request(request,client_address)致使一個異常的話,將調用 handle_error(request,client_address)。默認狀況下,process_request簡單地調用 finish_request(request,client_address);子進程和線程類覆蓋了這個行爲去開始一新的進程或線程,而後調用 finish_request。finish_request實例化一個新的請求處理器,請求處理器輪流調用它們的handle()方法。
當SocketServer建立一個新的請求處理器時,它傳遞給這個處理器的__init__函數的self變量,以便於這個處理器可以訪問關於這個服務的信息。
SocketServer 的fileno()方法返回監聽socket的文件描述符。address_family成員變量指定了監聽socket的socket族(如 AF_INET),server_address包含了監聽socket被綁定到的地址。socket變量包含監聽socket自身。
二、請求處理器
請 求處理器有setup()、handle()和finish()方法,你能夠覆蓋它們來定製你本身的行爲。通常狀況下,你只須要覆蓋handle方法。 BaseRequestHandler的__init__函數調用setup()方法來作初始化的工做,handle()服務於請求,finish()用 於執行清理工做,若是handle或setup致使一個異常,finish不會被調用。記住,你的請求處理器會爲每一個請求建立一個新的實例。
request 成員變量有關於流(TCP)服務的最近接受的socket;對於數據報服務,它是一個包含進入消息和監聽socket的元組。 client_address包含發送者的地址,server有對SocketServer的一個引用(經過這你能夠訪問它的成員,如 server_address)。
下面的例子實現了一個EchoRequestHandler,這做爲一個服務端它將客戶端所發送的數據再發送回客戶端:
>>> import SocketServer
>>> class EchoRequestHandler(SocketServer.BaseRequestHandler):
... def handle(self):
... print 'Got new connection!'
... while 1:
... mesg=self.request.recv(1024)
... if not msg:
... break
... print 'Received:',msg
... self.request.send(msg)
... print 'Done with connection'
>>> server=SocketServer.ThreadingTCPServer(('127.0.0.1',12321),EchoReuestHandler)
>>> server.handle_request() #執行後將等待鏈接
Got new connection!
Received: Hello!
Received: I like Tuesdays!
Done with connection
打開另外一個Python解釋器做爲客戶端,而後執行以下代碼:
>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.connect(('120.0.0.1',12321))
>>> s.send('Hello!')
6
>>> print s.recv(1024)
Hello!
>>> s.send('I like Tuesdays!')
16
>>> print s.recv(1024)
I like Tuesdays!
>>> s.close()
SocketServer 模塊也定義了BaseRequestHandler的兩個子類:StreamRequestHandler和 DatagramRequestHandler。它們覆蓋了setup和finish方法並建立了兩個文件對象rfile和wfile,你能夠用這兩個文 件對象來向客戶端讀寫數據,從而代替使用socket方法。
socket的阻塞或同步編程
1、使用socket
網 絡編程中最基本的部分就是socket(套接字)。socket有兩種:服務端socket和客戶端 socket。在你建立了一個服務端socket之 後,你告訴它去等待鏈接。而後它將監聽某個網絡地址(形如:xxx.xxx.xxx.xxx:xxx) 直到客戶端鏈接。而後這兩端就能夠通訊了。
處理客戶端socket一般比處理服務端socket要容易一點,由於服務端必須時刻準備處理來自客戶端的鏈接,而且它必須處理多個鏈接,而客戶端只須要簡單的鏈接,而後作點什麼,而後斷開鏈接。
實 例化一個socket時,能夠指定三個參數:地址系列(默認爲socket.AF_INET)、流socket(這是個默認 值: socket.SOCK_STREAM)或數據報socket(socket.SOCK_DGRAM)、協議(默認值是0)。對於簡單的 socket,你能夠不指定任何參數而所有使用默認值。
服務端socket在使用bind方法以後調用listen方法去監聽一個給定的 地址。而後,客戶端socket就能夠經過使用connect方法(connect方法所使用的地址參數與bind相同)去鏈接服務端。listen方法 要求一個參數,這個參數就是等待鏈接隊列中所能包含的鏈接數。
一旦服務端socket調用了listen方法,就進入了臨聽狀態,而後通 常使用一個無限的循環:一、開始接受客房端的鏈接,這經過調用accept方法來實現。調用了這個方法後將處於阻塞狀態(等待客戶端發起鏈接)直到一個客 戶端鏈接,鏈接後,accept返回形如(client,address)的一個元組,其中client是一個用於與客戶端通訊的 socket,address是客戶端的形如xxx.xxx.xxx.xxx:xxx的地址;二、而後服務端處理客戶端的請求;三、處理完成以後又調用 1。
關於傳輸數據,socket有兩個方法: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()
客戶端:
import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.connect((host, port))
print s.recv(1024)
注意:若是你使用Ctrl-C來中止服務端的話,若是再次使用相同的端口可能須要等待一下子。
2、使用SocketServer
SocketServer模塊簡單化了編寫網絡服務器的工做。
它提供了四個基本的服務類:TCPServer(使用TCP協議)、UDPServer(使用數據報)、UnixStreamServer、
UnixDatagramServer。UnixStreamServer和UnixDatagramServer用於類Unix平臺。
這四個類處理請求都使用同步的方法,也就是說,在下一個請求處理開始以前當前的請求處理必須已完成
。
用SocketServer建立一個服務器須要四步:
一、經過子類化BaseRequestHandler類和覆蓋它的handle()方法來建立一個請求處理器類,用於處理進來
的請求;
二、實例化服務類如TCPServer,並傳遞給它參數:服務器地址和請求處理器類;
三、調用服務實例對象的handle_request()或serve_forever()方法去處理請求。
下面使用SocketServer用同步的方法寫一個最簡單的服務器:
from SocketServer import TCPServer, StreamRequestHandler
#第一步。其中StreamRequestHandler類是BaseRequestHandler類的子類,它爲流socket定義了
#rfile和wfile方法
class Handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
print 'Got connection from', addr
self.wfile.write('Thank you for connecting')
#第二步。其中''表明運行服務器的主機
server = TCPServer(('', 1234), Handler)
#第三步。serve_forever()致使進入循環狀態
server.serve_forever()
注意:使用阻塞或同步的方法一次只能鏈接一個客戶端,處理完成後才能鏈接下一個客戶端。
非阻塞或異步編程
例如,對於一個聊天室來講,由於有多個鏈接須要同時被處理,因此很顯然,阻塞或同步的方法是不合適的,這就像買票只開了一個窗口,佷多人排隊等同樣。那麼咱們如何解決這個問題呢?主要有三種方法:forking、threading、異步I/O。
Forking和threading的方法很是簡單,經過使用SocketServer服務類的min-in類就能夠實現。forking只適用於類Unix平臺;threading須要注意內存共享的問題。
異步I/O若是底層的方法來實現是有點困難的。要簡單點,咱們能夠考慮使用標準庫中的框架或Twisted(Twisted是一個很是強大的異步網絡編程的框架)。
1、用ScoketServer實現Forking和threading
下面咱們使用兩個例子來分別建立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()
2、使用select實現異步I/O
所謂異步I/O,打個比方,就是若是一大羣人都想你聽他說話,那麼你就給他們每人一分鐘的時間說,你們輪流說,沒說完的待會兒輪到時再繼續說。也就是一個時間片的方法。
要實現異步I/O,咱們能夠經過使用框架asyncore/asynchat或Twisted,它們都是基於select函數或poll函數(poll只適於類Unix系統)的。select和poll函數都來自select模塊。
select 函數要求三個必須序列做爲參數和一個可選的以秒爲單位的超時值。序列中是表示文件描述符的整數值,它們是咱們要等待的鏈接。這三個序列是關於輸入、輸出和 異常條件的。若是超時值沒有給出的話,select將處於阻塞狀態(也就是等待)直到有文件描述符準備動做。若是超時值給出了,那麼select只阻塞給 定的時間。若是超時值是0的話,那麼將不阻塞。select返回的值是一個由三個序列組成的元組,它們分別表明相應參數的活動的子集。例如,第一個序列返 回的是用於讀的輸入文件描述符構成的序列。
序列能夠包含文件對象(不適於Windows)或socket。下面這個例子建立一個使用 select去服務幾個鏈接的服務器(注意:服務端的socket自身也提供給了select,以便於它可以在有新的鏈接準備接受時發出信號通知)。這個 服務器只是簡單地打印接受自客戶端的數據。你可使用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
3、Twisted
Twisted 是針對Python的一個事件驅動的網絡框架,最初是爲了網絡遊戲而開發的,可是如今被應用於各種網絡軟件。用Twisted,你能夠實現事件處理器,非 常相似用GUI工具包(Tk, GTK, Qt, wxWidgets)。這部分我將介紹一些基本的概念和演示如何使用Twisted來作一些相對簡單的 網絡編程。Twisted是很是強大的框架並提供了大量的支持,如:Web服務器和客戶端、 SSH2, SMTP, POP3, IMAP4, AIM, ICQ, IRC, MSN,Jabber, NNTP, DNS等等。
早先咱們所寫的基於socket的服務器,它們都有一個顯示的事件循環:尋找新的鏈接和新的數據;基於SocketServer的服務器有一個隱含的循環:尋找鏈接和爲鏈接建立處理器。但時處理器仍然時顯示的讀數據。
而 Twisted使用了更多的基於事件的方式。要寫一個基本的服務器,你要實現事件處理器,它處理諸如一個新的客戶端鏈接、新的數據到達和客戶端鏈接中斷等 狀況。在Twisted中,你的事件處理器定義在一個protocol中;你也須要一個factory,當一個新的鏈接到達時它可以構造這個 protocol對象,可是若是你僅僅想建立一個自定義的Protocol類的實例的話,你可使用來自Twisted的factory,Factory 類在模塊twisted.internet.protocol中。當你寫你的protocol時,使用 twisted.internet.protocol模塊中的Protocol做爲你的父類。當你獲得一個鏈接時,事件處理器 connectionMade被調用;當你丟失了一個鏈接時,connectionLost被調用。從客戶端接受數據使用處理器 dataReceived。可是你不能使用事件處理策略向客戶端發送數據;要向客戶端發送數據,你可使用self.transport,它有一個 write方法。它也有一個client屬性,其中包含了客戶端的地址(主機名和端口)。
下面這個例子是一個Twisted版的服務器。 其中實例化了Factory並設置了它的protocol屬性以便它知道使用哪一個protocol與客戶端通訊(這就是所謂的你的自定義 protocol)。而後你使用factory開始監聽指定的端口,factory經過實例化的protocol對象處理鏈接。監聽使用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
# 監聽指定的端口
reactor.listenTCP(1234, factory)
# 開始運行主程序
reactor.run()
爲 你的處理目的而寫一個自定義的protocol是很容易的。模塊twisted.protocols.basic中包含了幾個有用的已存在的 protocol,其中的LineReceiver執行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頁面、提取信息、自動建立你所尋找的東西的報告。
urllib2更流行一些。對於簡單的下載任務,urllib比較好。若是你須要HTTP驗證或cookies,或你想寫一些擴展去處理你本身的協議的話,那麼urllib2是正確的選擇。
1、打開遠程文件
打開遠程文件的操做和本地差很少,不一樣的是隻能使用讀模式,而且使用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/'
2、獲取遠程文件
urlopen 函數給你一個文件類對象,你能夠讀取它。若是你使用urlib時只關心下載文件並存儲一個複本到本地文件的話,你可使用urlretrieve替而代 之。urlretrieve返回一個元組(filename, headers),filename是本地文件(複本)的名字(它由urllib自動創 建),headers包含關於遠程文件的一些信息。
若是你想爲複本指定一個名字的話,你能夠提供第二個參數:
urlretrieve('http://www.python.org', 'C:\\python_webpage.html')
這 將獲取Python官方主頁並存儲到本地C:\python_webpage.html中。若是你不指定複本的文件名,那麼文件將放到一個臨時的地方,你 可以使用open函數打開它,若是你要清除這些臨時的複本,你能夠調用urlcleanup函數而不帶任何參數,它將爲你完成清除工做。
1、套接字
套接字是爲特定網絡協議(例如TCP/IP,ICMP/IP,UDP/IP等)套件對上的網絡應用程序提供者提供當前可移植標準的對象。它們容許程序接受並進行鏈接,如發送和接受數據。爲了創建通訊通道,網絡通訊的每一個端點擁有一個套接字對象極爲重要。
套接字爲BSD UNIX系統核心的一部分,並且他們也被許多其餘相似UNIX的操做系統包括Linux所採納。許多非BSD UNIX系統(如ms-dos,windows,os/2,mac os及大部分主機環境)都以庫形式提供對套接字的支持。
三種最流行的套接字類型是:stream,datagram和raw。stream和datagram套接字能夠直接與TCP協議進行接口,而raw套接字則接口到IP協議。但套接字並不限於TCP/IP。
2、套接字模塊
套接字模塊是一個很是簡單的基於對象的接口,它提供對低層BSD套接字樣式網絡的訪問。使用該模塊能夠實現客戶機和服務器套接字。要在python 中創建具備TCP和流套接字的簡單服務器,須要使用socket模塊。利用該模塊包含的函數和類定義,可生成經過網絡通訊的程序。通常來講,創建服務器鏈接須要六個步驟。
第1步是建立socket對象。調用socket構造函數。
socket=socket.socket(familly,type)
family的值能夠是AF_UNIX(Unix域,用於同一臺機器上的進程間通信),也能夠是AF_INET(對於IPV4協議的TCP和 UDP),至於type參數,SOCK_STREAM(流套接字)或者 SOCK_DGRAM(數據報文套接字),SOCK_RAW(raw套接字)。
第2步則是將socket綁定(指派)到指定地址上,socket.bind(address)
address必須是一個雙元素元組,((host,port)),主機名或者ip地址+端口號。若是端口號正在被使用或者保留,或者主機名或ip地址錯誤,則引起socke.error異常。
第3步,綁定後,必須準備好套接字,以便接受鏈接請求。
socket.listen(backlog)
backlog指定了最多鏈接數,至少爲1,接到鏈接請求後,這些請求必須排隊,若是隊列已滿,則拒絕請求。
第4步,服務器套接字經過socket的accept方法等待客戶請求一個鏈接:
connection,address=socket.accept()
調用accept方法時,socket會進入'waiting'(或阻塞)狀態。客戶請求鏈接時,方法創建鏈接並返回服務器。accept方法返回一個含有倆個元素的元組,形如(connection,address)。第一個元素(connection)是新的socket對象,服務器經過它與客戶通訊;第二個元素(address)是客戶的internet地址。
第5步是處理階段,服務器和客戶經過send和recv方法通訊(傳輸數據)。服務器調用send,並採用字符串形式向客戶發送信息。send方法返回已發送的字符個數。服務器使用recv方法從客戶接受信息。調用recv時,必須指定一個整數來控制本次調用所接受的最大數據量。recv方法在接受數據時會進入'blocket'狀態,最後返回一個字符串,用它來表示收到的數據。若是發送的量超過recv所容許,數據會被截斷。多餘的數據將緩衝於接受端。之後調用recv時,多餘的數據會從緩衝區刪除。
第6步,傳輸結束,服務器調用socket的close方法以關閉鏈接。
創建一個簡單客戶鏈接則須要4個步驟。
第1步,建立一個socket以鏈接服務器 socket=socket.socket(family,type)
第2步,使用socket的connect方法鏈接服務器 socket.connect((host,port))
第3步,客戶和服務器經過send和recv方法通訊。
第4步,結束後,客戶經過調用socket的close方法來關閉鏈接。
3、一個簡單的服務器和客戶端通訊的例子
服務器:
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環境下,能夠將ip地址改成本機ip,端口號在1024以上,windows將1024如下的爲保留),運行--CMD--進入命令行模式
先python 服務器程序,後python 客戶端程序便可。
或者啓動服務器程序後,用telnet ip地址 端口號,也能夠獲得一樣結果。
--------------------------------------------------------------------------------
讓server持續接受鏈接
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網絡編 程吧,就如同開發網站要用django是同樣的,兩者都是python下有名的框架。twisted是基於單線程的事件驅動的網絡引擎。關於它的學習資料 比較少,並且中文的就更少了,因此學習twisted必定要硬着頭皮看英文文檔,也就是它的twisted documentation,在這裏基本能夠找到你所須要的全部基礎知識。尤爲是core documentation 和example裏面都講了不少示例,這些示例若是都統統的運行一遍,那麼你的twisted已經能夠算入門了。
我主要是用twisted的工廠和協議框架編寫了一個內部的內容分發網絡的Tracker服務器,不是基於標準bt協議的,若是要學習,最好仍是按照標準BT協議。前面也給了網址。至於如何使用twisted,我會在後續文章詳細介紹。
本文先介紹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事件循環的核心,它提供了一些服務的基本接口,像網絡通訊、線程和事件的分發。
詳細的關於reactor的介紹見twisted core documentation裏面的Low-Level Twisted一章的第一節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。
系統默認使用的是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()python
轉載自:http://www.jb51.net/article/19751_all.htmreact