Python之路【第七篇續】:I/O多路複用

回顧原生Socket

1、Socket起源:html

socket起源於Unix,而Unix/Linux基本哲學之一就是「一切皆文件」,對於文件用【打開】【讀寫】【關閉】模式來操做。python

socket就是該模式的一個實現,socket便是一種特殊的文件,一些socket函數就是對其進行的操做(讀/寫IO、打開、關閉)linux

「他是全部WEB服務器的祖宗」程序員

pupepet、ansible、他們也能夠經過輸入命令而後返回結果這個也是基於Socket來實現的。web

2、socket和file的區別
    file模塊是針對某個指定文件進行【打開】【讀寫】【關閉】
    socket模塊是針對 服務器端 和 客戶端Socket 進行【打開】【讀寫】【關閉】
數據庫

3、原生Socket加強:django

上一篇《初識socket》:server端他們僅能處理一個請求在有鏈接過來的時候,若是 第一個請求在和服務器鏈接中,那麼第二個只能等待第一個斷開後第二個才能鏈接

過程:windows

第一請求發發了一個操做,server端返回了,那麼如今兩頭等在等待這輸入。緩存

那麼這段時間第二個請求還在等待!如今服務端是否是在空閒着呢?他只佔着I/O資源,CPU是否是空閒着呢?他阻塞着後面的請求沒法進來。tomcat

不急繼續往下看!

網絡IO模型:阻塞IO和非阻塞IO|同步IO和異步IO

介紹:

    網絡I/O模型討論的背景是Linux環境下的network IO。本文最重要的參考文獻是Richard Stevens的「UNIX? Network Programming Volume 1, Third Edition: The Sockets Networking 」,6.2節「I/O Models 」,Stevens在這節中詳細說明了各類IO的特色和區別,若是英文夠好的話,推薦直接閱讀。Stevens的文風是有名的深刻淺出,因此不用擔憂看不 懂。

1、什麼是I/O

一、先了解什麼是I/O:I/O(input/output),即輸入/輸出端口。每一個設備都會有一個專用的I/O地址,用來處理本身的輸入輸出信息。

二、I/O model:阻塞:blocking IO、非阻塞:non-blocking IO同步:synchronous  IO  、 異步:asynchronous IO 之間的區別

三、IO發生時涉及的對象和步驟:以輸入操做的socket爲例:第一步:首先等待網絡數據到達,當數據接收就會複製到內核緩衝區中,第二步:複製從內核緩衝區到應用緩衝區

  1. 等待數據準備 (Waiting for the data to be ready)
  2. 將數據從內核拷貝到進程中(Copying the data from the kernel to the process)  記住這兩點很重要,由於這些IO模型的區別就是在兩個階段上各有不一樣的狀況。

2、Blocking I/O Model
默認狀況下全部的Socket是阻塞(分享例子----分享完後須要刪除本括號內容),看下面的圖例:

當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:準備數據。對於network io來講,不少時候數據在一開始尚未到達(好比沒有收到一個完整的TCP/UDP包),這個時候kernel就要等待足夠的數據到來。而在用戶進程這邊,整個進程會被阻塞。當kernel一直等到數據準備好了,它就會將數據從kernel中拷貝到用戶內存,而後kernel返回結果,用戶進程才解除 block的狀態,從新運行起來。

因此阻塞:blocking IO的特色是I/O執行時的兩個操做(等待數據準備 (Waiting for the data to be ready)將數據從內核拷貝到進程中(Copying the data from the kernel to the process))都是阻塞的。

python socket中:accept()  recv() 是阻塞的

因此,所謂阻塞型接口是指系統調用(通常是IO接口)若是不返回結果就一直阻塞,就是socket常常說的,有發就有收收發必相等若是兩邊都在同時收,是否是阻塞着後面的代碼就沒法執行?

那既然原生的Socket是阻塞的,那有什麼辦法來解決呢?

使用多線程(或多進程)、多線程(或多進程)的目的是讓每一個鏈接都擁有獨立的線程(或進程),這樣任何一個鏈接的阻塞都不會影響其餘的鏈接。

 

