欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

總結(jié)網(wǎng)絡(luò)IO模型與select模型的Python實例講解

 更新時間:2016年06月27日 16:49:05   作者:人世間  
同步、異步、阻塞、非阻塞,當(dāng)這些網(wǎng)絡(luò)IO名詞堆到一起時難免使編程初學(xué)者感到困惑,這里我們就來為大家總結(jié)網(wǎng)絡(luò)IO模型與select模型的Python實例講解:

網(wǎng)絡(luò)I/O模型
人多了,就會有問題。web剛出現(xiàn)的時候,光顧的人很少。近年來網(wǎng)絡(luò)應(yīng)用規(guī)模逐漸擴大,應(yīng)用的架構(gòu)也需要隨之改變。C10k的問題,讓工程師們需要思考服務(wù)的性能與應(yīng)用的并發(fā)能力。

網(wǎng)絡(luò)應(yīng)用需要處理的無非就是兩大類問題,網(wǎng)絡(luò)I/O,數(shù)據(jù)計算。相對于后者,網(wǎng)絡(luò)I/O的延遲,給應(yīng)用帶來的性能瓶頸大于后者。網(wǎng)絡(luò)I/O的模型大致有如下幾種:

  • 同步模型(synchronous I/O)
  • 阻塞I/O(bloking I/O)
  • 非阻塞I/O(non-blocking I/O)
  • 多路復(fù)用I/O(multiplexing I/O)
  • 信號驅(qū)動式I/O(signal-driven I/O)
  • 異步I/O(asynchronous I/O)

網(wǎng)絡(luò)I/O的本質(zhì)是socket的讀取,socket在linux系統(tǒng)被抽象為流,I/O可以理解為對流的操作。這個操作又分為兩個階段:

等待流數(shù)據(jù)準(zhǔn)備(wating for the data to be ready)。
從內(nèi)核向進程復(fù)制數(shù)據(jù)(copying the data from the kernel to the process)。
對于socket流而已,

第一步通常涉及等待網(wǎng)絡(luò)上的數(shù)據(jù)分組到達,然后被復(fù)制到內(nèi)核的某個緩沖區(qū)。
第二步把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用進程緩沖區(qū)。
I/O模型:
舉個簡單比喻,來了解這幾種模型。網(wǎng)絡(luò)IO好比釣魚,等待魚上鉤就是網(wǎng)絡(luò)中等待數(shù)據(jù)準(zhǔn)備好的過程,魚上鉤了,把魚拉上岸就是內(nèi)核復(fù)制數(shù)據(jù)階段。釣魚的人就是一個應(yīng)用進程。

阻塞I/O(bloking I/O)
阻塞I/O是最流行的I/O模型。它符合人們最常見的思考邏輯。阻塞就是進程 "被" 休息, CPU處理其它進程去了。在網(wǎng)絡(luò)I/O的時候,進程發(fā)起recvform系統(tǒng)調(diào)用,然后進程就被阻塞了,什么也不干,直到數(shù)據(jù)準(zhǔn)備好,并且將數(shù)據(jù)從內(nèi)核復(fù)制到用戶進程,最后進程再處理數(shù)據(jù),在等待數(shù)據(jù)到處理數(shù)據(jù)的兩個階段,整個進程都被阻塞。不能處理別的網(wǎng)絡(luò)I/O。大致如下圖:

2016627164407309.jpg (686×379)

這就好比我們?nèi)メ烎~,拋竿之后就一直在岸邊等,直到等待魚上鉤。然后再一次拋竿,等待下一條魚上鉤,等待的時候,什么事情也不做,大概會胡思亂想吧。

阻塞IO的特點就是在IO執(zhí)行的兩個階段都被block了
非阻塞I/O(non-bloking I/O)
在網(wǎng)絡(luò)I/O時候,非阻塞I/O也會進行recvform系統(tǒng)調(diào)用,檢查數(shù)據(jù)是否準(zhǔn)備好,與阻塞I/O不一樣,"非阻塞將大的整片時間的阻塞分成N多的小的阻塞, 所以進程不斷地有機會 '被' CPU光顧"。

