1. 程式人生 > >說說基於網絡的五種IO模型

說說基於網絡的五種IO模型

處理 所有 cpe 客戶 分享 for imp stream targe

  

# django不是一個異步框架
# tornado是異步的web框架
    # 處理每秒大量的請求

# 個人理解的IO:就是應用層與內核驅動層的交互,這個過程無論從應用層到內核中,還是驅動層等待硬件層的數據,都是需要時間的,這個過程是IO操作過程

# 五種IO Model
    # blocking IO  阻塞IO
    # nonblocking 非阻塞IO
    # IO multiplexing IO多路復用
    # signal driven IO 信號驅動IO
    # asynchronous IO 異步IO
        # 在python中沒有提供異步IO的機制,沒有操作系統將數據直接給應用層的獲取到數據的接口. 但是由很多python的異步框架
# 這種異步IO機制其實是很好的
# IO發生時涉及的對象和步驟,對於一個network IO,我們以read舉例,他會涉及到兩個系統對象,一個是調用這個IO的process(or thread),另一個就是系統內核。當一個read操作時,該操作會經歷兩個階段
    # 1.等待數據準備
    # 2.將數據從內核拷貝到應用層

# 同步    提交一個任務之後要等待這個任務執行完畢才能繼續執行其他的
# 異步    只管提交任務,不用等待該任務執行完畢就可以繼續做其他事情
# 阻塞    運行狀態 -> 阻塞狀態 -> 就緒狀態 -> 運行狀態、一進程或線程阻塞則會進入阻塞狀態休眠
# 非阻塞

  1.非阻塞IO

# 非阻塞IO
# import socket
# sk = socket.socket()
# sk.setblocking(False)   # 設置非阻塞
# sk.bind((‘127.0.0.1‘, 8080))
# sk.listen()
#
# try:
#     conn, addr = sk.accept()  # 因為將套接字設置為了非阻塞,所以這裏會報錯,因為accept不允許為非阻塞性的,所以下面捕捉異常,有異常則直接pass
#     print(‘有客戶端連接上來 ‘)
# except BlockingIOError:
#     pass

  2.阻塞IO

  3.IO多路復用

技術分享圖片

    # 在windows、linux上,有一個select專門提供IO多路復用的。
    # poll機制    # linux上有
    # epoll機制   # linux上有
    # poll機制和epoll機制使用方法和select一樣
        # poll可以監聽的對象比select可以監聽的多。如果select能監聽500,則poll能監聽1000個類似這樣
        # poll和select都是操作系統去輪詢機制的監聽被監聽的項,看是否有讀操作等,隨著監聽列表增多,會導致效率變差
        # epoll機制
            # 給每一個被監聽的對象都綁定了一個回調函數,當被監聽的對象有監聽事件後,會觸發此監聽對象綁定的回調函數這種機制比select和poll的輪詢效率要高,高並發非常有用
    # import selectors 這個模塊會幫助你選擇當前操作系統上最優的IO多路復用

  3.1 IO多路復用中的select

  服務端

import select   # 內置的select模塊,用於IO多路復用
import socket
# select.select(rlist, wlist, xlist, timeout=None)
    # 參數是3個列表,一個監聽超時時間
    # rlist參數,表示監聽讀,直到監聽到讀或超時返回
    # wlist參數,表示監聽寫,直到監聽到寫或超時返回
    # xlist參數,表示監聽條件
    # timeout參數,監聽超時時間
    # 返回值有三個
sk = socket.socket()
sk.bind((127.0.0.1, 8080))
sk.setblocking(False)   # 設置為非阻塞
sk.listen()

