1. 程式人生 > >Python的socket模塊詳解

Python的socket模塊詳解

peek ear gun .py .get cati 如同 cookies django

一、網絡知識的一些介紹

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不建立連接,它的速度快但不可靠。你發送的信息也可能到不了另一端;或它們沒有按順序到達。有時候一個信息的多個復制到達接收端,即使你 只發送了一次。

二、使用地址和主機名

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])函數返回關於給定主機名的全域名(如果省略,則返回本機的全域名)。

三、使用低級的socket通信

盡管Python提供了一些封裝,使得使用socket更容易,但是你也可以直接使用socket來工作。

1、創建和銷毀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阻止接收和發送。

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。

3、發送和接收數據

函 數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函數一樣。

4、使用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

5、數值轉換

由於不同平臺的字節順序不一樣,所以當在網絡中傳輸數據時我們使用標準的網絡字節順序。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連接的細節。

1、SocketServers家族
TCPServer和UDPServer都是SocketServer的子類,它們分別處理TCP和UDP信息。

註意: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自身。

2、請求處理器

請 求處理器有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的阻塞或同步編程


一、使用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方法,就進入了臨聽狀態,然後通 常使用一個無限的循環:1、開始接受客房端的連接,這通過調用accept方法來實現。調用了這個方法後將處於阻塞狀態(等待客戶端發起連接)直到一個客 戶端連接,連接後,accept返回形如(client,address)的一個元組,其中client是一個用於與客戶端通信的 socket,address是客戶端的形如xxx.xxx.xxx.xxx:xxx的地址;2、然後服務端處理客戶端的請求;3、處理完成之後又調用 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來停止服務端的話,如果再次使用相同的端口可能需要等待一會兒。

二、使用SocketServer

SocketServer模塊簡單化了編寫網絡服務器的工作。
它提供了四個基本的服務類:TCPServer(使用TCP協議)、UDPServer(使用數據報)、UnixStreamServer、

UnixDatagramServer。UnixStreamServer和UnixDatagramServer用於類Unix平臺。
這四個類處理請求都使用同步的方法,也就是說,在下一個請求處理開始之前當前的請求處理必須已完成



用SocketServer創建一個服務器需要四步:

1、通過子類化BaseRequestHandler類和覆蓋它的handle()方法來創建一個請求處理器類,用於處理進來

的請求;
2、實例化服務類如TCPServer,並傳遞給它參數:服務器地址和請求處理器類;
3、調用服務實例對象的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是一個非常強大的異步網絡編程的框架)。

一、用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()

二、使用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


三、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是正確的選擇。

一、打開遠程文件

打開遠程文件的操作和本地差不多,不同的是只能使用讀模式,並且使用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自動創 建),headers包含關於遠程文件的一些信息。
如果你想為復本指定一個名字的話,你可以提供第二個參數:
urlretrieve(‘http://www.python.org‘, ‘C:\\python_webpage.html‘)
這 將獲取Python官方主頁並存儲到本地C:\python_webpage.html中。如果你不指定復本的文件名,那麽文件將放到一個臨時的地方,你 能夠使用open函數打開它,如果你要清除這些臨時的復本,你可以調用urlcleanup函數而不帶任何參數,它將為你完成清除工作。


一、套接字
套接字是為特定網絡協議(例如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。

二、套接字模塊

套接字模塊是一個非常簡單的基於對象的接口,它提供對低層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方法來關閉連接。


三、一個簡單的服務器和客戶端通信的例子

服務器:


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()

轉載自:http://www.jb51.net/article/19751_all.htm

Python的socket模塊詳解