也就是說非阻塞的recvform系統(tǒng)調(diào)用調(diào)用之后,進程并沒有被阻塞,內(nèi)核馬上返回給進程,如果數(shù)據(jù)還沒準(zhǔn)備好,此時會返回一個error。進程在返回之后,可以干點別的事情,然后再發(fā)起recvform系統(tǒng)調(diào)用。重復(fù)上面的過程,循環(huán)往復(fù)的進行recvform系統(tǒng)調(diào)用。這個過程通常被稱之為輪詢。輪詢檢查內(nèi)核數(shù)據(jù),直到數(shù)據(jù)準(zhǔn)備好,再拷貝數(shù)據(jù)到進程,進行數(shù)據(jù)處理。需要注意,拷貝數(shù)據(jù)整個過程,進程仍然是屬于阻塞的狀態(tài)。

2016627164443436.jpg (780×410)

我們再用釣魚的方式來類別,當(dāng)我們拋竿入水之后,就看下魚漂是否有動靜,如果沒有魚上鉤,就去干點別的事情,比如再挖幾條蚯蚓。然后不久又來看看魚漂是否有魚上鉤。這樣往返的檢查又離開,直到魚上鉤,再進行處理。

非阻塞 IO的特點是用戶進程需要不斷的主動詢問kernel數(shù)據(jù)是否準(zhǔn)備好。
多路復(fù)用I/O(multiplexing I/O)
可以看出,由于非阻塞的調(diào)用,輪詢占據(jù)了很大一部分過程,輪詢會消耗大量的CPU時間。結(jié)合前面兩種模式。如果輪詢不是進程的用戶態(tài),而是有人幫忙就好了。多路復(fù)用正好處理這樣的問題。

多路復(fù)用有兩個特別的系統(tǒng)調(diào)用select或poll。select調(diào)用是內(nèi)核級別的,select輪詢相對非阻塞的輪詢的區(qū)別在于---前者可以等待多個socket,當(dāng)其中任何一個socket的數(shù)據(jù)準(zhǔn)好了,就能返回進行可讀,然后進程再進行recvform系統(tǒng)調(diào)用,將數(shù)據(jù)由內(nèi)核拷貝到用戶進程,當(dāng)然這個過程是阻塞的。多路復(fù)用有兩種阻塞,select或poll調(diào)用之后,會阻塞進程,與第一種阻塞不同在于,此時的select不是等到socket數(shù)據(jù)全部到達再處理, 而是有了一部分?jǐn)?shù)據(jù)就會調(diào)用用戶進程來處理。如何知道有一部分?jǐn)?shù)據(jù)到達了呢?監(jiān)視的事情交給了內(nèi)核,內(nèi)核負(fù)責(zé)數(shù)據(jù)到達的處理。也可以理解為"非阻塞"吧。

2016627164501914.jpg (812×411)

對于多路復(fù)用,也就是輪詢多個socket。釣魚的時候,我們雇了一個幫手,他可以同時拋下多個釣魚竿,任何一桿的魚一上鉤,他就會拉桿。他只負(fù)責(zé)幫我們釣魚,并不會幫我們處理,所以我們還得在一幫等著,等他把收桿。我們再處理魚。多路復(fù)用既然可以處理多個I/O,也就帶來了新的問題,多個I/O之間的順序變得不確定了,當(dāng)然也可以針對不同的編號。

多路復(fù)用的特點是通過一種機制一個進程能同時等待IO文件描述符,內(nèi)核監(jiān)視這些文件描述符(套接字描述符),其中的任意一個進入讀就緒狀態(tài),select, poll,epoll函數(shù)就可以返回。對于監(jiān)視的方式,又可以分為 select, poll, epoll三種方式。
了解了前面三種模式,在用戶進程進行系統(tǒng)調(diào)用的時候,他們在等待數(shù)據(jù)到來的時候,處理的方式不一樣,直接等待,輪詢,select或poll輪詢,第一個過程有的阻塞,有的不阻塞,有的可以阻塞又可以不阻塞。當(dāng)時第二個過程都是阻塞的。從整個I/O過程來看,他們都是順序執(zhí)行的,因此可以歸為同步模型(asynchronous)。都是進程主動向內(nèi)核檢查。

