Python之IO併發(多路複用)

IO 分類

IO分類:阻塞IO ,非阻塞IOIO多路複用,異步IO等。

    阻塞IO

         1.定義:在執行IO操作時如果執行條件不滿足則阻塞。阻塞IOIO的默認形態。

         2.效率:阻塞IO是效率很低的一種IO。但是由於邏輯簡單所以是默認IO行爲。

         3.阻塞情況

                    因爲某種執行條件沒有滿足造成的函數阻塞 accept input recv

                    處理IO的時間較長產生的阻塞狀態 網絡傳輸,大文件讀寫

    非阻塞IO

         1. 定義 :通過修改IO屬性行爲,使原本阻塞的IO變爲非阻塞的狀態。

         設置套接字爲非阻塞IO

sockfd.setblocking(bool)

功能:設置套接字爲非阻塞IO

參數:默認爲True,表示套接字IO阻塞;設置爲False則套接字IO變爲非阻塞

超時檢測 :設置一個最長阻塞時間,超過該時間後則不再阻塞等待。

sockfd.settimeout(sec)

功能:設置套接字的超時時間

參數:設置的時間

示例:

"""
    非阻塞io演示
"""
from socket import *
from time import ctime, sleep

f = open('log.txt', 'a')  # 日誌文件

s = socket()
s.bind(('0.0.0.0', 8888))
s.listen(5)

# 設置套接字非阻塞
# s.setblocking(False)

# 設置超時時間
s.settimeout(3)

while True:
    try:
        # 設置非阻塞時會直接進入except
        # 設置阻塞,超時時間3秒,等待3秒若無客戶端連接,進入except
        c, addr = s.accept()
        print("Connect from", addr)
    except (BlockingIOError, timeout) as e:
        print(e)
        sleep(2)
        f.write(ctime() + ':' + str(e) + '\n')
        f.flush()
    else:
        data = c.recv(1024)
        print(data)

結果:

 

IO多路複用

    1. 定義

        同時監控多個IO事件,當哪個IO事件準備就緒就執行哪個IO事件。以此形成可以同時處理多個IO的行爲,避免一個IO阻塞造成

其他IO均無法執行,提高了IO執行效率。

    2. 具體方案

【1】select方法 : windows linux unix

【2】poll方法: linux unix

【3】epoll方法: linux

    【1】select 方法

rs, ws, xs=select(rlist, wlist, xlist[, timeout])

功能: 監控IO事件,阻塞等待IO發生

參數:rlist 列表 存放關注的等待發生的IO事件

           wlist 列表 存放關注的要主動處理的IO事件

           xlist 列表 存放關注的出現異常要處理的IO

           timeout 超時時間

返回值: rs 列表 rlist中準備就緒的IO

               ws 列表 wlist中準備就緒的IO

               xs 列表 xlist中準備就緒的IO

    select 實現tcp服務

1】 將關注的IO放入對應的監控類別列表

2】通過select函數進行監控

3】遍歷select返回值列表,確定就緒IO事件

4】處理髮生的IO事件

    注意

        wlist中如果存在IO事件,則select立即返回給ws

        處理IO過程中不要出現死循環佔有服務端的情況

        IO多路複用消耗資源較少,效率較高

示例:

"""
select tcp 服務
1.將關注的IO放入對應的監控類別列表
2.通過select函數進行監控
3.遍歷select返回值列表,確定就緒IO事件
4.處理髮生的IO事件
"""

from socket import *
from select import select

# 創建監聽套接字作爲關注IO
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 8888))
s.listen(3)

# 設置關注列表
rlist = [s]  # 等待客戶端連接
wlist = []
xlist = []

# 監控IO發生
while True:
    rs, ws, xs = select(rlist, wlist, xlist)
    # 遍歷等待處理類型的IO(等待客戶端連接)
    for r in rs:
        if r is s:
            # 有客戶端連接
            c, addr = r.accept()
            print("Connect from", addr)
            rlist.append(c)  # 客戶端連接對象加入監控
        else:
            # 如果是客戶端連接發來信息
            data = r.recv(1024).decode()
            if not data:  # 斷開連接
                rlist.remove(r)  # 取消對它關注
                r.close()
                continue
            print(data)
            # 將立即發送回覆消息的IO添加到主動處理IO檢測列表裏會立即觸發執行
            wlist.append(r)

    for w in ws:
        w.send(b'OK')
        wlist.remove(w)  # 從主動執行監控IO列表中移除

    【2】poll方法