咱們假設對上述的服務器 / 客戶機模型,提出更高的要求,即讓服務器同時爲多個客戶機提供一問一答的服務。因而有了以下的模型。

 

在上述的線程 / 時間圖例中,主線程持續等待客戶端的鏈接請求,若是有鏈接,則建立新線程,並在新線程中提供爲前例一樣的問答服務。

不少初學者可能不明白爲什麼一個socket能夠accept屢次。實際上socket的設計者可能特地爲多客戶機的狀況留下了伏筆,讓accept()可以返回一個新的socket。

 

執行完bind()和listen()後,操做系統已經開始在指定的端口處監聽全部的鏈接請求,若是有請求,則將該鏈接請求加入請求隊列。

調用accept()接口正是從的請求隊列抽取第一個鏈接信息,建立一個新的socket返回句柄。新的socket句柄便是後續read()和recv()的輸入參數。若是請求隊列當前沒有請求,則accept()將進入阻塞狀態直到有請求進入隊列。

上述多線程的服務器模型彷佛完美的解決了爲多個客戶機提供問答服務的要求,但其實並不盡然。若是要同時響應成百上千路的鏈接請求,則不管多線程仍是多進程都會嚴重佔據系統資源,下降系統對外界響應效率,而線程與進程自己也更容易進入假死狀態。

不少程序員可能會考慮使用「線程池」或「鏈接池」。「線程池」旨在減小建立和銷燬線程的頻率,其維持必定合理數量的線程,並讓空閒的線程從新承擔新的執行任務「鏈接池」維持鏈接的緩存池,儘可能重用已有的鏈接、減小建立和關閉鏈接的頻率。

這兩種技術均可以很好的下降系統開銷,都被普遍應用不少大型系統,如websphere、tomcat和各類數據庫等。可是,「線程池」和「鏈接池」技術也只是在必定程度上緩解了頻繁調用IO接口帶來的資源佔用。並且,所謂「池」始終有其上限,當請求大大超過上限時,「池」構成的系統對外界的響應並不比沒有池的時候效果好多少。因此使用「池」必須考慮其面臨的響應規模,並根據響應規模調整「池」的大小。
對應上例中的所面臨的可能同時出現的上千甚至上萬次的客戶端請求,「線程池」或「鏈接池」或許能夠緩解部分壓力,可是不能解決全部問題。總之,多線程模型能夠方便高效的解決小規模的服務請求,但面對大規模的服務請求,多線程模型也會遇到瓶頸,能夠用非阻塞接口來嘗試解決這個問題。

3、非阻塞:non-blocking IO

(分享例子----分享完後須要刪除本括號內容)

#!/usr/bin/env python
#-*- coding:utf-8 -*-


import time
import socket
#建立socket對象
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.setsockopt
#設置監聽的IP與端口
sk.bind(('127.0.0.1',6666))
#設置client最大等待鏈接數
sk.listen(5)
sk.setblocking(False) #這裏設置setblocking爲Falseaccept將不在阻塞,可是若是沒有收到請求就會報錯

while True: #循環
    try:
        print 'waiting client connection .......'
        #connection表明客戶端對象,address是客戶端的IP
        connection,address = sk.accept()
        #等待接收客戶端信息
        client_messge = connection.recv(1024)
        #打印客戶端信息
        print address
        #發送回執信息給client 收發必須相同
        connection.sendall('hello Client this server')
        connection.send()
        #關閉和client的鏈接
        connection.close()
    except Exception as e:
        print e
    time.sleep(4)
sk.setblocking(False)

 

 

看上面的代碼,我修改了setblocking的值,那麼如今accept()將再也不阻塞。因此他相似下面的圖:

EWOULDBLOCK 意思是說:該操做可能會被阻塞。E是error,WOULD BLOCK是可能會被阻塞的意思。

從圖中能夠看出,當用戶進程發出read操做時,若是kernel中的數據尚未準備好,那麼它並不會block用戶進程,而是馬上返回一個error。從 用戶進程角度講 ,它發起一個read操做後,並不須要等待,而是立刻就獲得了一個結果。用戶進程判斷結果是一個error時,它就知道數據尚未準備好,因而它能夠再次 發送read操做。一旦kernel中的數據準備好了,而且又再次收到了用戶進程的system call,那麼它立刻就將數據拷貝到了用戶內存,而後返回。
    因此,在非阻塞式IO中,用戶進程實際上是須要不斷的主動詢問kernel數據準備好了沒有。     非阻塞的接口相比於阻塞型接口的顯著差別在於,在被調用以後當即返回。python中的  sk.setblocking(False)   accept() 將不會阻塞