異步I/O(asynchronous I/O)
相對于同步I/O,異步I/O不是順序執(zhí)行。用戶進程進行aio_read系統(tǒng)調(diào)用之后,無論內(nèi)核數(shù)據(jù)是否準(zhǔn)備好,都會直接返回給用戶進程,然后用戶態(tài)進程可以去做別的事情。等到socket數(shù)據(jù)準(zhǔn)備好了,內(nèi)核直接復(fù)制數(shù)據(jù)給進程,然后從內(nèi)核向進程發(fā)送通知。I/O兩個階段,進程都是非阻塞的。

2016627164611582.jpg (764×473)

比之前的釣魚方式不一樣,這一次我們雇了一個釣魚高手。他不僅會釣魚,還會在魚上鉤之后給我們發(fā)短信,通知我們魚已經(jīng)準(zhǔn)備好了。我們只要委托他去拋竿,然后就能跑去干別的事情了,直到他的短信。我們再回來處理已經(jīng)上岸的魚。

同步和異步的區(qū)別
通過對上述幾種模型的討論,需要區(qū)分阻塞和非阻塞,同步和異步。他們其實是兩組概念。區(qū)別前一組比較容易,后一種往往容易和前面混合。在我看來,所謂同步就是在整個I/O過程。尤其是拷貝數(shù)據(jù)的過程是阻塞進程的,并且都是應(yīng)用進程態(tài)去檢查內(nèi)核態(tài)。而異步則是整個過程I/O過程用戶進程都是非阻塞的,并且當(dāng)拷貝數(shù)據(jù)的時是由內(nèi)核發(fā)送通知給用戶進程。

2016627164629348.jpg (868×463)

對于同步模型,主要是第一階段處理方法不一樣。而異步模型,兩個階段都不一樣。這里我們忽略了信號驅(qū)動模式。這幾個名詞還是容易讓人迷惑,只有同步模型才考慮阻塞和非阻塞,因為異步肯定是非阻塞,異步非阻塞的說法感覺畫蛇添足。


Select 模型
同步模型中,使用多路復(fù)用I/O可以提高服務(wù)器的性能。
在多路復(fù)用的模型中,比較常用的有select模型和poll模型。這兩個都是系統(tǒng)接口,由操作系統(tǒng)提供。當(dāng)然,Python的select模塊進行了更高級的封裝。select與poll的底層原理都差不多。千呼萬喚始出來,本文的重點select模型。
1.select 原理
網(wǎng)絡(luò)通信被Unix系統(tǒng)抽象為文件的讀寫,通常是一個設(shè)備,由設(shè)備驅(qū)動程序提供,驅(qū)動可以知道自身的數(shù)據(jù)是否可用。支持阻塞操作的設(shè)備驅(qū)動通常會實現(xiàn)一組自身的等待隊列,如讀/寫等待隊列用于支持上層(用戶層)所需的block或non-block操作。設(shè)備的文件的資源如果可用(可讀或者可寫)則會通知進程,反之則會讓進程睡眠,等到數(shù)據(jù)到來可用的時候,再喚醒進程。

這些設(shè)備的文件描述符被放在一個數(shù)組中,然后select調(diào)用的時候遍歷這個數(shù)組,如果對于的文件描述符可讀則會返回改文件描述符。當(dāng)遍歷結(jié)束之后,如果仍然沒有一個可用設(shè)備文件描述符,select讓用戶進程則會睡眠,直到等待資源可用的時候在喚醒,遍歷之前那個監(jiān)視的數(shù)組。每次遍歷都是線性的。

2.select 回顯服務(wù)器
select涉及系統(tǒng)調(diào)用和操作系統(tǒng)相關(guān)的知識,因此單從字面上理解其原理還是比較乏味。用代碼來演示最好不過了。使用python的select模塊很容易寫出下面一個回顯服務(wù)器:

import select
import socket
import sys

HOST = 'localhost'
PORT = 5000
BUFFER_SIZE = 1024

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))
server.listen(5)

inputs = [server, sys.stdin]
running = True

