Linux下I/O多路復(fù)用select, poll, epoll 三種模型的Python使用

Linux下I/O多路復(fù)用select, poll, epoll 三種模型

select, poll, epoll本質(zhì)上都是同步的I/O龄糊,因?yàn)樗鼈兌际窃谧x寫事件就緒后自己負(fù)責(zé)進(jìn)行讀寫,這個讀寫的過程是阻塞的赛蔫。

select, poll, epoll 都是一種 I/O 復(fù)用的機(jī)制裤唠。它們都是通過一種機(jī)制(由系統(tǒng)提供的)來監(jiān)視多個描述符挤牛,一旦某個描述符就緒了,就能通知程序進(jìn)行相應(yīng)的讀寫操作种蘸。

select

select的原理

select 是通過系統(tǒng)調(diào)用來監(jiān)視著一個由多個文件描述符(file descriptor)組成的數(shù)組墓赴,當(dāng)select()返回后,數(shù)組中就緒的文件描述符會被內(nèi)核修改標(biāo)記位(其實(shí)就是一個整數(shù))劈彪,使得進(jìn)程可以獲得這些文件描述符從而進(jìn)行后續(xù)的讀寫操作竣蹦。select飾通過遍歷來監(jiān)視整個數(shù)組的,而且每次遍歷都是線性的沧奴。

select的缺點(diǎn)

  1. 每次調(diào)用select痘括,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷在fd很多的時候會很大
  2. 單個進(jìn)程能夠監(jiān)視的fd數(shù)量存在最大限制,在linux上默認(rèn)為1024(可以通過修改宏定義或者重新編譯內(nèi)核的方式提升這個限制)
  3. 并且由于select的fd是放在數(shù)組中纲菌,并且每次都要線性遍歷整個數(shù)組挠日,當(dāng)fd很多的時候,開銷也很大

在Python中調(diào)用select

Python中翰舌,select嚣潜,poll,epoll和unix的kqueue()都在模塊select中椅贱。

調(diào)用select的函數(shù)為select.select(rlist, wlist, xlist[, timeout])懂算,前三個參數(shù)都分別是三個數(shù)組,數(shù)組中的對象均為waitable object:均是整數(shù)的文件描述符(file descriptor)或者一個擁有返回文件描述符方法fileno()的對象庇麦;

  • rlist: 等待讀就緒的list
  • wlist: 等待寫就緒的list
  • xlist: 等待“異臣萍迹”的list

這三個list可以是一個空的list,但是接收3個空的list是依賴于系統(tǒng)的(在Linux上是可以接受的山橄,但是在window上是不可以的)垮媒。

timeout參數(shù)是接受一個 float 的數(shù)字,單位是航棱。當(dāng)缺省timeout時睡雇,select會一直阻塞之道至少有一個文件描述符(fd)準(zhǔn)備就緒。如果timeout設(shè)為0時饮醇,則select不會阻塞它抱。

函數(shù)的返回值是返回三個準(zhǔn)備就緒的list: 對應(yīng)者rlist, wlist, xlist這三個list的子集。如果timeout朴艰,會返回3個空的list抗愁。

在list中可以接受Ptython的的file對象(比如sys.stdin,或者會被open()os.open()返回的object)呵晚,socket object將會返回socket.socket()蜘腌。也可以自定義類,只要有一個合適的fileno()的方法(需要真實(shí)返回一個文件描述符饵隙,而不是一個隨機(jī)的整數(shù))撮珠。

Python的簡單示例

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

import select, socket

response = b"hello world"

#創(chuàng)建一個server socket
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('localhost', 8080))
serversocket.listen(1)
serversocket.setblocking(0)

inputs = [serversocket, ]

while True:
    rlist, wlist, xlist = select.select(inputs, [], [])
    for sock in rlist:
        # server socket讀就緒
        if sock == serversocket:
            con, addr = serversocket.accept()
            #將這個connection添加到讀就緒中
            inputs.append(con)
        else:
            data = sock.recv(1024)
            if data:
                sock.send(response)
                #從讀就緒的list中刪除
                inputs.remove(sock)
                sock.close()

