Python 網(wǎng)絡(luò)編程說(shuō)明第1/2頁(yè)
一、網(wǎng)絡(luò)知識(shí)的一些介紹
socket 是網(wǎng)絡(luò)連接端點(diǎn)。例如當(dāng)你的Web瀏覽器請(qǐng)求chabaoo.cn上的主頁(yè)時(shí),你的Web瀏覽器創(chuàng)建一個(gè)socket并命令它去連接 chabaoo.cn的Web服務(wù)器主機(jī),Web服務(wù)器也對(duì)來(lái)自的請(qǐng)求在一個(gè)socket上進(jìn)行監(jiān)聽(tīng)。兩端使用各自的socket來(lái)發(fā)送和 接收信息。
在使用的時(shí)候,每個(gè)socket都被綁定到一個(gè)特定的IP地址和端口。IP地址是一個(gè)由4個(gè)數(shù)組成的序列,這4個(gè)數(shù)均是范圍 0~255中的值(例如,220,176,36,76);端口數(shù)值的取值范圍是0~65535。端口數(shù)小于1024的都是為眾所周知的網(wǎng)絡(luò)服務(wù)所保留的 (例如Web服務(wù)使用的80端口);最大的保留數(shù)被存儲(chǔ)在socket模塊的IPPORT_RESERVED變量中。你也可以為你的程序使用另外的端口數(shù) 值。
不是所有的IP地址都對(duì)世界的其它地方可見(jiàn)。實(shí)際上,一些是專門為那些非公共的地址所保留的(比如形如192.168.y.z或10.x.y.z)。地址127.0.0.1是本機(jī)地址;它始終指向當(dāng)前的計(jì)算機(jī)。程序可以使用這個(gè)地址來(lái)連接運(yùn)行在同一計(jì)算機(jī)上的其它程序。
IP地址不好記,你可以花點(diǎn)錢為特定的IP地址注冊(cè)一個(gè)主機(jī)名或域名(比如使用chabaoo.cn代替222.76.216.16)。域名服務(wù)器(DNS)處理名字到IP地址的映射。每個(gè)計(jì)算機(jī)都可以有一個(gè)主機(jī)名,即使它沒(méi)有在官方注冊(cè)。
多少信息通過(guò)一個(gè)網(wǎng)絡(luò)被傳送基于許多因素,其中之一就是使用的協(xié)議。許多的協(xié)議是基于簡(jiǎn)單的、低級(jí)協(xié)議以形成一個(gè)協(xié)議棧。例如HTTP協(xié)議,它是用在Web瀏覽器與Web服務(wù)器之間通信的協(xié)議,它是基于TCP協(xié)議,而TCP協(xié)議又基于IP協(xié)議。
當(dāng) 在你自己的兩個(gè)程序間傳送信息的時(shí)候,你通常選擇TCP或UDP協(xié)議。TCP協(xié)議在兩端間建立一個(gè)持續(xù)的連接,并且你所發(fā)送的信息有保證的按順序到達(dá)它們 的目的地。UDP不建立連接,它的速度快但不可靠。你發(fā)送的信息也可能到不了另一端;或它們沒(méi)有按順序到達(dá)。有時(shí)候一個(gè)信息的多個(gè)復(fù)制到達(dá)接收端,即使你 只發(fā)送了一次。
二、使用地址和主機(jī)名
socket模塊提供了幾個(gè)函數(shù)用于使用主機(jī)名和地址來(lái)工作。
gethostname()返回運(yùn)行程序所在的計(jì)算機(jī)的主機(jī)名:
>>> import socket
>>> socket.gethostname()
'lenovo'
gethostbyname(name) 嘗試將給定的主機(jī)名解釋為一個(gè)IP地址。首先將檢查當(dāng)前計(jì)算機(jī)是否能夠解釋。如果不能,一個(gè)解釋請(qǐng)求將發(fā)送給一個(gè)遠(yuǎn)程的DNS服務(wù)器(遠(yuǎn)程的DNS服務(wù)器 還可能將解釋請(qǐng)求轉(zhuǎn)發(fā)給另一個(gè)DNS服務(wù)器,直到該請(qǐng)求可以被處理)。gethostbyname函數(shù)返回這個(gè)IP地址或在查找失敗后引發(fā)一個(gè)異常。
>>> socket.gethostbyname('lenovo')
'192.168.1.4'
>>> socket.gethostbyname('chabaoo.cn')
'222.76.216.16'
一個(gè)擴(kuò)展的形式是gethostbyname_ex(name),它返回一個(gè)包含三個(gè)元素的元組,分別是給定地址的主要的主機(jī)名、同一IP地址的可選的主機(jī)名的一個(gè)列表、關(guān)于同一主機(jī)的同一接口的其它IP地址的一個(gè)列表(列表可能都是空的)。
>>> 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)函數(shù)的作用與gethostbyname_ex相同,只是你提供給它的參數(shù)是一個(gè)IP地址字符串:
>>> socket.gethostbyaddr('202.165.102.205')
('homepage.vip.cnb.yahoo.com', ['www.yahoo.com.cn'], ['202.165.102.205'])
getservbyname(service,protocol)函數(shù)要求一個(gè)服務(wù)名(如'telnet'或'ftp')和一個(gè)協(xié)議(如'tcp'或'udp'),返回服務(wù)所使用的端口號(hào):
>>>socket.getservbyname('http','tcp')
80
>>>socket.getservbyname('telnet','tcp)
23
通常,非Python程序以32位字節(jié)包的形式存儲(chǔ)和使用IP地址。inet_aton(ip_addr)和inet_ntoa(packed)函數(shù)在這個(gè)形式和IP地址間作轉(zhuǎn)換:
>>> socket.inet_aton('222.76.216.16')
'\xdeL\xd8\x10'
>>> socket.inet_ntoa('\xdeL\xd8\x10')
'222.76.216.16'
socket 也定義了一些變量來(lái)代表保留的IP地址。INADDR_ANY和INADDR_BROADCAST是被保留的IP地址分別代表任意IP地址和廣播地 址;INADDR_LOOPBACK代表loopback設(shè)備,總是地址127.0.0.1。這些變量是32位字節(jié)數(shù)字形式的。
getfqdn([name])函數(shù)返回關(guān)于給定主機(jī)名的全域名(如果省略,則返回本機(jī)的全域名)。
三、使用低級(jí)的socket通信
盡管Python提供了一些封裝,使得使用socket更容易,但是你也可以直接使用socket來(lái)工作。
1、創(chuàng)建和銷毀socket
socket 模塊中的socket(family,type[,proto])函數(shù)創(chuàng)建一個(gè)新的socket對(duì)象。family的取值通常是AF_INET。type 的取值通常是SOCK_STREAM(用于定向的連接,可靠的TCP連接)或SOCK_DGRAM(用于UDP):
>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
family和type參數(shù)暗指了一個(gè)協(xié)議,但是你可以使用socket的第三個(gè)可選的參數(shù)(proto的取值如IPPROTO_TCP或IPPROTO_RAW)來(lái)指定所使用的協(xié)議。代替使用IPPROTO_XX變量,你可以使用函數(shù)getprotobyname:
>>> getprotobyname('tcp')
6
>>> IPPROTO_TCP
6
fromfd(fd,type[,proto]) 是一個(gè)很少被使用的函數(shù),它用來(lái)從打開(kāi)的一個(gè)文件描述符創(chuàng)建一個(gè)socket對(duì)象(文件描述符由文件的fileno()方法返回)。文件描述符與一個(gè)真實(shí) 的socket連接,而非一個(gè)文件。socket對(duì)象的fileno()方法返回關(guān)于這個(gè)socket的文件描述符。
當(dāng)你使用完工 socket對(duì)象時(shí),你應(yīng)調(diào)用close()方法顯式的關(guān)閉socket以盡快釋放資源(盡管socket被垃圾回收器回收時(shí)將自動(dòng)被關(guān)閉)。另外,你也 可以使用shutdown(how)方法來(lái)關(guān)閉連接一邊或兩邊。參數(shù)0阻止socket接收數(shù)據(jù),1阻止發(fā)送,2阻止接收和發(fā)送。
2、連接socket
當(dāng) 兩個(gè)socket連接時(shí)(例如使用TCP),一端監(jiān)聽(tīng)和接收進(jìn)來(lái)的連接,而另一端發(fā)起連接。臨聽(tīng)端創(chuàng)建一個(gè)socket,調(diào)用bind(address) 函數(shù)去綁定一個(gè)特定的地址和端口,調(diào)用listen(backlog)來(lái)臨聽(tīng)進(jìn)來(lái)的連接,最后調(diào)用accept()來(lái)接收這個(gè)新的,進(jìn)來(lái)的連接,下面是在 服務(wù)器端的代碼:
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.bind(('127.0.0.1',44444))
>>> s.listen(1)
>>> q,v=s.accept() #返回socket q和地址v
注意:上面的代碼將一直處于等待直到連接被建立。下面我們?cè)俅蜷_(kāi)另一個(gè)Python解釋器,用作客戶端;然后鍵入如下代碼:
>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.connect(('127.0.0.1',44444) #發(fā)起連接
好了,我們驗(yàn)證一下連接是否建立了。我們?cè)诜?wù)器端鍵入以下代碼來(lái)發(fā)送一條信息:
>>> q.send('hello,i come from pythontik.com') 注:有時(shí)可能出現(xiàn)send() argument 1 must be string or buffer,not str 錯(cuò)誤,原因可能是您的機(jī)器不支持UTF-8字符集,臨時(shí)解決方案是q.send(b' hello...')
31 #發(fā)送的字節(jié)數(shù)
在客戶端鍵入以下代碼來(lái)接收信息:
>>> s.recv(1024)
'hello,i come from pythontik.com'
你 傳遞給bind和connect的地址是一個(gè)關(guān)于AF_INET的socket的元組(ipAddress,port)。代替connect,你也可以調(diào) 用connect_ex(address)方法。如果背后對(duì)C的connect的調(diào)用返回一個(gè)錯(cuò)誤,那么connect_ex也將返回一個(gè)錯(cuò)誤(否則返回 0代表成功),代替引發(fā)一個(gè)異常。
當(dāng)你調(diào)用listen時(shí),你給了它一個(gè)參數(shù),這個(gè)數(shù)值表示在等待隊(duì)列中允許放置的進(jìn)來(lái)的連接總數(shù)。當(dāng)?shù)却?duì)列已滿時(shí),如果有更多的連接到達(dá),那么遠(yuǎn)程端將被告知連接被拒絕。在socket模塊中的SOMAXCONN變量表明了等待隊(duì)列所能容納的最大量。
accept()方法返回形如bind和connect的一個(gè)地址,代表遠(yuǎn)程socket的地址。下面顯示變量v的值:
>>> v
('127.0.0.1', 1334)
UDP是不定向的連接,但是你仍然可以使用給定的目的地址和端口來(lái)調(diào)用connect去關(guān)聯(lián)一個(gè)socket。
3、發(fā)送和接收數(shù)據(jù)
函 數(shù)send(string[,flags])發(fā)送給定的字符串到遠(yuǎn)程socket。sendto(string[,flags],address)發(fā)送給 定的字符串到一個(gè)特定的地址。通常,send方法用于可靠連接的socket,sendto方法用于不可靠連接的socket,但是如果你在一個(gè) UDP socket上調(diào)用connect來(lái)使它與一個(gè)特定的目標(biāo)建立聯(lián)系,那么這時(shí)你也可以使用send方法來(lái)代替sendto。
send和sendto都返回實(shí)際發(fā)送的字節(jié)數(shù)。當(dāng)你快速發(fā)送大量的數(shù)據(jù)的時(shí)候,你可能想去確保全部信息已被發(fā)送,那么你可以使用如下的一個(gè)函數(shù):
def safeSend(sock,msg):
sent=0
while msg:
i=sock.send(msg)
if i==-1: #發(fā)生了錯(cuò)誤
return -1
sent+=i
msg=msg[i:]
time.sleep(25)
return sent
recv(bufsize[,flags]) 方法接收一個(gè)進(jìn)來(lái)的消息。如果有大量的數(shù)據(jù)在等待,它只返回前面的bufsize字節(jié)數(shù)的數(shù)據(jù)。recvfrom(bufsize[,flags])做同 樣的事,除了它使用AF_INET socket的返回值是(data,(ipAddress,port)),這便于你知道消息來(lái)自哪兒(這對(duì)于非連接的 socket是有用的)。
send,sendto,recv和recvfrom方法都有一個(gè)可選的參數(shù)flags,默認(rèn)值為0。你可以通過(guò)對(duì)socket.MSG_*變量進(jìn)行組合(按位或)來(lái)建立flags的值。這些值因平臺(tái)而有所不同,但是最通用的值如下所示:
MSG_OOB:處理帶外數(shù)據(jù)(既TCP緊急數(shù)據(jù))。
MSG_DONTROUTE:不使用路由表;直接發(fā)送到接口。
MSG_PEEK:返回等待的數(shù)據(jù)且不把它們從隊(duì)列中刪除。
例如,如果你有一個(gè)打開(kāi)的socket,它有一個(gè)消息等待被接收,你可以接收這個(gè)消息后并不把它從進(jìn)來(lái)的數(shù)據(jù)的隊(duì)列中刪除:
>>> q.recv(1024,MSG_PEEK)
'hello'
>>> q.recv(1024,MSG_PEEK) #因?yàn)闆](méi)有刪除,所以你可以再得到它。
'hello'
makefile([mode[,bufsize]]) 方法返回一個(gè)文件類對(duì)象,其中封裝了socket,以便于你以后將它傳遞給要求參數(shù)為一個(gè)文件的代碼(或許你喜歡使用文件的方法來(lái)代替send和 recv)。這個(gè)可選的mode和bufsize參數(shù)的取值和內(nèi)建的open函數(shù)一樣。
4、使用socket選項(xiàng)
socket對(duì)象的getpeername()和 getsockname()方法都返回包含一個(gè)IP地址和端口的二元組(這個(gè)二元組的形式就像你傳遞給connect和bind的)。 getpeername返回所連接的遠(yuǎn)程socket的地址和端口,getsockname返回關(guān)于本地socket的相同信息。
在默認(rèn) 情況下,socket是阻塞式的,意思就是socket的方法的調(diào)用在任務(wù)完成之前是不會(huì)返回的。例如,如果存儲(chǔ)向外發(fā)送的數(shù)據(jù)的緩存已滿,你又企圖發(fā)送 更多的數(shù)據(jù),那么你對(duì)send的調(diào)用將被阻塞直到它能夠?qū)⒏嗟臄?shù)據(jù)放入緩存。你可以通過(guò)調(diào)用setblocking(flag)方法(其中flag取值 是0,setblocking(0))來(lái)改變這個(gè)默認(rèn)行為,以使socket為非阻塞式。當(dāng)socket為非阻塞式的時(shí)候,如果所做的動(dòng)作將導(dǎo)致阻塞,將 會(huì)引起error異常。下面一段代碼將試圖不斷地接受新的連接并使用函數(shù)processRequest來(lái)處理。如果一個(gè)新連接無(wú)效,它將間隔半秒再試。另 一方法是在你的監(jiān)聽(tīng)socket上調(diào)用select或poll來(lái)檢測(cè)一個(gè)新的連接的到達(dá)。
別的socket的選項(xiàng)可以使用 setsockopt(level,name,value)和getsockopt(level,name[,buflen])方法來(lái)設(shè)置和獲取。 socket代表了一個(gè)協(xié)議棧的不同層,level參數(shù)指定了選項(xiàng)應(yīng)用于哪一層。level的取值以SOL_開(kāi)頭(SOL_SOCKET,SOL_TCP 等等)。name表明你涉及的是哪個(gè)選項(xiàng)。對(duì)于value,如果該選項(xiàng)要求數(shù)值的值,value只能傳入數(shù)字值。你也可以傳遞入一個(gè)緩存(一個(gè)字符串), 但你必須使用正確的格式。對(duì)getsockopt,不指定buflen參數(shù)意味你要求一個(gè)數(shù)字值,并返回這個(gè)值。如果你提供了 buflen,getsockopt返回代表一個(gè)緩存的字符串,它的最大長(zhǎng)度是buflen的字節(jié)數(shù)。下面的例子設(shè)置了一個(gè)socket的用于發(fā)送的緩存 尺寸為64KB:
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.setsockopt(SOL_SOCKET,SO_SNDBUF,65535)
要得到一個(gè)包在被路由丟棄前所能有的生命周期(TTL)和跳數(shù),你可以使用如下代碼:
>>> s.getsockopt(SOL_IP,IP_TTL)
32
5、數(shù)值轉(zhuǎn)換
由于不同平臺(tái)的字節(jié)順序不一樣,所以當(dāng)在網(wǎng)絡(luò)中傳輸數(shù)據(jù)時(shí)我們使用標(biāo)準(zhǔn)的網(wǎng)絡(luò)字節(jié)順序。nthol(x)和ntohs(x)函數(shù)要求一個(gè)網(wǎng)絡(luò)字節(jié)順序的數(shù)值并把它轉(zhuǎn)換為當(dāng)前主機(jī)字節(jié)順序的相同數(shù)值,而htonl(x)和htons(x)則相反:
>>> import.socket
>>> socket.htons(20000) #轉(zhuǎn)換為一個(gè)16位的值
8270
>>> socket.htonl(20000) #轉(zhuǎn)換為一個(gè)32位的值
541982720
>>> socket.ntohl(541982720)
20000
使用SocketServers
SocketServers模塊為一組socket服務(wù)類定義了一個(gè)基類,這組類壓縮和隱藏了監(jiān)聽(tīng)、接受和處理進(jìn)入的socket連接的細(xì)節(jié)。
1、SocketServers家族
TCPServer和UDPServer都是SocketServer的子類,它們分別處理TCP和UDP信息。
注意:SocketServer也提供UnixStreamServer(TCPServer的子類)和UNIXdatagramServer(UDPServer的子類),它們都如同其父類一樣除了在創(chuàng)建監(jiān)聽(tīng)socket時(shí)使用AF_UNIX代替了AF_INET。
默 認(rèn)情況下,socket服務(wù)一次處理一個(gè)連接,但是你可以使用ThreadingMixIN和ForkingMixIn類來(lái)創(chuàng)建任一 SocketServer的線程和子進(jìn)程。實(shí)際上,SocketServer模塊提供了一些對(duì)些有用的類來(lái)解決你的麻煩,它們 是:ForkingUDPServer、ForkingTCPServer、ThreadingUDPServer、 ThreadingTCPServer、ThreadingUnixStreamServer和 ThreadingUnixDatagramServer。
SocketServer以通常的方法處理進(jìn)入的連接;要使它更有用,你應(yīng)該 提供你自己的請(qǐng)求處理器類給它以便它傳遞一個(gè)socket去處理。SocketServer模塊中的BaseRequestHandler類是所有請(qǐng)求處 理器的父類。假設(shè),例如你需要寫一個(gè)多線程的電子郵件服務(wù)器,首先你要?jiǎng)?chuàng)建一個(gè)MailRequestHandler,它是 BaseRequestHandler的子類,然后把它傳遞給一個(gè)新創(chuàng)建的SocketServer:
import SocketServer
...#創(chuàng)建你的MailRequestHandler
addr=('220.172.20.6',25) #監(jiān)聽(tīng)的地址和端口
server=SocketServer.ThreadingTCPServer(addr,MailRequestHandler)
server.serve_forever()
每 次一個(gè)新的連接到來(lái)時(shí),這個(gè)server創(chuàng)建一個(gè)新的MailRequestHandler實(shí)例并調(diào)用它的handle()方法來(lái)處理這個(gè)新的請(qǐng)求。因?yàn)?server繼承自ThreadingTCPServer,對(duì)于每個(gè)新的請(qǐng)求它都啟動(dòng)一個(gè)單獨(dú)的線程來(lái)處理這個(gè)請(qǐng)求,以便于多個(gè)請(qǐng)求能夠被同時(shí)處理。如果 用handle_request()代替server_forever,它將一個(gè)一個(gè)的處理連接請(qǐng)求。server_forever 只是反復(fù)調(diào)用 handle_request而已。
一般來(lái)說(shuō),你只需使用socket服務(wù)之一,但是如果你需要?jiǎng)?chuàng)建你自己的子類的話,你可以覆蓋我們下面提到的方法來(lái)定制它。
當(dāng) 服務(wù)被第一次創(chuàng)建的時(shí)候,__init__函數(shù)調(diào)用server_bind()方法來(lái)綁定監(jiān)聽(tīng)socket(self.socket)到正確的地址 (self.server_address)。然后調(diào)用server_activate()來(lái)激活這個(gè)服務(wù)(默認(rèn)情況下,調(diào)用socket的listen 方法)。
這個(gè)socket服務(wù)不做任何事情直到調(diào)用了handle_request或serve_forever方法。 handle_request調(diào)用get_request()去等待和接收一個(gè)新的socket連接,然后調(diào)用 verify_request(request,client_address)去看服務(wù)是否會(huì)處理這個(gè)連接(你可以在訪問(wèn)控制中使用這個(gè),默認(rèn)情況下 verify_request總是返回true)。如果會(huì)處理這個(gè)請(qǐng)求,handle_request然后調(diào)用 process_request(request,client_address),如果 process_request(request,client_address)導(dǎo)致一個(gè)異常的話,將調(diào)用 handle_error(request,client_address)。默認(rèn)情況下,process_request簡(jiǎn)單地調(diào)用 finish_request(request,client_address);子進(jìn)程和線程類覆蓋了這個(gè)行為去開(kāi)始一新的進(jìn)程或線程,然后調(diào)用 finish_request。finish_request實(shí)例化一個(gè)新的請(qǐng)求處理器,請(qǐng)求處理器輪流調(diào)用它們的handle()方法。
當(dāng)SocketServer創(chuàng)建一個(gè)新的請(qǐng)求處理器時(shí),它傳遞給這個(gè)處理器的__init__函數(shù)的self變量,以便于這個(gè)處理器能夠訪問(wèn)關(guān)于這個(gè)服務(wù)的信息。
SocketServer 的fileno()方法返回監(jiān)聽(tīng)socket的文件描述符。address_family成員變量指定了監(jiān)聽(tīng)socket的socket族(如 AF_INET),server_address包含了監(jiān)聽(tīng)socket被綁定到的地址。socket變量包含監(jiān)聽(tīng)socket自身。
2、請(qǐng)求處理器
請(qǐng) 求處理器有setup()、handle()和finish()方法,你可以覆蓋它們來(lái)定制你自己的行為。一般情況下,你只需要覆蓋handle方法。 BaseRequestHandler的__init__函數(shù)調(diào)用setup()方法來(lái)做初始化的工作,handle()服務(wù)于請(qǐng)求,finish()用 于執(zhí)行清理工作,如果handle或setup導(dǎo)致一個(gè)異常,finish不會(huì)被調(diào)用。記住,你的請(qǐng)求處理器會(huì)為每個(gè)請(qǐng)求創(chuàng)建一個(gè)新的實(shí)例。
request 成員變量有關(guān)于流(TCP)服務(wù)的最近接受的socket;對(duì)于數(shù)據(jù)報(bào)服務(wù),它是一個(gè)包含進(jìn)入消息和監(jiān)聽(tīng)socket的元組。 client_address包含發(fā)送者的地址,server有對(duì)SocketServer的一個(gè)引用(通過(guò)這你可以訪問(wèn)它的成員,如 server_address)。
下面的例子實(shí)現(xiàn)了一個(gè)EchoRequestHandler,這作為一個(gè)服務(wù)端它將客戶端所發(fā)送的數(shù)據(jù)再發(fā)送回客戶端:
>>> 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() #執(zhí)行后將等待連接
Got new connection!
Received: Hello!
Received: I like Tuesdays!
Done with connection
打開(kāi)另一個(gè)Python解釋器作為客戶端,然后執(zhí)行如下代碼:
>>> 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的兩個(gè)子類:StreamRequestHandler和 DatagramRequestHandler。它們覆蓋了setup和finish方法并創(chuàng)建了兩個(gè)文件對(duì)象rfile和wfile,你可以用這兩個(gè)文 件對(duì)象來(lái)向客戶端讀寫數(shù)據(jù),從而代替使用socket方法。
socket的阻塞或同步編程
一、使用socket
網(wǎng) 絡(luò)編程中最基本的部分就是socket(套接字)。socket有兩種:服務(wù)端socket和客戶端 socket。在你創(chuàng)建了一個(gè)服務(wù)端socket之 后,你告訴它去等待連接。然后它將監(jiān)聽(tīng)某個(gè)網(wǎng)絡(luò)地址(形如:xxx.xxx.xxx.xxx:xxx) 直到客戶端連接。然后這兩端就可以通信了。
處理客戶端socket通常比處理服務(wù)端socket要容易一點(diǎn),因?yàn)榉?wù)端必須時(shí)刻準(zhǔn)備處理來(lái)自客戶端的連接,并且它必須處理多個(gè)連接,而客戶端只需要簡(jiǎn)單的連接,然后做點(diǎn)什么,然后斷開(kāi)連接。
實(shí) 例化一個(gè)socket時(shí),可以指定三個(gè)參數(shù):地址系列(默認(rèn)為socket.AF_INET)、流socket(這是個(gè)默認(rèn) 值: socket.SOCK_STREAM)或數(shù)據(jù)報(bào)socket(socket.SOCK_DGRAM)、協(xié)議(默認(rèn)值是0)。對(duì)于簡(jiǎn)單的 socket,你可以不指定任何參數(shù)而全部使用默認(rèn)值。
服務(wù)端socket在使用bind方法之后調(diào)用listen方法去監(jiān)聽(tīng)一個(gè)給定的 地址。然后,客戶端socket就可以通過(guò)使用connect方法(connect方法所使用的地址參數(shù)與bind相同)去連接服務(wù)端。listen方法 要求一個(gè)參數(shù),這個(gè)參數(shù)就是等待連接隊(duì)列中所能包含的連接數(shù)。
一旦服務(wù)端socket調(diào)用了listen方法,就進(jìn)入了臨聽(tīng)狀態(tài),然后通 常使用一個(gè)無(wú)限的循環(huán):1、開(kāi)始接受客房端的連接,這通過(guò)調(diào)用accept方法來(lái)實(shí)現(xiàn)。調(diào)用了這個(gè)方法后將處于阻塞狀態(tài)(等待客戶端發(fā)起連接)直到一個(gè)客 戶端連接,連接后,accept返回形如(client,address)的一個(gè)元組,其中client是一個(gè)用于與客戶端通信的 socket,address是客戶端的形如xxx.xxx.xxx.xxx:xxx的地址;2、然后服務(wù)端處理客戶端的請(qǐng)求;3、處理完成之后又調(diào)用 1。
關(guān)于傳輸數(shù)據(jù),socket有兩個(gè)方法:send和recv。send使用字符串參數(shù)發(fā)送數(shù)據(jù);recv參數(shù)是字節(jié)數(shù),表示一次接受的數(shù)據(jù)量,如果你不確定一次該接受的數(shù)據(jù)量的話,最好使用1024。
下面給出一個(gè)最小的服務(wù)器/客戶機(jī)的例子:
服務(wù)端:
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來(lái)停止服務(wù)端的話,如果再次使用相同的端口可能需要等待一會(huì)兒。
二、使用SocketServer
SocketServer模塊簡(jiǎn)單化了編寫網(wǎng)絡(luò)服務(wù)器的工作。
它提供了四個(gè)基本的服務(wù)類:TCPServer(使用TCP協(xié)議)、UDPServer(使用數(shù)據(jù)報(bào))、UnixStreamServer、
UnixDatagramServer。UnixStreamServer和UnixDatagramServer用于類Unix平臺(tái)。
這四個(gè)類處理請(qǐng)求都使用同步的方法,也就是說(shuō),在下一個(gè)請(qǐng)求處理開(kāi)始之前當(dāng)前的請(qǐng)求處理必須已完成
。
用SocketServer創(chuàng)建一個(gè)服務(wù)器需要四步:
1、通過(guò)子類化BaseRequestHandler類和覆蓋它的handle()方法來(lái)創(chuàng)建一個(gè)請(qǐng)求處理器類,用于處理進(jìn)來(lái)
的請(qǐng)求;
2、實(shí)例化服務(wù)類如TCPServer,并傳遞給它參數(shù):服務(wù)器地址和請(qǐng)求處理器類;
3、調(diào)用服務(wù)實(shí)例對(duì)象的handle_request()或serve_forever()方法去處理請(qǐng)求。
下面使用SocketServer用同步的方法寫一個(gè)最簡(jiǎn)單的服務(wù)器:
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')
#第二步。其中''代表運(yùn)行服務(wù)器的主機(jī)
server = TCPServer(('', 1234), Handler)
#第三步。serve_forever()導(dǎo)致進(jìn)入循環(huán)狀態(tài)
server.serve_forever()
注意:使用阻塞或同步的方法一次只能連接一個(gè)客戶端,處理完成后才能連接下一個(gè)客戶端。
非阻塞或異步編程
- python網(wǎng)絡(luò)編程之TCP通信實(shí)例和socketserver框架使用例子
- python網(wǎng)絡(luò)編程之UDP通信實(shí)例(含服務(wù)器端、客戶端、UDP廣播例子)
- Python 網(wǎng)絡(luò)編程起步(Socket發(fā)送消息)
- python socket網(wǎng)絡(luò)編程步驟詳解(socket套接字使用)
- 用Python進(jìn)行TCP網(wǎng)絡(luò)編程的教程
- python網(wǎng)絡(luò)編程調(diào)用recv函數(shù)完整接收數(shù)據(jù)的三種方法
- python網(wǎng)絡(luò)編程實(shí)例簡(jiǎn)析
- Python網(wǎng)絡(luò)編程中urllib2模塊的用法總結(jié)
- python 網(wǎng)絡(luò)編程詳解及簡(jiǎn)單實(shí)例
- python網(wǎng)絡(luò)編程:socketserver的基本使用方法實(shí)例分析
相關(guān)文章
Python 語(yǔ)言實(shí)現(xiàn)六大查找算法
本文給大家分享Python 語(yǔ)言實(shí)現(xiàn)六大查找算法,針對(duì)每種算法通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-06-06學(xué)習(xí)win32com操作word之Range精講
這篇文章主要為大家介紹了win32com操作word之Range精講學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01Python浮點(diǎn)數(shù)取整、格式化和NaN處理的操作方法
這篇文章主要介紹了Python浮點(diǎn)數(shù)取整、格式化和NaN處理的操作方法,本文較詳細(xì)介紹了取整的三種方法,格式化浮點(diǎn)數(shù)輸出的示例代碼詳解,感興趣的朋友跟隨小編一起看看吧2022-05-05Python實(shí)現(xiàn)最常見(jiàn)加密方式詳解
這篇文章主要介紹了Python實(shí)現(xiàn)最常見(jiàn)加密方式詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07