Python(二十八)I/O多路復(fù)用

看完本篇文章,你將會了解到什么是I/O多路復(fù)用檬某,以及如何通過代碼來防止服務(wù)器上的阻塞現(xiàn)象。

1. 基本IO模型

1.1. 數(shù)據(jù)流概念

數(shù)據(jù)流(data stream)是一組有序耕拷,有起點和終點的字節(jié)的數(shù)據(jù)序列锚国,是只能被讀取一次或少數(shù)幾次的點的有序序列蒙袍,其包括輸入流和輸出流。
數(shù)據(jù)流分為輸入流(InputStream)和輸出流(OutputStream)兩類嫩挤。輸入流只能讀不能寫害幅,而輸出流只能寫不能讀,通常程序中使用輸入流讀出數(shù)據(jù)岂昭,輸出流寫入數(shù)據(jù)以现,就好像數(shù)據(jù)流入到程序并從程序中流出,采用數(shù)據(jù)流使程序的輸入輸出操作獨立于相關(guān)設(shè)備约啊。

輸入流可從鍵盤或文件中獲得數(shù)據(jù)邑遏,輸出流可向顯示器、打印機(jī)或文件中傳輸數(shù)據(jù)棍苹。

1.2. IO解釋與IO交互

IO即 input and output,在unix世界里无宿,一切皆文件。而文件是什么呢枢里?文件就是一串二進(jìn)制流,不管socket蹂午、還是FIFO栏豺、管道或終端,一切都是文件豆胸,一切都是流奥洼。在信息交換的過程中,對這些流進(jìn)行數(shù)據(jù)的收發(fā)操作晚胡,簡稱為I/O操作(input and output)灵奖。
往流中讀出數(shù)據(jù),系統(tǒng)調(diào)用read估盘;寫入數(shù)據(jù)瓷患,系統(tǒng)調(diào)用write。但是計算機(jī)里有這么多的流遣妥,怎么知道要操作哪個流呢擅编?做到這個的就是文件描述符,即通常所說的fd箫踩。一個fd就是一個整數(shù)爱态,所以對這個整數(shù)的操作,就是對這個文件(流)的操作境钟。創(chuàng)建一個socket锦担,通過系統(tǒng)調(diào)用會返回一個文件描述符,那么剩下對socket的操作就會轉(zhuǎn)化為對這個描述符的操作慨削。

用戶空間中存放的是用戶程序的代碼和數(shù)據(jù)洞渔。內(nèi)核空間用來存放的是內(nèi)核代碼和數(shù)據(jù)鱼的。

1.3. 阻塞IO

很多時候數(shù)據(jù)在一開始還沒有到達(dá),這個時候內(nèi)核就要等待足夠的數(shù)據(jù)到來痘煤。而在用戶進(jìn)程這邊凑阶,整個進(jìn)程會被阻塞,當(dāng)內(nèi)核一直等到數(shù)據(jù)準(zhǔn)備好了衷快,它就會將數(shù)據(jù)從內(nèi)核中拷貝到用戶內(nèi)存宙橱,然后返回結(jié)果,用戶進(jìn)程才解除阻塞的狀態(tài)蘸拔,重新運行起來师郑。


阻塞IO

2. 非阻塞IO模型與非阻塞套接字

2.1. 非阻塞IO模型

從用戶進(jìn)程角度講,它發(fā)起一個read操作后调窍,并不需要等待宝冕,而是馬上就得到了一個結(jié)果,當(dāng)用戶進(jìn)程判斷結(jié)果是一個error時,它就知道數(shù)據(jù)還沒有準(zhǔn)備好邓萨。
于是它可以再次發(fā)送read操作地梨,一旦內(nèi)核中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶進(jìn)程的系統(tǒng)調(diào)用缔恳,那么它馬上就將數(shù)據(jù)拷貝到了用戶內(nèi)存宝剖,然后返回,非阻塞的接口相比于阻塞型接口的顯著差異在于歉甚,在被調(diào)用之后立即返回万细。


2.2. 非阻塞IO

利用異常處理來處理非阻塞IO產(chǎn)生的異常
服務(wù)器代碼:

import socket
server = socket.socket()  # 創(chuàng)建一個socket對象,命名為服務(wù)器
server.setblocking(False)       #設(shè)置非阻塞套接字
server.bind(('127.0.0.1',8989))     # 綁定端口纸泄,注意這里填入的是元組
server.listen(10)       # 設(shè)置最大監(jiān)聽數(shù)赖钞,最大連接量
while True:
    try:
        result = server.accept()        # 與客戶端創(chuàng)建對等套接字
        conn,address = result       # 元組拆包
        conn.setblocking(False)     # 設(shè)置非阻塞io
    except BlockingIOError:
        pass
    except Exception as e:
        print(f'發(fā)生了未知異常{e}')

"""若端口被占用,可以打開虛擬機(jī)查看進(jìn)程并結(jié)束它"""
# ps -aux | grep python     # 查看進(jìn)程
# kill -9 2821        # 這里的2821指的是服務(wù)器運行進(jìn)程id聘裁,照具體情況而定

客戶端代碼

import socket
client = socket.socket()        # 創(chuàng)建一個socket對象雪营,命名為客戶端
client.connect(('127.0.0.1',8989))# 連接服務(wù)器端口,注意這里填入的是元組

for i in range(10):
    client.send(b'hello')       # 發(fā)送hello給服務(wù)器
    print(client.recv(1024))

2.3. 并發(fā)與并行

2.3.1. 并發(fā)

指一個時間段中有幾個程序都處于已啟動運行到運行完畢之間咧虎,且這幾個程序都是在同一個處理機(jī)上運行卓缰,但任一個時刻點上只有一個程序在處理機(jī)上運行。

2.3.2. 并行

指一個時間段中有幾個程序都處于已啟動運行到運行完畢之間砰诵,且這幾個程序都是在同個處理機(jī)上運行征唬,并任一個時刻點上有多個程序在處理機(jī)上運行。

2.4. 實現(xiàn)并發(fā)

服務(wù)器代碼

import socket
server = socket.socket()  # 創(chuàng)建一個socket對象茁彭,命名為服務(wù)器
server.setblocking(False)       #設(shè)置非阻塞套接字
server.bind(('127.0.0.1',8989))     # 綁定端口总寒,注意這里填入的是元組
server.listen(10)       # 設(shè)置最大監(jiān)聽數(shù),最大連接量

all_conn = []       # 創(chuàng)建一個空列表理肺,等待套接字添加進(jìn)來
while True:
    try:
        result = server.accept()        # 與客戶端創(chuàng)建對等套接字
        conn,address = result       # 元組拆包
        conn.setblocking(False)     # 設(shè)置非阻塞io
        all_conn.append(conn)
    except BlockingIOError:
        pass
    except Exception as e:
        print(f'發(fā)生了未知異常{e}')

    for conn in all_conn:       # 依次處理套接字中的數(shù)據(jù)
        try:
            recv_data = conn.recv(1024)
            if recv_data:
                print(recv_data)
                conn.send(recv_data)
            else:
                conn.close()
                all_conn.remove(conn)
        except BlockingIOError:
            pass
        except Exception as e:
            print(f'發(fā)生了未知異常{e}')

"""若端口被占用摄闸,可以打開虛擬機(jī)查看進(jìn)程并結(jié)束它"""
# ps -aux | grep python     # 查看進(jìn)程
# kill -9 2821        # 這里的2821指的是服務(wù)器運行進(jìn)程id善镰,照具體情況而定

客戶端代碼

import socket
client = socket.socket()        # 創(chuàng)建一個socket對象,命名為客戶端
client.connect(('127.0.0.1',8989))# 連接服務(wù)器端口年枕,注意這里填入的是元組

for i in range(10):
    client.send(b'hello')       # 發(fā)送hello給服務(wù)器
    print(client.recv(1024))

3. IO多路復(fù)用

3.1. 概念介紹

在之前的非阻塞IO模型中炫欺,通過不斷的詢問來查看是否有數(shù)據(jù),會造成資源的浪費熏兄。將查看的過程由主動的查詢變成交給復(fù)用器完成品洛,這樣能夠更加節(jié)省系統(tǒng)資源,并且性能更好摩桶。


3.2. epoll

3.2.1. 非阻塞套接字與多路復(fù)用

非阻塞套接寧需要自身遍歷每個對等連接套接字桥状,并且每次都進(jìn)行IO操作。復(fù)用器不需要進(jìn)行大量的IO操作硝清,因為復(fù)用器會告訴哪個對等連接套接字有數(shù)據(jù)傳輸過來辅斟,然后再去處理即可。

3.2.2. epoll概念