4、多路複用IO(IO multiplexing)

IO multiplexing這個詞可能有點陌生,可是若是我說select/epoll,大概就都能明白了。有些地方也稱這種IO方式爲事件驅動IO(event driven IO)。咱們都知道,select/epoll的好處就在於單個process就能夠同時處理多個網絡鏈接的IO。它的基本原理就是select /epoll這個function會不斷的輪詢所負責的全部socket,當某個socket有數據到達了,就通知用戶進程。就通知用戶進程。它的流程如圖:

 

I/O多路複用指:經過一種機制,能夠 監視多個描述符,監聽的描述符發生了改變,可讀了或者可寫了,一旦他發生了改變,那我就能夠獲得一個回調信息或者我主動的去,去知道系統
發生變化了!
 
Python中有一個select模塊,其中提供了: select、poll、epoll三個方法,分別調用系統的 select,poll,epoll 從而實現IO多路複用。
根據系統不一樣:他支持的也不一樣
Windows Python:
    提供: select
Mac Python:
    提供: select
Linux Python:
    提供: select、poll、epoll

注意:網絡操做、文件操做、終端操做等均屬於IO操做,對於windows只支持Socket操做,其餘系統支持其餘IO操做,可是沒法檢測 普通文件操做 自動上次讀取是否已經變化。

普通文件操做全部系統都是完成不了的,普通文件是屬於I/O操做!可是對於python來講文件變動python是監控不了的,因此咱們能用的只有是「終端的輸入輸出,Socket的輸入輸出」

對於Select:

句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超時時間)
 
參數: 可接受四個參數(前三個必須)
返回值:三個列表
 
select方法用來監視文件句柄,若是句柄發生變化,則獲取該句柄。
1、當 參數1 序列中的句柄發生可讀時(accetp和read),則獲取發生變化的句柄並添加到 返回值1 序列中
2、當 參數2 序列中含有句柄時,則將該序列中全部的句柄添加到 返回值2 序列中
3、當 參數3 序列中的句柄發生錯誤時,則將該發生錯誤的句柄添加到 返回值3 序列中
4、當 超時時間 未設置,則select會一直阻塞,直到監聽的句柄發生變化
五、當 超時時間 = 1時,那麼若是監聽的句柄均無任何變化,則select會阻塞 1 秒,以後返回三個空列表,若是監聽的句柄有變化,則直接執行。

利用select監聽終端操做實例

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import select
import sys

while True:
    readable, writeable, error = select.select([sys.stdin,],[],[],1)
    '''select.select([sys.stdin,],[],[],1)用到I/O多路複用,第一個參數是列表,我放進去的是stdin就是我輸入進去東西的描述符,
       至關於打開一個文件,和obj = socket(),相似的文件描述符,
       sys.stdin 他只是一個特殊的文件描述符= 終端的輸入,一旦你輸入OK select I/O多路複用他就感知到了。
       先看readable這個參數,其餘的縣不用看一旦你發生了我就他他發到readable裏了,
       這裏添加的就是修改的那個文件描述符,若是你一直沒有修改過,那麼readable他就是一個空的列表
    '''
    if sys.stdin in readable:
        print 'select get stdin',sys.stdin.readline()

'''
注:
一、[sys.stdin,]  之後無論是列表仍是元組在最後的元素後面建議增長一個逗號,那元組舉例(1,) | (1) 這兩個有區別嗎?是否是第二個
更像方法的調用或者函數的調用,加個,是否是更容易分清楚。還有就是在之後寫django的配置文件的時候,他是必需要加的。寫做習慣
二、select第一個參數他就是監聽多個文件句柄,當誰改變了我是否是就能夠監聽到!
三、select參數裏1是超時時間,當到slect那一行後,若是這裏仍是沒有輸入,那麼我就繼續走!
'''