read_lst = [sk] # 創建一個列表,想要監聽誰,則將哪個對象放進來,這裏開始先監聽socket對象,當有人向這個socket發起連接的時候,select則會監聽到返回一個socket
while 1:
    # 調用select後操作系統會幫你監聽三個監聽列表,這裏監聽的是監聽讀列表
    r_lst, w_lst, x_lst = select.select(read_lst, [], [])   # 當監聽到後,返回一個元組,元組中有三個元素,分別是rlist,wlist,xlist。這裏rlist中最開始監聽sk,因此當有客戶端連接上來後,會監聽有要被讀的事件,這裏會返回得到一個r_lst,r_lst中有一個sk對象
    #print(r_lst)
    for i in r_lst:
        if i is sk: # 判斷監聽到的對象是否是sk對象
            conn, addr = i.accept() # 此時直接sk.accpet()就會得到客戶端連接和地址
            read_lst.append(conn)   # 將客戶端連接符加入到監聽列表中
        else:   # 如果監聽到的不是sk對象, 這裏的第二可能是監聽到了客戶端連接符有要被讀的事件
            msg = i.recv(1024)
            if msg == b‘‘:  # 當客戶端連接關閉時,會接收到空的數據,
                i.close()   # 因為客戶端連接主動關閉,這裏也要關閉下這個客戶端連接
                read_lst.remove(i)  # 同時在監聽列表中去除這個客戶端連接符,不再去監聽它
                continue
            print(msg)

  3.2 linux上更好的IO多路復用epoll、selectors選擇當前系統最優的IO多路復用機制

  服務端

# linux上的selectors IO多路復用機制,IO多路復用機制,默認選擇系統最優,linux上肯定選擇epoll
import selectors
from socket import *

def read(conn, mask):
    ‘‘‘
    將來要綁定的回調函數
    :param conn:
    :param mask:
    :return:
    ‘‘‘
    try:
        data = conn.recv(1024)
        if not data:    # 如果監聽客戶端的數據是空數據,則表示客戶端連接關閉了
            print(closeing, conn)
            sel.unregister(conn)    # 在監聽列表中去除這個客戶端連接的監聽
            conn.close()    # 同時服務端也關閉這個客戶端連接描述符
            return
        conn.send(data.upper() + bSB)
    except Exception:   # 該客戶端連接描述符監聽到異常事件,則直接關閉客戶端連接
        print(closing, conn)
        sel.unregister(conn)  # 在監聽列表中去除這個客戶端連接的監聽
        conn.close()  # 同時服務端也關閉這個客戶端連接描述符


def accept(server_fileobj, mask):
    ‘‘‘

    :param server_fileobj:  接收到的socket
    :param mask:
    :return:
    ‘‘‘
    conn, addr = server_fileobj.accpet()    # 得到客戶端連接描述符
    sel.register(conn, selectors.EVENT_READ, read)  # 將conn客戶端連接符註冊到監聽列表中,監聽的是其讀時間,綁定的回調函數是read


if __name__ == __main:
    sk = socket()
    sk.setblocking(SOL_SOCKET, SO_REUSEPORT, 1)
    sk.bind((127.0.0.1, 8080))
    sk.listen(5)
    sk.setblocking(False)
    sel = selectors.DefaultSelector()   # 獲取到當前操作系統最優的IO多路復用機制
    sel.register(sk, selectors.EVENT_READ, accept)  # 將socket對象註冊到監聽列表中,監聽其讀事件,當有讀事件時綁定的回調函數accpet會被執行

    # server_fileobj = socket(AF_INET, SOCK_STREAM)
    # server_fileobj.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
    # server_fileobj.bind((‘127.0.0.1‘, 8080))
    # server_fileobj.listen(5)
    # server_fileobj.setblocking(False)
    # sel.register(server_fileobj, selectors.EVENT_READ, accept)

    while True:
        events = sel.select()   # 檢測到所有的fileobj(監聽列表中的所有對象,都是文件描述符),是否有完成wait data階段。當監聽到有事件時返回
        for sel_obj, mask in events:
            callback = sel_obj.data     # 第一次是callback = accpet  通過sel_obj.data就能拿到剛剛這個被監聽對象的回調函數
            callback(sel_obj.fileobj, mask) # 第一次是ccpet(server_fileobj, 1)   直接調用回調函數

  客戶端

import socket
from threading import Thread

def func():
    sk = socket.socket()
    sk.connect((127.0.0.1, 8080))
    sk.send(bhello)
    sk.close()

if __name__ == __main__:
    for i in range(20):
        Thread(target=func).start()

  4.信號驅動IO

  5.異步IO

技術分享圖片

 6.五種IO模型的比較,個人覺得肯定還是異步IO好

技術分享圖片

說說基於網絡的五種IO模型