epoll是一個惰性事件回調(diào)芦拿,即回調(diào)過程是用戶自己去調(diào)用的士飒,操作系統(tǒng)只起到通知的作用。epoll是Linux上最好的IO多路復(fù)用器防嗡,但是也只有Linux有变汪,在其他的地方都沒有。

3.2.3. 代碼實現(xiàn)

服務(wù)器代碼

import time
import socket
import selectors        # 導(dǎo)入IO多路復(fù)用選擇器

epoll_sel = selectors.EpollSelector()       # 實例化一個復(fù)用器對象
default_sel = selectors.DefaultSelector()     # 使用默認(rèn)選擇器蚁趁,根據(jù)系統(tǒng)自動選擇

server = socket.socket()  # 創(chuàng)建一個socket對象,命名為服務(wù)器
server.bind(('127.0.0.1',8989))     # 綁定端口实胸,注意這里填入的是元組
server.listen(10)       # 設(shè)置最大監(jiān)聽數(shù)他嫡,最大連接量

def f_recv(conn):
    recv_data = conn.recv(1024)
    if recv_data:
        print(recv_data)
        conn.send(recv_data)
    else:
        conn.close()

def f_accept(server):
    conn,address = server.accept()
    epoll_sel.register(conn,selectors.EVENT_READ,f_recv)

# 參數(shù)一:可能會發(fā)生事件的對象;參數(shù)二:檢查是否發(fā)生了事件庐完;參數(shù)三:發(fā)生事件之后需要執(zhí)行的功能钢属。
epoll_sel.register(server,selectors.EVENT_READ,f_accept)

while True:
    events=epoll_sel.select()   # 是否有事件發(fā)生
    time.sleep(1)
    print(events)
    for key,i in events:        # i用來接收1
        obj = key.fileobj       # 注冊進(jìn)來的發(fā)生事件對象,采用實例對象.屬性的方式進(jìn)行調(diào)用
        func = key.data     # 要執(zhí)行的方法
        func(obj)

客戶端代碼

import socket
client = socket.socket()        # 創(chuàng)建一個socket對象门躯,命名為客戶端
client.connect(('127.0.0.1',8989))# 連接服務(wù)器端口淆党,注意這里填入的是元組

for i in range(10):
    client.send(b'hello')       # 發(fā)送hello給服務(wù)器
    print(client.recv(1024))

文章到這里就結(jié)束了!希望大家能多多支持Python(系列)讶凉!六個月帶大家學(xué)會Python染乌,私聊我,可以問關(guān)于本文章的問題懂讯!以后每天都會發(fā)布新的文章荷憋,喜歡的點點關(guān)注!一個陪伴你學(xué)習(xí)Python的新青年褐望!不管多忙都會更新下去勒庄,一起加油串前!

Editor:Lonelyroots

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者实蔽。
  • 序言:七十年代末荡碾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子局装,更是在濱河造成了極大的恐慌坛吁,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贼邓,死亡現(xiàn)場離奇詭異阶冈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)塑径,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進(jìn)店門女坑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人统舀,你說我怎么就攤上這事匆骗。” “怎么了誉简?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵碉就,是天一觀的道長。 經(jīng)常有香客問我闷串,道長瓮钥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任烹吵,我火速辦了婚禮碉熄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肋拔。我一直安慰自己锈津,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布凉蜂。 她就那樣靜靜地躺著琼梆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窿吩。 梳的紋絲不亂的頭發(fā)上茎杂,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天,我揣著相機(jī)與錄音爆存,去河邊找鬼蛉顽。 笑死,一個胖子當(dāng)著我的面吹牛先较,可吹牛的內(nèi)容都是我干的携冤。 我是一名探鬼主播悼粮,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼曾棕!你這毒婦竟也來了扣猫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤翘地,失蹤者是張志新(化名)和其女友劉穎申尤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衙耕,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡昧穿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了橙喘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片时鸵。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖厅瞎,靈堂內(nèi)的尸體忽然破棺而出饰潜,到底是詐尸還是另有隱情,我是刑警寧澤和簸,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布彭雾,位于F島的核電站,受9級特大地震影響锁保,放射性物質(zhì)發(fā)生泄漏薯酝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一爽柒、第九天 我趴在偏房一處隱蔽的房頂上張望蜜托。 院中可真熱鬧,春花似錦霉赡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至重挑,卻和暖如春嗓化,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谬哀。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工刺覆, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人史煎。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓谦屑,卻偏偏與公主長得像驳糯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子氢橙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,922評論 2 361

推薦閱讀更多精彩內(nèi)容