select, poll, epoll本質上都是同步的I/O,由於它們都是在讀寫事件就緒後本身負責進行讀寫,這個讀寫的過程是阻塞的。html
select, poll, epoll 都是一種 I/O 複用的機制。它們都是經過一種機制(由系統提供的)來監視多個描述符,一旦某個描述符就緒了,就能通知程序進行相應的讀寫操做。python
select 是經過系統調用來監視着一個由多個文件描述符(file descriptor)組成的數組,當select()返回後,數組中就緒的文件描述符會被內核修改標記位(其實就是一個整數),使得進程能夠得到這些文件描述符從而進行後續的讀寫操做。select飾經過遍歷來監視整個數組的,並且每次遍歷都是線性的。linux
Python中,select,poll,epoll和unix的kqueue()都在模塊select
中。數組
調用select的函數爲select.select(rlist, wlist, xlist[, timeout])
,前三個參數都分別是三個數組,數組中的對象均爲waitable object
:均是整數的文件描述符(file descriptor)或者一個擁有返回文件描述符方法fileno()
的對象;app
rlist
: 等待讀就緒的listwlist
: 等待寫就緒的listxlist
: 等待「異常」的list這三個list能夠是一個空的list,可是接收3個空的list是依賴於系統的(在Linux上是能夠接受的,可是在window上是不能夠的)。socket
timeout
參數是接受一個 float
的數字,單位是秒。當缺省timeout
時,select會一直阻塞之道至少有一個文件描述符(fd)準備就緒。若是timeout
設爲0時,則select不會阻塞。函數
函數的返回值是返回三個準備就緒的list: 對應者rlist
, wlist
, xlist
這三個list的子集。若是timeout,會返回3個空的list。unix
在list中能夠接受Ptython的的file
對象(好比sys.stdin
,或者會被open()
和os.open()
返回的object),socket object將會返回socket.socket()
。也能夠自定義類,只要有一個合適的fileno()
的方法(須要真實返回一個文件描述符,而不是一個隨機的整數)。code
#!/usr/bin/env python # -*- coding: utf-8 -*- import select, socket response = b"hello world" #建立一個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本質上和select沒有區別,只是沒有了最大鏈接數(linux上默認1024個)的限制,緣由是它基於鏈表存儲的。server
poll除了沒有了最大鏈接數的缺點,其餘都和select同樣
select.poll()
,返回一個poll的對象,支持註冊和註銷文件描述符。
poll.register(fd[, eventmask])
註冊一個文件描述符,註冊後,能夠經過poll()
方法來檢查是否有對應的I/O事件發生。fd
能夠是i 個整數,或者有返回整數的fileno()
方法對象。若是File對象實現了fileno(),也能夠看成參數使用。
eventmask
是一個你想去檢查的事件類型,它能夠是常量POLLIN
, POLLPRI
和 POLLOUT
的組合。若是缺省,默認會去檢查全部的3種事件類型。
事件常量 | 意義 |
---|---|
POLLIN | 有數據讀取 |
POLLPRT | 有數據緊急讀取 |
POLLOUT | 準備輸出:輸出不會阻塞 |
POLLERR | 某些錯誤狀況出現 |
POLLHUP | 掛起 |
POLLNVAL | 無效請求:描述沒法打開 |
poll.modify(fd, eventmask)
修改一個已經存在的fd,和poll.register(fd, eventmask)
有相同的做用。若是去嘗試修改一個未經註冊的fd,會引發一個errno
爲ENOENT的IOError
。poll.unregister(fd)
從poll對象中註銷一個fd。嘗試去註銷一個未經註冊的fd,會引發KeyError
。poll.poll([timeout])
去檢測已經註冊了的文件描述符。會返回一個可能爲空的list,list中包含着(fd, event)
這樣的二元組。 fd
是文件描述符, event
是文件描述符對應的事件。若是返回的是一個空的list,則說明超時了且沒有文件描述符有事件發生。timeout
的單位是milliseconds,若是設置了timeout
,系統將會等待對應的時間。若是timeout
缺省或者是None
,這個方法將會阻塞直到對應的poll對象有一個事件發生。#!/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()
在linux2.6(準確來講是2.5.44)由內核直接支持的方法。epoll解決了select和poll的缺點。
epoll同時支持水平觸發和邊緣觸發:
epoll.poll()
會重複通知關注的event,直到與該event有關的全部數據都已被處理。(select, poll是水平觸發, epoll默認水平觸發)epoll.poll()
的程序必須處理全部和這個event相關的數據,隨後的epoll.poll()
調用不會再有這個event的通知。select.epoll([sizehint=-1])
返回一個epoll對象。
eventmask
事件常量 | 意義 |
---|---|
EPOLLIN | 讀就緒 |
EPOLLOUT | 寫就緒 |
EPOLLPRI | 有數據緊急讀取 |
EPOLLERR | assoc. fd有錯誤狀況發生 |
EPOLLHUP | assoc. fd發生掛起 |
EPOLLRT | 設置邊緣觸發(ET)(默認的是水平觸發) |
EPOLLONESHOT | 設置爲 one-short 行爲,一個事件(event)被拉出後,對應的fd在內部被禁用 |
EPOLLRDNORM | 和 EPOLLIN 相等 |
EPOLLRDBAND | 優先讀取的數據帶(data band) |
EPOLLWRNORM | 和 EPOLLOUT 相等 |
EPOLLWRBAND | 優先寫的數據帶(data band) |
EPOLLMSG | 忽視 |
epoll.close()
關閉epoll對象的文件描述符。epoll.fileno
返回control fd的文件描述符number。epoll.fromfd(fd)
用給予的fd來建立一個epoll對象。epoll.register(fd[, eventmask])
在epoll對象中註冊一個文件描述符。(若是文件描述符已經存在,將會引發一個IOError
)epoll.modify(fd, eventmask)
修改一個已經註冊的文件描述符。epoll.unregister(fd)
註銷一個文件描述符。epoll.poll(timeout=-1[, maxevnets=-1])
等待事件,timeout(float)的單位是秒(second)。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()