'''
when runing the program get error :
Traceback (most recent call last):
  File "E:/study/GitHub/homework/tianshuai/share_3_select_socket.py", line 8, in <module>
    readable, writeable, error = select.select([sys.stdin,],[],[],1)
select.error: (10093, 'Either the application has not called WSAStartup, or WSAStartup failed')

when windows only use select socket !!!!!
'''
frist select stdin

利用select監聽終端操做實例

#/usr/bin/env python
#-*- coding:utf-8 -*-
import time
import socket
import select
#建立socket對象
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.setsockopt
#設置監聽的IP與端口
sk.bind(('127.0.0.1',6666))
#設置client最大等待鏈接數
sk.listen(5)
sk.setblocking(False) #這裏設置setblocking爲Falseaccept將不在阻塞,可是若是沒有收到請求就會報錯
while True:
    readable_list, writeable_list, error_list = select.select([sk,],[],[],2)  #監聽第一個列表的文件描述符,若是裏面有文件描述符發生改變既能捕獲並放到readable_list中
    for r in readable_list:    #若是是空列表將不執行,若是是空列表。將執行。
        conn,addr = r.accept()
        print addr
select socket

執行程序並打開IE輸入地址:127.0.0.1:6666

('127.0.0.1', 53606)

利用select監聽多端口操做實例

#/usr/bin/env python
#-*- coding:utf-8 -*-
import time
import socket
import select
#建立socket對象
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.setsockopt
#設置監聽的IP與端口
sk.bind(('127.0.0.1',6666))
#設置client最大等待鏈接數
sk.listen(5)
sk.setblocking(False) #這裏設置setblocking爲Falseaccept將不在阻塞,可是若是沒有收到請求就會報錯

sk1 = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk1.setsockopt
#設置監聽的IP與端口
sk1.bind(('127.0.0.1',7777))
#設置client最大等待鏈接數
sk1.listen(5)
sk1.setblocking(False) #這裏設置setblocking爲Falseaccept將不在阻塞,可是若是沒有收到請求就會報錯


while True:
    readable_list, writeable_list, error_list = select.select([sk,sk1,],[],[],2)  #監聽第一個列表的文件描述符,若是裏面有文件描述符發生改變既能捕獲並放到readable_list中
    for r in readable_list:    #若是是空列表將不執行,若是是空列表。將執行。
        conn,address = r.accept()
        print address
select socket mulit port
('127.0.0.1', 53809)
('127.0.0.1', 53811)

利用select模擬僞Socket Server操做實例 

#/usr/bin/env python
#-*- coding:utf-8 -*-
import time
import socket
import select
#建立socket對象
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.setsockopt
#設置監聽的IP與端口
sk.bind(('127.0.0.1',6666))
#設置client最大等待鏈接數
sk.listen(5)
sk.setblocking(False) #這裏設置setblocking爲Falseaccept將不在阻塞,可是若是沒有收到請求就會報錯
inputs = [sk,] #將sk這個對象加入到列表中,而且賦值給inputs
#緣由:看上例conn是客戶端對象,客戶是一直鏈接着呢,鏈接的時候狀態變了,鏈接上以後,鏈接上以後,仍是服務端的socket 有關嗎?
#是否是的把他改成動態的?

while True:
    readable_list, writeable_list, error_list = select.select(inputs,[],[],1)  #把第一個參數設爲列表動態的添加
    time.sleep(2) #測試使用
    print "inputs list :",inputs     #打印inputs列表,查看執行變化
    print "file descriptor :",readable_list #打印readable_list ,查看執行變化

    for r in readable_list:
        if r == sk:  #這裏判斷,若是是客戶端鏈接過來的話他不是sk,若是是服務端的socket鏈接過來的話是sk
            conn,address = r.accept()
            inputs.append(conn)
            print address
        else:
        #若是是客戶端,接受和返回數據
            client_data = r.recv(1024)
            r.sendall(client_data)
select socket server - server
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import socket

client = socket.socket()
client.connect(('127.0.0.1',6666))
client.settimeout(5)

while True:
    client_input = raw_input('please input message:').strip()
    client.sendall(client_input)
    server_data = client.recv(1024)
    print server_data