poll

poll的原理

poll本質(zhì)上和select沒有區(qū)別,只是沒有了最大連接數(shù)(linux上默認(rèn)1024個)的限制金矛,原因是它基于鏈表存儲的芯急。

poll的缺點(diǎn)

poll除了沒有了最大連接數(shù)的缺點(diǎn),其他都和select一樣

在Python中調(diào)用poll

  • select.poll()驶俊,返回一個poll的對象娶耍,支持注冊和注銷文件描述符。

  • poll.register(fd[, eventmask])注冊一個文件描述符饼酿,注冊后榕酒,可以通過poll()方法來檢查是否有對應(yīng)的I/O事件發(fā)生胚膊。fd可以是i 個整數(shù),或者有返回整數(shù)的fileno()方法對象想鹰。如果File對象實(shí)現(xiàn)了fileno()紊婉,也可以當(dāng)作參數(shù)使用。

  • eventmask是一個你想去檢查的事件類型辑舷,它可以是常量POLLIN, POLLPRIPOLLOUT的組合喻犁。如果缺省,默認(rèn)會去檢查所有的3種事件類型何缓。

事件常量 意義
POLLIN 有數(shù)據(jù)讀取
POLLPRT 有數(shù)據(jù)緊急讀取
POLLOUT 準(zhǔn)備輸出:輸出不會阻塞
POLLERR 某些錯誤情況出現(xiàn)
POLLHUP 掛起
POLLNVAL 無效請求:描述無法打開
  • poll.modify(fd, eventmask) 修改一個已經(jīng)存在的fd肢础,和poll.register(fd, eventmask)有相同的作用。如果去嘗試修改一個未經(jīng)注冊的fd碌廓,會引起一個errnoENOENTIOError乔妈。
  • poll.unregister(fd)從poll對象中注銷一個fd。嘗試去注銷一個未經(jīng)注冊的fd氓皱,會引起KeyError
  • poll.poll([timeout])去檢測已經(jīng)注冊了的文件描述符勃刨。會返回一個可能為空的list波材,list中包含著(fd, event)這樣的二元組。 fd是文件描述符身隐, event是文件描述符對應(yīng)的事件廷区。如果返回的是一個空的list,則說明超時了且沒有文件描述符有事件發(fā)生贾铝。timeout的單位是milliseconds隙轻,如果設(shè)置了timeout,系統(tǒng)將會等待對應(yīng)的時間垢揩。如果timeout缺省或者是None玖绿,這個方法將會阻塞直到對應(yīng)的poll對象有一個事件發(fā)生。

Python簡單示例

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

import select, socket

response = b"hello world"

serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('192.168.199.197', 8080))
serversocket.listen(1)
serversocket.setblocking(0)

#
poll = select.poll()
poll.register(serversocket.fileno(), select.POLLIN)

connections = {}
while True:
    for fd, event in poll.poll():
        if event == select.POLLIN:
            if fd == serversocket.fileno():
                con, addr = serversocket.accept()
                poll.register(con.fileno(), select.POLLIN)
                connections[con.fileno()] = con
            else:
                con = connections[fd]
                data = con.recv(1024)
                if data:
                    poll.modify(con.fileno(), select.POLLOUT)
        elif event == select.POLLOUT:
            con = connections[fd]
            con.send(response)
            poll.unregister(con.fileno())
            con.close()

epoll

epoll的原理及改進(jìn)