p = select.poll()

功能 : 創建poll對象

返回值: poll對象

p.register(fd,event)

功能: 註冊關注的IO事件

參數:fd 要關注的IO

          event 要關注的IO事件類型

          常用類型:POLLIN IO事件(rlist

                            POLLOUT IO事件 (wlist)

                            POLLERR 異常IO xlist

                            POLLHUP 斷開連接

比如:p.register(sockfd,POLLIN|POLLERR)

p.unregister(fd)

功能:取消對IO的關注

參數:IO對象或者IO對象的fileno

events = p.poll()

功能: 阻塞等待監控的IO事件發生

返回值: 返回發生的IO

                events格式 [(fileno,event),()....]

                每個元組爲一個就緒IO,元組第一項是該IOfileno,第二項爲該IO就緒的事件類型

    poll_server 步驟

1】 創建套接字

2】 將套接字register

3】 創建查找字典,並維護

4】 循環監控IO發生

5】 處理髮生的IO

示例:

"""
    方法實現IO多路複用
【1】 創建套接字
【2】 將套接字register
【3】 創建查找字典,並維護
【4】 循環監控IO發生
【5】 處理髮生的IO
"""

from socket import *
from select import *

# 創建套接字作爲關注IO
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 8888))
s.listen(3)

# 創建poll對象
p = poll()

# 建立查找字典,通過IO對象的fileno找到對象
# 字典內容與關注IO保持一直{fileno:io_obj}
fdmap = {s.fileno(): s}

# 關注s
p.register(s, POLLIN | POLLERR)

# 循環監控IO的發生
while True:
    events = p.poll()
    print(events)
    for fd, event in events:
        if fd == s.fileno():
            c, addr = fdmap[fd].accept()
            print("Connect from", addr)
            # 添加新的關注對象,同時維護字典
            p.register(c, POLLIN)
            fdmap[c.fileno()] = c
        elif event & POLLIN:
            data = fdmap[fd].recv(1024).decode()
            if not data:
                # 客戶端退出
                p.unregister(fd)  # 取消關注
                fdmap[fd].close()
                del fdmap[fd]
                continue
            print(data)
            p.register(fd, POLLOUT)
        elif event & POLLOUT:
            fdmap[fd].send(b'OK')
            p.register(fd, POLLIN)

    【3】epoll方法

              1. 使用方法 :

                                      基本與poll相同

                                      生成對象改爲 epoll()

                                      將所有事件類型改爲EPOLL類型

              2. epoll特點:

                                   epoll 效率比select poll要高

                                   epoll 監控IO數量比select要多

                                   epoll 的觸發方式比poll要多 (EPOLLET邊緣觸發)

示例:

"""
    方法實現IO多路服用
【1】 創建套接字
【2】 將套接字register
【3】 創建查找字典,並維護
【4】 循環監控IO發生
【5】 處理髮生的IO
"""

from socket import *
from select import *

# 創建套接字作爲關注IO
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('127.0.0.1', 8888))
s.listen(3)

# 創建epoll對象
ep = epoll()

# 建立查找字典,通過IO對象的fileno找到對象
# 字典內容與關注IO保持一直{fileno:io_obj}
fdmap = {s.fileno(): s}

# 關注s
ep.register(s, EPOLLIN | EPOLLERR)

# 循環監控IO的發生
while True:
    events = ep.poll()
    print("你有新的IO需要處理哦:", events)
    for fd, event in events:
        if fd == s.fileno():
            c, addr = fdmap[fd].accept()
            print("Connect from", addr)
            # 添加新的關注對象,同時維護字典
            ep.register(c, EPOLLIN | EPOLLET)  # 邊緣觸發
            fdmap[c.fileno()] = c
        elif event & EPOLLIN:
            data = fdmap[fd].recv(1024).decode()
            if not data:
                # 客戶端退出
                ep.unregister(fd)  # 取消關注
                fdmap[fd].close()
                del fdmap[fd]
                continue
            print(data)
            ep.unregister(fd)
            ep.register(fd, POLLOUT)
        elif event & POLLOUT:
            fdmap[fd].send(b'OK')
            ep.unregister(fd)
            ep.register(fd, POLLIN)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章