select socket server - client

交互過程:

#1  默認,sk這個對象文件句柄就在inputs列表中select監聽客戶端的請求,當有客戶端請求過來 client1 ---> server
#用戶捕獲了變化readable_list = [sk,]  那麼循環是有值得,判斷r = sk 說明是一個新的請求連接,而後把client連接加入到inputs裏 inputs = [sk,conn1,]
#若是如今什麼都不作,那麼select沒法捕獲到變化:readable_list = []
#執行看下:
inputs list : [<socket._socketobject object at 0x0000000002C66798>] #默認inputs list 就有一個server socket sk 對象
file descriptor : [<socket._socketobject object at 0x0000000002C66798>]  #當有客戶端請求過來時候,sk發生了變化,select捕獲到了
('127.0.0.1', 62495)
inputs list : [<socket._socketobject object at 0x0000000002C66798>, <socket._socketobject object at 0x0000000002C66800>]  #第二次循環的時候,inputs = [sk,conn1,]
file descriptor : [] #第二次循環的時候readable_list = [] 由於客戶端沒有作任何操做,沒有捕獲到變化因此爲空

#2 又有一個新的連接過來了,誰變化了?  sk 他變化了,有人向他發起了一個請求連接,那麼如今inputs = [sk,conn1,conn2]  readable_list = [sk]
#本次循環完成以後再循環的時候 inputs = [sk,conn1,conn2,]  readable_list = [] 由於咱們沒有繼續作操做

#第一個連接
inputs list : [<socket._socketobject object at 0x0000000002C56798>]  #默認只有一個對象
file descriptor : []
inputs list : [<socket._socketobject object at 0x0000000002C56798>]  
file descriptor : [<socket._socketobject object at 0x0000000002C56798>] #當捕獲到,判斷是不是新連接,若是是加入到inputs列表中監控
('127.0.0.1', 62539)
inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>]  #inputs列表變動爲了[sk,conn1]
file descriptor : []  #由於沒有後續的操做,這裏沒有捕獲到異常因此列表爲空

#第二個連接
inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>]  #第一個連接沒有作任何操做
file descriptor : [<socket._socketobject object at 0x0000000002C56798>] #第二個連接過來了被捕獲到,判斷是否爲新連接
('127.0.0.1', 62548)
inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>, <socket._socketobject object at 0x0000000002C56868>] #加入到inputs列表中
file descriptor : []
inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>, <socket._socketobject object at 0x0000000002C56868>]
file descriptor : []
inputs list : [<socket._socketobject object at 0x0000000002C56798>, <socket._socketobject object at 0x0000000002C56800>, <socket._socketobject object at 0x0000000002C56868>]
file descriptor : []

 優化:當client端退出後,在inputs列表中移除對象!

#/usr/bin/env python
#-*- coding:utf-8 -*-
import time
import socket
import select
#建立socket對象
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.setsockopt
#設置監聽的IP與端口
sk.bind(('127.0.0.1',6666))
#設置client最大等待鏈接數
sk.listen(5)
sk.setblocking(False) #這裏設置setblocking爲Falseaccept將不在阻塞,可是若是沒有收到請求就會報錯
inputs = [sk,] #將sk這個對象加入到列表中,而且賦值給inputs
#緣由:看上例conn是客戶端對象,客戶是一直鏈接着呢,鏈接的時候狀態變了,鏈接上以後,鏈接上以後,仍是服務端的socket 有關嗎?
#是否是的把他改成動態的?

while True:
    readable_list, writeable_list, error_list = select.select(inputs,[],[],1)  #把第一個參數設爲列表動態的添加
    time.sleep(2) #測試使用
    print "inputs list :",inputs     #打印inputs列表,查看執行變化
    print "file descriptor :",readable_list #打印readable_list ,查看執行變化

    for r in readable_list:
        if r == sk:  #這裏判斷,若是是客戶端鏈接過來的話他不是sk,若是是服務端的socket鏈接過來的話是sk
            conn,address = r.accept()
            inputs.append(conn)
            print address
        else:
        #若是是客戶端,接受和返回數據
            client_data = r.recv(1024)
            if client_data:
                r.sendall(client_data)
            else:
                inputs.remove(r)#若是沒有收到客戶端端數據,則移除客戶端句柄 由於,無論是正常關閉仍是異常關閉,client端的系統底層都會發送一個消息