在linux2.6(準(zhǔn)確來說是2.5.44)由內(nèi)核直接支持的方法叁巨。epoll解決了select和poll的缺點(diǎn)斑匪。

  • 對于第一個缺點(diǎn),epoll的解決方法是每次注冊新的事件到epoll中锋勺,會把所有的fd拷貝進(jìn)內(nèi)核蚀瘸,而不是在等待的時候重復(fù)拷貝,保證了每個fd在整個過程中只會拷貝1次庶橱。
  • 對于第二個缺點(diǎn)贮勃,epoll沒有這個限制,它所支持的fd上限是最大可以打開文件的數(shù)目苏章,具體數(shù)目可以cat /proc/sys/fs/file-max查看寂嘉,一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系比較大奏瞬。
  • 對于第三個缺點(diǎn),epoll的解決方法不像select和poll每次對所有fd進(jìn)行遍歷輪詢所有fd集合垫释,而是在注冊新的事件時丝格,為每個fd指定一個回調(diào)函數(shù),當(dāng)設(shè)備就緒的時候棵譬,調(diào)用這個回調(diào)函數(shù)显蝌,這個回調(diào)函數(shù)就會把就緒的fd加入一個就緒表中。(所以epoll實(shí)際只需要遍歷就緒表)订咸。

epoll同時支持水平觸發(fā)和邊緣觸發(fā):

  • 水平觸發(fā)(level-triggered):只要滿足條件曼尊,就觸發(fā)一個事件(只要有數(shù)據(jù)沒有被獲取,內(nèi)核就不斷通知你)脏嚷。e.g:在水平觸發(fā)模式下骆撇,重復(fù)調(diào)用epoll.poll()會重復(fù)通知關(guān)注的event,直到與該event有關(guān)的所有數(shù)據(jù)都已被處理父叙。(select, poll是水平觸發(fā), epoll默認(rèn)水平觸發(fā))
  • 邊緣觸發(fā)(edge-triggered):每當(dāng)狀態(tài)變化時神郊,觸發(fā)一個事件。e.g:在邊沿觸發(fā)模式中趾唱,epoll.poll()在讀或者寫event在socket上面發(fā)生后涌乳,將只會返回一次event。調(diào)用epoll.poll()的程序必須處理所有和這個event相關(guān)的數(shù)據(jù)甜癞,隨后的epoll.poll()調(diào)用不會再有這個event的通知夕晓。

在Python中調(diào)用epoll

  • select.epoll([sizehint=-1])返回一個epoll對象。

  • eventmask

事件常量 意義
EPOLLIN 讀就緒
EPOLLOUT 寫就緒
EPOLLPRI 有數(shù)據(jù)緊急讀取
EPOLLERR assoc. fd有錯誤情況發(fā)生
EPOLLHUP assoc. fd發(fā)生掛起
EPOLLRT 設(shè)置邊緣觸發(fā)(ET)(默認(rèn)的是水平觸發(fā))
EPOLLONESHOT 設(shè)置為 one-short 行為悠咱,一個事件(event)被拉出后蒸辆,對應(yīng)的fd在內(nèi)部被禁用
EPOLLRDNORM 和 EPOLLIN 相等
EPOLLRDBAND 優(yōu)先讀取的數(shù)據(jù)帶(data band)
EPOLLWRNORM 和 EPOLLOUT 相等
EPOLLWRBAND 優(yōu)先寫的數(shù)據(jù)帶(data band)
EPOLLMSG 忽視
  • epoll.close()關(guān)閉epoll對象的文件描述符。
  • epoll.fileno返回control fd的文件描述符number析既。
  • epoll.fromfd(fd)用給予的fd來創(chuàng)建一個epoll對象躬贡。
  • epoll.register(fd[, eventmask])在epoll對象中注冊一個文件描述符。(如果文件描述符已經(jīng)存在眼坏,將會引起一個IOError
  • epoll.modify(fd, eventmask)修改一個已經(jīng)注冊的文件描述符逗宜。
  • epoll.unregister(fd)注銷一個文件描述符。
  • epoll.poll(timeout=-1[, maxevnets=-1])等待事件空骚,timeout(float)的單位是秒(second)纺讲。

Ptython示例

epoll的示例就直接引用這篇出名的blog

import socket, select

EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
response  = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'
response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'
response += b'Hello, world!'

serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1)
serversocket.setblocking(0)

epoll = select.epoll()
epoll.register(serversocket.fileno(), select.EPOLLIN)

