Python中的select模組專注於I/O多路復用,提供了select poll epoll三個方法(其中後兩個在Linux中可用,windows僅支援select),另外也提供了kqueue方法(freeBSD系統)
進程指定核心監聽哪些檔案描述子(最多監聽1024個fd)的哪些事件,當沒有檔案描述子事件發生時,進程被阻塞;當一個或多個檔案描述符事件發生時,進程被喚醒。
當我們呼叫select()時:
1、上下文切換轉換為核心態
2、將fd從使用者空間複製到核心空間
3、核心遍歷所有fd,查看其對應事件是否發生
4、如果沒發生,將進程阻塞,當設備驅動產生中斷或timeout時間後,將進程喚醒,再次進行遍歷
5、傳回遍歷後的fd
6、將fd從核心空間複製到使用者空間
fd:file descriptor 檔案描述符
fd_r_list, fd_w_list, fd_e_list = select.select(rlist, wlist, xlist, [timeout])
參數: 可接受四個參數(前三個必須)
rlist: wait until ready for reading
wlist: wait until ready for writing
xlist: wait for an “exceptional condition”
timeout: 逾時時間
傳回值:三個清單
select方法用來監視檔案描述子(當檔案描述子條件不滿足時,select會阻塞),當某個檔案描述子狀態改變後,會回傳三個清單
1、當參數1 序列中的fd滿足「可讀」條件時,則取得發生變化的fd並加入fd_r_list中
2、當參數2 序列中含有fd時,則將該序列中所有的fd加入fd_w_list中
3、當參數3 序列中的fd發生錯誤時,則將該發生錯誤的fd加到fd_e_list中
4、當逾時時間為空,則逾時時間為空,則逾時時間為空,則為空select會一直阻塞,直到監聽的句柄發生變化
當超時時間= n(正整數)時,那麼如果監聽的句柄均無任何變化,則select會阻塞n秒,之後返回三個空列表,如果監聽的句柄有變化,則直接執行。
利用select實作一個可並發的服務端
import socket import select s = socket.socket() s.bind(('127.0.0.1',8888)) s.listen(5) r_list = [s,] num = 0 while True: rl, wl, error = select.select(r_list,[],[],10) num+=1 print('counts is %s'%num) print("rl's length is %s"%len(rl)) for fd in rl: if fd == s: conn, addr = fd.accept() r_list.append(conn) msg = conn.recv(200) conn.sendall(('first----%s'%conn.fileno()).encode()) else: try: msg = fd.recv(200) fd.sendall('second'.encode()) except ConnectionAbortedError: r_list.remove(fd) s.close()
import socket flag = 1 s = socket.socket() s.connect(('127.0.0.1',8888)) while flag: input_msg = input('input>>>') if input_msg == '0': break s.sendall(input_msg.encode()) msg = s.recv(1024) print(msg.decode()) s.close()
在服務端我們可以看到,我們需要不停的呼叫select, 這就意味著:
1 當檔案描述子過多時,檔案描述子在使用者空間與核心空間進行copy會很費時
2 當檔案描述子過多時,核心對檔案描述子的遍歷也很浪費時間
3 select最大僅支援1024個檔案描述子
poll與select相差不大,本文不作介紹
#epoll很好的改進了select:
1、epoll的解決方案在epoll_ctl函數中。每次註冊新的事件到epoll句柄中時,會把所有的fd拷貝進內核,而不是在epoll_wait的時候重複拷貝。 epoll保證了每個fd在整個過程中只會拷貝一次。
2、epoll會在epoll_ctl時把指定的fd遍歷一遍(這一遍必不可少)並為每個fd指定一個回調函數,當設備就緒,喚醒等待隊列上的等待者時,就會呼叫這個回呼函數,而這個回呼函數會把就緒的fd加入一個就緒鍊錶。 epoll_wait的工作其實就是在這個就緒鍊錶中查看有沒有就緒的fd
3、epoll對檔案描述子沒有額外限制
select.epoll(sizehint=-1, flags= 0) 建立epoll物件
epoll.close()
Close the control file descriptor of the epoll object.關閉epoll物件的檔案描述子
epoll.closed
True if the epoll object is closed.偵測epoll物件是否關閉
#epoll.fileno()
Return the file descriptor number of the control fd.傳回epoll物件的檔案描述子
epoll.fromfd(fd)
#Create an epoll object from a given file descriptor.根據指定的fd創建epoll物件
epoll.register(fd[, eventmask])
Register a fd descriptor with the epoll object.向epoll物件中註冊fd和對應的事件
epoll.modify(fd, eventmask)
Modify a registered file descriptor.修改fd的事件
#
epoll.unregister(fd)
Remove a registered file descriptor from the epoll object.取消註冊
#epoll.poll(timeout=-1, maxevents=-1)
Wait for events. timeout in seconds (float)阻塞,直到註冊的fd事件發生,會傳回一個dict,格式為:{(fd1,event1),(fd2,event2) ,……(fdn,eventn)}
事件:
EPOLLIN Available for read 可讀狀態符為1
EPOLLOUT Available for write 可寫狀態符為4
EPOLLPRI Urgent data for read
EPOLLERR Error condition happened on the assoc. fd 發生錯誤狀態符號為8
EPOLLHUP Hang up happened on the assoc. fd
EPOLLHUP Hang up happened on the assoc. fd ####EPOL ##EPOLLET Set Edge Trigger behavior, the default is Level Trigger behavior 默认为水平触发,设置该事件后则边缘触发
EPOLLONESHOT Set one-shot behavior. After one event is pulled out, the fd is internally disabled
EPOLLRDNORM Equivalent to EPOLLIN
EPOLLRDBAND Priority data band can be read.
EPOLLWRNORM Equivalent to EPOLLOUT
EPOLLWRBAND Priority data may be written.
EPOLLMSG Ignored.
水平触发和边缘触发:
Level_triggered(水平触发,有时也称条件触发):当被监控的文件描述符上有可读写事件发生时,epoll.poll()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll.poll()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!! 优点很明显:稳定可靠
Edge_triggered(边缘触发,有时也称状态触发):当被监控的文件描述符上有可读写事件发生时,epoll.poll()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll.poll()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!缺点:某些条件下不可靠
import socket import select s = socket.socket() s.bind(('127.0.0.1',8888)) s.listen(5) epoll_obj = select.epoll() epoll_obj.register(s,select.EPOLLIN) connections = {} while True: events = epoll_obj.poll() for fd, event in events: print(fd,event) if fd == s.fileno(): conn, addr = s.accept() connections[conn.fileno()] = conn epoll_obj.register(conn,select.EPOLLIN) msg = conn.recv(200) conn.sendall('ok'.encode()) else: try: fd_obj = connections[fd] msg = fd_obj.recv(200) fd_obj.sendall('ok'.encode()) except BrokenPipeError: epoll_obj.unregister(fd) connections[fd].close() del connections[fd] s.close() epoll_obj.close()
import socket flag = 1 s = socket.socket() s.connect(('127.0.0.1',8888)) while flag: input_msg = input('input>>>') if input_msg == '0': break s.sendall(input_msg.encode()) msg = s.recv(1024) print(msg.decode()) s.close()
以上是python教程之select模組介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!