select socket server - server release client-connect

經過I/O多路複用讓socket實現了處理多個客戶端的方法,參數註解:

#第一個參數,監聽的句柄序列,當有變更的時候就能捕獲到把值賦值給readable_list
#若是第二參數有參數,即只要不是空列表,select就能感知,而後writeabled_list就能獲取值
#第三個參數監聽描述符,select內部,檢測列表裏面的描述符在底層操做的時候有沒有異常,若是異常了他也當成一個變化,把這個賦值給error_list 通常第三個參數和第一個參數相同
#第四個參數,阻塞時間,如 1秒(這個若是不寫,select會阻塞住,直到監聽的描述符發生變化才繼續往下執行)
readable_list, writeable_list, error_list = select.select(inputs,[],[],1)  

對於I/O多路複用,我們上面的例子就能夠了,可是爲了遵循select規範須要把讀和寫進行分離:

#rlist -- wait until ready for reading  #等待直到有讀的操做
#wlist -- wait until ready for writing  #等待直到有寫的操做
#xlist -- wait for an ``exceptional condition'' #等待一個錯誤的狀況

 

讀和寫他共享接收的數據,僅僅靠變量是完成不了的,還的須要藉助外界的字典,字典裏爲每個客戶度維護了一個隊列。收到的信息都放到隊列了,而後返回的時候直接從隊列裏拿就能夠了

Queue 隊列

隊列的特色:

一、隊列是先進先出,棧是相反的,後進先出
二、隊列是線程安全的
import Queue

q = Queue.Queue() #調用隊列生成對象
q.put(1)  #存放第一個值到隊列
q.put(2)  #存放第二個值到隊列


print 'get frist one:',q.get() #獲取隊列的第一個值
print 'get second on:',q.get() #獲取隊列的第二個值

先進先出原則第一次存放的是1,第二次存放的是2,那麼咱們在獲取值得時候,第一次獲取的就是1,第二次就是2

看下面的例子若是隊列裏沒有值怎麼辦?他會等待直到有數據爲止

q = Queue.Queue() #調用隊列生成對象

q.put(1)  #存放第一個值到隊列
q.put(2)  #存放第二個值到隊列

a = q.get() #獲取隊列的第一個值
print 'get frist one:%s' % a
b = q.get() #獲取隊列的第二個值
print 'get second one:%s' % b
c = q.get()#獲取隊列的第三個值
print 'get third one:%s' % c

#結果:
'''
get frist one:1
get second one:2
#這裏一直在等待着值進來~
'''

若是不想讓他等待,無論是否隊列裏都取數據,可使用get_nowait,可是若是隊列中沒有數據就會報錯!

q = Queue.Queue() #調用隊列生成對象

q.put(1)  #存放第一個值到隊列
q.put(2)  #存放第二個值到隊列

a = q.get() #獲取隊列的第一個值
print 'get frist one:%s' % a
b = q.get() #獲取隊列的第二個值
print 'get second one:%s' % b
c = q.get_nowait()#獲取隊列的第三個值 ,使用:get_nowait()
print 'get third one:%s' % c

若是隊列爲空的時候能夠經過異常處理進行捕獲:

q = Queue.Queue() #調用隊列生成對象
try:
    q.get_nowait()
except Queue.Empty as f:
    print 'The Queue is empty!'

一樣的若是隊列長度爲2,若是隊列滿了以後,一樣他也是等待,直到有位置纔會繼續以下代碼:

q = Queue.Queue(2) #調用隊列生成對象

q.put(1)  #存放第一個值到隊列
print 'put value 1 done'
q.put(2)  #存放第二個值到隊列
print 'put vlaue 2 done'
q.put(3) #存放第三個值到隊列
print 'put value 3 done'


#結果:
'''
put value 1 done
put vlaue 2 done
#這裏會一直等待~
'''

一樣若是存放數值的時候若是不想讓他等待,使用put_nowait()可是隊列沒法存放後會報錯!