while True:
  try:
    # 調(diào)用 select 函數(shù),阻塞等待
    readable, writeable, exceptional = select.select(inputs, [], [])
  except select.error, e:
    break

  # 數(shù)據(jù)抵達,循環(huán)
  for sock in readable:
    # 建立連接
    if sock == server:
      conn, addr = server.accept()
      # select 監(jiān)聽的socket
      inputs.append(conn)
    elif sock == sys.stdin:
      junk = sys.stdin.readlines()
      running = False
    else:
      try:
        # 讀取客戶端連接發(fā)送的數(shù)據(jù)
        data = sock.recv(BUFFER_SIZE)
        if data:
          sock.send(data)
          if data.endswith('\r\n\r\n'):
            # 移除select監(jiān)聽的socket
            inputs.remove(sock)
            sock.close()
        else:
          # 移除select監(jiān)聽的socket
          inputs.remove(sock)
          sock.close()
      except socket.error, e:
        inputs.remove(sock)

server.close()

運行上述代碼,使用curl訪問http://localhost:5000,即可看命令行返回請求的HTTP request信息。

下面詳細解析上述代碼的原理。

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))
server.listen(5)

上述代碼使用socket初始化一個TCP套接字,并綁定主機地址和端口,然后設(shè)置服務(wù)器監(jiān)聽。

inputs = [server, sys.stdin]

這里定義了一個需要select監(jiān)聽的列表,列表里面是需要監(jiān)聽的對象(等于系統(tǒng)監(jiān)聽的文件描述符)。這里監(jiān)聽socket套接字和用戶的輸入。

然后代碼進行一個服務(wù)器無線循環(huán)。

try:
  # 調(diào)用 select 函數(shù),阻塞等待
  readable, writeable, exceptional = select.select(inputs, [], [])
except select.error, e:
  break

調(diào)用了select函數(shù),開始循環(huán)遍歷監(jiān)聽傳入的列表inputs。如果沒有curl服務(wù)器,此時沒有建立tcp客戶端連接,因此改列表內(nèi)的對象都是數(shù)據(jù)資源不可用。因此select阻塞不返回。

客戶端輸入curl http://localhost:5000之后,一個套接字通信開始,此時input中的第一個對象server由不可用變成可用。因此select函數(shù)調(diào)用返回,此時的readable有一個套接字對象(文件描述符可讀)。

for sock in readable:
  # 建立連接
  if sock == server:
    conn, addr = server.accept()
    # select 監(jiān)聽的socket
    inputs.append(conn)

select返回之后,接下來遍歷可讀的文件對象,此時的可讀中只有一個套接字連接,調(diào)用套接字的accept()方法建立TCP三次握手的連接,然后把該連接對象追加到inputs監(jiān)視列表中,表示我們要監(jiān)視該連接是否有數(shù)據(jù)IO操作。

由于此時readable只有一個可用的對象,因此遍歷結(jié)束。再回到主循環(huán),再次調(diào)用select,此時調(diào)用的時候,不僅會遍歷監(jiān)視是否有新的連接需要建立,還是監(jiān)視剛才追加的連接。如果curl的數(shù)據(jù)到了,select再返回到readable,此時在進行for循環(huán)。如果沒有新的套接字,將會執(zhí)行下面的代碼:

try:
  # 讀取客戶端連接發(fā)送的數(shù)據(jù)
  data = sock.recv(BUFFER_SIZE)
  if data:
    sock.send(data)
    if data.endswith('\r\n\r\n'):
      # 移除select監(jiān)聽的socket
      inputs.remove(sock)
      sock.close()
  else:
    # 移除select監(jiān)聽的socket
    inputs.remove(sock)
    sock.close()
except socket.error, e:
  inputs.remove(sock)

通過套接字連接調(diào)用recv函數(shù),獲取客戶端發(fā)送的數(shù)據(jù),當(dāng)數(shù)據(jù)傳輸完畢,再把監(jiān)視的inputs列表中除去該連接。然后關(guān)閉連接。

整個網(wǎng)絡(luò)交互過程就是如此,當(dāng)然這里如果用戶在命令行中輸入中斷,inputs列表中監(jiān)視的sys.stdin也會讓select返回,最后也會執(zhí)行下面的代碼:

elif sock == sys.stdin:
  junk = sys.stdin.readlines()
  running = False

有人可能有疑問,在程序處理sock連接的是時候,假設(shè)又輸入了curl對服務(wù)器請求,將會怎么辦?此時毫無疑問,inputs里面的server套接字會變成可用。等現(xiàn)在的for循環(huán)處理完畢,此時select調(diào)用就會返回server。如果inputs里面還有上一個過程的conn連接,那么也會循環(huán)遍歷inputs的時候,再一次針對新的套接字accept到inputs列表進行監(jiān)視,然后繼續(xù)循環(huán)處理之前的conn連接。如此有條不紊的進行,直到for循環(huán)結(jié)束,進入主循環(huán)調(diào)用select。