try:
   connections = {}; requests = {}; responses = {}
   while True:
      events = epoll.poll(1)
      for fileno, event in events:
         if fileno == serversocket.fileno():
            connection, address = serversocket.accept()
            connection.setblocking(0)
            epoll.register(connection.fileno(), select.EPOLLIN)
            connections[connection.fileno()] = connection
            requests[connection.fileno()] = b''
            responses[connection.fileno()] = response
         elif event & select.EPOLLIN:
            requests[fileno] += connections[fileno].recv(1024)
            if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
               epoll.modify(fileno, select.EPOLLOUT)
               print('-'*40 + '\n' + requests[fileno].decode()[:-2])
         elif event & select.EPOLLOUT:
            byteswritten = connections[fileno].send(responses[fileno])
            responses[fileno] = responses[fileno][byteswritten:]
            if len(responses[fileno]) == 0:
               epoll.modify(fileno, 0)
               connections[fileno].shutdown(socket.SHUT_RDWR)
         elif event & select.EPOLLHUP:
            epoll.unregister(fileno)
            connections[fileno].close()
            del connections[fileno]
finally:
   epoll.unregister(serversocket.fileno())
   epoll.close()
   serversocket.close()

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市囤屹,隨后出現(xiàn)的幾起案子熬甚,更是在濱河造成了極大的恐慌,老刑警劉巖肋坚,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乡括,死亡現(xiàn)場離奇詭異肃廓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)诲泌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門盲赊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人敷扫,你說我怎么就攤上這事哀蘑。” “怎么了葵第?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵绘迁,是天一觀的道長。 經(jīng)常有香客問我卒密,道長缀台,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任哮奇,我火速辦了婚禮膛腐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鼎俘。我一直安慰自己哲身,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布而芥。 她就那樣靜靜地躺著,像睡著了一般膀值。 火紅的嫁衣襯著肌膚如雪棍丐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天沧踏,我揣著相機(jī)與錄音歌逢,去河邊找鬼。 笑死翘狱,一個胖子當(dāng)著我的面吹牛秘案,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播潦匈,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼阱高,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了茬缩?” 一聲冷哼從身側(cè)響起赤惊,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凰锡,沒想到半個月后未舟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體圈暗,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年裕膀,在試婚紗的時候發(fā)現(xiàn)自己被綠了员串。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡昼扛,死狀恐怖寸齐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情野揪,我是刑警寧澤访忿,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站斯稳,受9級特大地震影響海铆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挣惰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一卧斟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧憎茂,春花似錦珍语、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拳氢,卻和暖如春募逞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背馋评。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工放接, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人留特。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓纠脾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蜕青。 傳聞我的和親對象是個殘疾皇子苟蹈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

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

  • 一、概述 I/O復(fù)用使得程序能同時監(jiān)聽多個文件描述符右核,這對提高程序的性能至關(guān)重要汉操。 I/O復(fù)用雖然能同時監(jiān)聽多個文...
    saviochen閱讀 1,044評論 0 4
  • epoll概述 epoll是linux中IO多路復(fù)用的一種機(jī)制,I/O多路復(fù)用就是通過一種機(jī)制蒙兰,一個進(jìn)程可以監(jiān)視多...
    發(fā)仔很忙閱讀 10,863評論 4 35
  • I/O多路復(fù)用問題的引出 當(dāng)需要讀兩個以上的I/O的時候磷瘤,如果使用阻塞式的I/O芒篷,那么可能長時間的阻塞在一個描述符...
    WendySays閱讀 517評論 2 4
  • 同步、異步采缚、阻塞针炉、非阻塞 同步 & 異步 同步與異步是針對多個事件(線程/進(jìn)程)來說的。 如果事件A需要等待事件B...
    rainybowe閱讀 2,883評論 0 9
  • 本文摘抄自linux基礎(chǔ)編程 IO概念 Linux的內(nèi)核將所有外部設(shè)備都可以看做一個文件來操作扳抽。那么我們對與外部設(shè)...
    lintong閱讀 1,571評論 0 4