q = Queue.Queue(2) #調用隊列生成對象

q.put(1)  #存放第一個值到隊列
print 'put value 1 done'
q.put(2)  #存放第二個值到隊列
print 'put vlaue 2 done'
q.put_nowait(3) #存放第三個值到隊列,若是使用put_nowait()隊列沒法存放後會報錯!
print 'put value 3 done'
#結果:
'''
put value 1 done
put vlaue 2 done
#這裏會一直等待~

 利用select模擬僞Socket Server操做實例並把讀/寫進行分離

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luo_t'
import select
import socket
import Queue
import time

sk = socket.socket()
sk.bind(('127.0.0.1',6666))
sk.listen(5)
sk.setblocking(False) #定義非阻塞
inputs = [sk,]  #定義一個列表,select第一個參數監聽句柄序列,當有變更是,捕獲並把socket server加入到句柄序列中
outputs = [] #定義一個列表,select第二個參數監聽句柄序列,當有值時就捕獲,並加入到句柄序列
message = {}
#message的樣板信息
#message = {
#    'c1':隊列,[這裏存放着用戶C1發過來的消息]例如:[message1,message2]
#    'c2':隊列,[這裏存放着用戶C2發過來的消息]例如:[message1,message2]
#}


while True:
    readable_list, writeable_list, error_list = select.select(inputs,outputs,[],1)
    #文件描述符可讀 readable_list    只有第一個參數變化時候才捕獲,並賦值給readable_list
    #文件描述符可寫 writeable_list   只要有值,第二個參數就捕獲並賦值給writeable_list
    #time.sleep(2)
    print 'inputs:',inputs
    print 'output:'
    print 'readable_list:',readable_list
    print 'writeable_list:',writeable_list
    print 'message',message
    for r in readable_list: #當readable_list有值得時候循環
        if r == sk:  #判斷是否爲連接請求變化的是不是socket server
            conn,addr = r.accept() #獲取請求
            inputs.append(conn) #把客戶端對象(句柄)加入到inputs裏
            message[conn] = Queue.Queue() #並在字典裏爲這個客戶端鏈接創建一個消息隊列
        else:
            client_data = r.recv(1024) #若是請求的不是sk是客戶端接收消息
            if client_data:#若是有數據
                outputs.append(r)#把用戶加入到outpus裏觸發select第二個參數
                message[r].put(client_data)#在指定隊列中插入數據
            else:
                inputs.remove(r)#沒有數據,刪除監聽連接
                del message[r] #當數據爲空的時候刪除隊列~~
    for w in writeable_list:#若是第二個參數有數據
        try:
            data = message[w].get_nowait()#去指定隊列取數據 而且不阻塞
            w.sendall(data) #返回請求輸入給client端
        except Queue.Empty:#反之觸發異常
            pass
        outputs.remove(w) #由於第二個參數有值得時候就觸發捕獲值,因此使用完以後須要移除它
        #del message[r]
    print '%s' %('-' * 40)
select socket server - server read | write separation

使用select() 的事件驅動模型只用單線程(進程)執行,佔用資源少,不消耗太多 CPU,同時可以爲多客戶端提供服務。若是試圖創建一個簡單的事件驅動的服務器程序,這個模型有必定的參考價值。
但這個模型依舊有着不少問題。首先select()接口並非實現「事件驅動」的最好選擇。由於當須要探測的句柄值較大時,select()接口自己須要消耗大量時間去輪詢各個句柄。很 多操做系統提供了更爲高效的接口,如linux提供了epoll,BSD提供了queue,Solaris提供了/dev/poll,…。若是須要實現 更高效的服務器程序,相似epoll這樣的接口更被推薦。遺憾的是不一樣的操做系統特供的epoll接口有很大差別,因此使用相似於epoll的接口實現具 有較好跨平臺能力的服務器會比較困難。
    其次,該模型將事件探測和事件響應夾雜在一塊兒,一旦事件響應的執行體龐大,則對整個模型是災難性的。以下例,龐大的執行體1的將直接致使響應事件2的執行體遲遲得不到執行,並在很大程度上下降了事件探測的及時性。

 5、異步I/O(asynchronous IO)

用戶進程發起read操做以後,馬上就能夠開始去作其它的事。而另外一方面,從kernel的角度,當它受到一個asynchronous read以後,首先它會馬上返回,因此不會對用戶進程產生任何block。而後,kernel會等待數據準備完成,而後將數據拷貝到用戶內存,當這一切都 完成以後,kernel會給用戶進程發送一個signal,告訴它read操做完成了。

用異步IO實現的服務器這裏就不舉例了,之後有時間另開文章來說述。異步IO是真正非阻塞的,它不會對請求進程產生任何的阻塞,所以對高併發的網絡服務器實現相當重要

到目前爲止,已經將四個IO模型都介紹完了。如今回過頭來看下面的問題:

一、blocking和non-blocking的區別在哪?

二、synchronous IO和asynchronous IO的區別在哪?

回答:

一、blocking和non-blocking的區別在哪?

blocking與non-blocking。前面的介紹中其實已經很明確的說明了這二者的區別。調用blocking IO會一直block住對應的進程直到操做完成,而non-blocking IO在kernel還在準備數據的狀況下會馬上返回。

二、synchronous IO和asynchronous IO的區別在哪?

在說明synchronous IO和asynchronous IO的區別以前,須要先給出二者的定義。Stevens給出的定義(實際上是POSIX的定義)是這樣子的:

  • A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
  • An asynchronous I/O operation does not cause the requesting process to be blocked;

二者的區別就在於synchronous IO作」IO operation」的時候會將process阻塞。按照這個定義,以前所述的blocking IO,non-blocking IO,IO multiplexing都屬於synchronous IO。有人可能會說,non-blocking IO並無被block啊。這裏有個很是「狡猾」的地方,定義中所指的」IO operation」是指真實的IO操做,就是例子中的recvfrom這個系統調用。

non-blocking IO在執行recvfrom這個系統調用的時候,若是kernel的數據沒有準備好,這時候不會block進程。可是當kernel中數據準備好的時 候,recvfrom會將數據從kernel拷貝到用戶內存中,這個時候進程是被block了,在這段時間內進程是被block的。

而asynchronous IO則不同,當進程發起IO操做以後,就直接返回不再理睬了,直到kernel發送一個信號,告訴進程說IO完成。在這整個過程當中,進程徹底沒有被block。

6、I/O多路複用的應用場景

#(1)當客戶處理多個描述字時(通常是交互式輸入和網絡套接口),必須使用I/O複用。
#(2)當一個客戶同時處理多個套接口時,而這種狀況是可能的,但不多出現。
#(3)若是一個TCP服務器既要處理監聽套接口,又要處理已鏈接套接口,通常也要用到I/O複用。
#(4)若是一個服務器即要處理TCP,又要處理UDP,通常要使用I/O複用。
#(5)若是一個服務器要處理多個服務或多個協議,通常要使用I/O複用。
'''與多進程和多線程技術相比,I/O多路複用技術的最大優點是系統開銷小,系統沒必要建立進程/線程,也沒必要維護這些進程/線程,從而大大減少了系統的開銷。'''

 

最後,再舉幾個不是很恰當的例子來講明這四個IO Model:
有A,B,C,D四我的在釣魚:
A用的是最老式的魚竿,因此呢,得一直守着,等到魚上鉤了再拉桿;【阻塞】
B的魚竿有個功能,可以顯示是否有魚上鉤(這個顯示功能一直去判斷魚是否上鉤),因此呢,B就和旁邊的MM聊天,隔會再看看有沒有魚上鉤,有的話就迅速拉桿;【非阻塞】
C用的魚竿和B差很少,但他想了一個好辦法,就是同時放好幾根魚竿,而後守在旁邊,一旦有顯示說魚上鉤了,它就將對應的魚竿拉起來;【同步】
D是個有錢人,乾脆僱了一我的幫他釣魚,一旦那我的把魚釣上來了,就給D發個短信(消息回掉機制,主動告知)。【異步】

 

參考資料:

http://www.cnblogs.com/wupeiqi/articles/5040823.html

http://blog.chinaunix.net/uid-28458801-id-4464639.html

相關文章
相關標籤/搜索