任何時候,inputs監(jiān)聽的對象有數(shù)據(jù),下一次調(diào)用select的時候,就會繁返回readable,只要返回,就會對readable進行for循環(huán),直到for循環(huán)結(jié)束在進行下一次select。

主要注意,套接字建立連接是一次IO,連接的數(shù)據(jù)抵達也是一次IO。

3.select的不足
盡管select用起來挺爽,跨平臺的特性。但是select還是存在一些問題。
select需要遍歷監(jiān)視的文件描述符,并且這個描述符的數(shù)組還有最大的限制。隨著文件描述符數(shù)量的增長,用戶態(tài)和內(nèi)核的地址空間的復(fù)制所引發(fā)的開銷也會線性增長。即使監(jiān)視的文件描述符長時間不活躍了,select還是會線性掃描。

為了解決這些問題,操作系統(tǒng)又提供了poll方案,但是poll的模型和select大致相當(dāng),只是改變了一些限制。目前Linux最先進的方式是epoll模型。

許多高性能的軟件如nginx, nodejs都是基于epoll進行的異步。

相關(guān)文章

  • 如何高效使用Python字典的方法詳解

    如何高效使用Python字典的方法詳解

    Dictionary 是 Python 的內(nèi)置數(shù)據(jù)類型之一,它定義了鍵和值之間一對一的關(guān)系。下面這篇文章主要給大家介紹了關(guān)于如何高效使用Python字典的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-08-08
  • Python對小數(shù)進行除法運算的正確方法示例

    Python對小數(shù)進行除法運算的正確方法示例

    這篇文章主要介紹了Python對小數(shù)進行除法運算的正確方法示例,正確的方法是需要轉(zhuǎn)換成浮點數(shù),否則永遠不會得到正確結(jié)果,需要的朋友可以參考下
    2014-08-08
  • django 發(fā)送郵件和緩存的實現(xiàn)代碼

    django 發(fā)送郵件和緩存的實現(xiàn)代碼

    這篇文章主要介紹了django 發(fā)送郵件和緩存的實現(xiàn)代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-07-07
  • Python 利用高德地圖api實現(xiàn)經(jīng)緯度與地址的批量轉(zhuǎn)換

    Python 利用高德地圖api實現(xiàn)經(jīng)緯度與地址的批量轉(zhuǎn)換

    這篇文章主要介紹了Python 利用高德地圖api實現(xiàn)經(jīng)緯度與地址的批量轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • python如何統(tǒng)計序列中元素

    python如何統(tǒng)計序列中元素

    這篇文章主要為大家詳細介紹了python如何統(tǒng)計序列中的元素,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • Python 機器學(xué)習(xí)工具包SKlearn的安裝與使用

    Python 機器學(xué)習(xí)工具包SKlearn的安裝與使用

    Sklearn(全稱 SciKit-Learn),是基于 Python 語言的機器學(xué)習(xí)工具包。本文將簡單的介紹SKlearn安裝與使用,想要入坑機器學(xué)習(xí)的同學(xué)可以參考下
    2021-05-05
  • 使用Tensorboard工具查看Loss損失率

    使用Tensorboard工具查看Loss損失率

    今天小編就為大家分享一篇使用Tensorboard工具查看Loss損失率,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-02-02
  • 解決django中ModelForm多表單組合的問題

    解決django中ModelForm多表單組合的問題

    今天小編就為大家分享一篇解決django中ModelForm多表單組合的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-07-07
  • centos下更新Python版本的步驟

    centos下更新Python版本的步驟

    安裝完CentOS5.9(Final)后,執(zhí)行#Python與#python -V,看到版本號是2.4.3,很老了,而且之前寫的都是跑在python3.X上面的,3.X和2.X有很多不同,有興趣的朋友可以參考下這篇文章
    2013-02-02
  • python得到windows自啟動列表的方法

    python得到windows自啟動列表的方法

    今天小編就為大家分享一篇python得到windows自啟動列表的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-10-10

最新評論