python?IO多路復(fù)用之epoll詳解
什么是epoll
epoll是什么?在linux的網(wǎng)絡(luò)編程中,很長(zhǎng)的時(shí)間都在使用select來做事件觸發(fā)。在linux新的內(nèi)核中,有了一種替換它的機(jī)制,就是epoll。當(dāng)然,這不是2.6內(nèi)核才有的,它是在2.5.44內(nèi)核中被引進(jìn)的(epoll(4) is a new API introduced in Linux kernel 2.5.44),它幾乎具備了之前所說的一切優(yōu)點(diǎn),被公認(rèn)為L(zhǎng)inux2.6下性能最好的多路復(fù)用I/O就緒通知方法。
相比于select,epoll最大的好處在于它不會(huì)隨著監(jiān)聽fd數(shù)目的增長(zhǎng)而降低效率。因?yàn)樵趦?nèi)核中的select實(shí)現(xiàn)中,它是采用輪詢來處理的,輪詢的fd數(shù)目越多,自然耗時(shí)越多。
epoll工作原理
epoll同樣只告知那些就緒的文件描述符,而且當(dāng)我們調(diào)用epoll_wait()獲得就緒文件描述符時(shí),返回的不是實(shí)際的描述符,而是一個(gè)代表就緒描述符數(shù)量的值,你只需要去epoll指定的一個(gè)數(shù)組中依次取得相應(yīng)數(shù)量的文件描述符即可,這里也使用了內(nèi)存映射(mmap)技術(shù),這樣便徹底省掉了這些文件描述符在系統(tǒng)調(diào)用時(shí)復(fù)制的開銷。
另一個(gè)本質(zhì)的改進(jìn)在于epoll采用基于事件的就緒通知方式。在select/poll中,進(jìn)程只有在調(diào)用一定的方法后,內(nèi)核才對(duì)所有監(jiān)視的文件描述符進(jìn)行掃描,而epoll事先通過epoll_ctl()來注冊(cè)一個(gè)文件描述符,一旦基于某個(gè)文件描述符就緒時(shí),內(nèi)核會(huì)采用類似callback的回調(diào)機(jī)制,迅速激活這個(gè)文件描述符,當(dāng)進(jìn)程調(diào)用epoll_wait()時(shí)便得到通知。
從以上可知,epoll是對(duì)select、poll模型的改進(jìn),提高了網(wǎng)絡(luò)編程的性能,廣泛應(yīng)用于大規(guī)模并發(fā)請(qǐng)求的C/S架構(gòu)中。
python中的epoll
1、觸發(fā)方式:
邊緣觸發(fā)/水平觸發(fā),只適用于Unix/Linux操作系統(tǒng)
2、原理圖
3、一般步驟
Create an epoll object——創(chuàng)建1個(gè)epoll對(duì)象
Tell the epoll object to monitor specific events on specific sockets——告訴epoll對(duì)象,在指定的socket上監(jiān)聽指定的事件
Ask the epoll object which sockets may have had the specified event since the last query——詢問epoll對(duì)象,從上次查詢以來,哪些socket發(fā)生了哪些指定的事件
Perform some action on those sockets——在這些socket上執(zhí)行一些操作
Tell the epoll object to modify the list of sockets and/or events to monitor——告訴epoll對(duì)象,修改socket列表和(或)事件,并監(jiān)控
Repeat steps 3 through 5 until finished——重復(fù)步驟3-5,直到完成
Destroy the epoll object——銷毀epoll對(duì)象
4、相關(guān)用法
import select 導(dǎo)入select模塊
epoll = select.epoll()創(chuàng)建一個(gè)epoll對(duì)象
epoll.register(文件句柄,事件類型)注冊(cè)要監(jiān)控的文件句柄和事件
事件類型:
select.EPOLLIN 可讀事件
select.EPOLLOUT 可寫事件
select.EPOLLERR 錯(cuò)誤事件
select.EPOLLHUP 客戶端斷開事件
epoll.unregister(文件句柄) 銷毀文件句柄
epoll.poll(timeout) 當(dāng)文件句柄發(fā)生變化,則會(huì)以列表的形式主動(dòng)報(bào)告給用戶進(jìn)程,timeout
為超時(shí)時(shí)間,默認(rèn)為-1,即一直等待直到文件句柄發(fā)生變化,如果指定為1
那么epoll每1秒?yún)R報(bào)一次當(dāng)前文件句柄的變化情況,如果無變化則返回空
epoll.fileno() 返回epoll的控制文件描述符(Return the epoll control file descriptor)
epoll.modfiy(fineno,event)fineno為文件描述符 event為事件類型 作用是修改文件描述符所對(duì)應(yīng)的事件
epoll.fromfd(fileno)從1個(gè)指定的文件描述符創(chuàng)建1個(gè)epoll對(duì)象
epoll.close() 關(guān)閉epoll對(duì)象的控制文件描述符
5 實(shí)例:客戶端發(fā)送數(shù)據(jù) 服務(wù)端將接收的數(shù)據(jù)返回給客戶端
服務(wù)端代碼
#!/usr/bin/env python #-*- coding:utf-8 -*- import socket import select import Queue #創(chuàng)建socket對(duì)象 serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #設(shè)置IP地址復(fù)用 serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #ip地址和端口號(hào) server_address = ("127.0.0.1", 8888) #綁定IP地址 serversocket.bind(server_address) #監(jiān)聽,并設(shè)置最大連接數(shù) serversocket.listen(10) print ?"服務(wù)器啟動(dòng)成功,監(jiān)聽I(yíng)P:" , server_address #服務(wù)端設(shè)置非阻塞 serversocket.setblocking(False) ? #超時(shí)時(shí)間 timeout = 10 #創(chuàng)建epoll事件對(duì)象,后續(xù)要監(jiān)控的事件添加到其中 epoll = select.epoll() #注冊(cè)服務(wù)器監(jiān)聽fd到等待讀事件集合 epoll.register(serversocket.fileno(), select.EPOLLIN) #保存連接客戶端消息的字典,格式為{} message_queues = {} #文件句柄到所對(duì)應(yīng)對(duì)象的字典,格式為{句柄:對(duì)象} fd_to_socket = {serversocket.fileno():serversocket,} while True: ? print "等待活動(dòng)連接......" ? #輪詢注冊(cè)的事件集合,返回值為[(文件句柄,對(duì)應(yīng)的事件),(...),....] ? events = epoll.poll(timeout) ? if not events: ? ? ?print "epoll超時(shí)無活動(dòng)連接,重新輪詢......" ? ? ?continue ? print "有" , len(events), "個(gè)新事件,開始處理......" ? ? ? for fd, event in events: ? ? ?socket = fd_to_socket[fd] ? ? ?#如果活動(dòng)socket為當(dāng)前服務(wù)器socket,表示有新連接 ? ? ?if socket == serversocket: ? ? ? ? ? ? connection, address = serversocket.accept() ? ? ? ? ? ? print "新連接:" , address ? ? ? ? ? ? #新連接socket設(shè)置為非阻塞 ? ? ? ? ? ? connection.setblocking(False) ? ? ? ? ? ? #注冊(cè)新連接fd到待讀事件集合 ? ? ? ? ? ? epoll.register(connection.fileno(), select.EPOLLIN) ? ? ? ? ? ? #把新連接的文件句柄以及對(duì)象保存到字典 ? ? ? ? ? ? fd_to_socket[connection.fileno()] = connection ? ? ? ? ? ? #以新連接的對(duì)象為鍵值,值存儲(chǔ)在隊(duì)列中,保存每個(gè)連接的信息 ? ? ? ? ? ? message_queues[connection] ?= Queue.Queue() ? ? ?#關(guān)閉事件 ? ? ?elif event & select.EPOLLHUP: ? ? ? ? print 'client close' ? ? ? ? #在epoll中注銷客戶端的文件句柄 ? ? ? ? epoll.unregister(fd) ? ? ? ? #關(guān)閉客戶端的文件句柄 ? ? ? ? fd_to_socket[fd].close() ? ? ? ? #在字典中刪除與已關(guān)閉客戶端相關(guān)的信息 ? ? ? ? del fd_to_socket[fd] ? ? ?#可讀事件 ? ? ?elif event & select.EPOLLIN: ? ? ? ? #接收數(shù)據(jù) ? ? ? ? data = socket.recv(1024) ? ? ? ? if data: ? ? ? ? ? ?print "收到數(shù)據(jù):" , data , "客戶端:" , socket.getpeername() ? ? ? ? ? ?#將數(shù)據(jù)放入對(duì)應(yīng)客戶端的字典 ? ? ? ? ? ?message_queues[socket].put(data) ? ? ? ? ? ?#修改讀取到消息的連接到等待寫事件集合(即對(duì)應(yīng)客戶端收到消息后,再將其fd修改并加入寫事件集合) ? ? ? ? ? ?epoll.modify(fd, select.EPOLLOUT) ? ? ?#可寫事件 ? ? ?elif event & select.EPOLLOUT: ? ? ? ? try: ? ? ? ? ? ?#從字典中獲取對(duì)應(yīng)客戶端的信息 ? ? ? ? ? ?msg = message_queues[socket].get_nowait() ? ? ? ? except Queue.Empty: ? ? ? ? ? ?print socket.getpeername() , " queue empty" ? ? ? ? ? ?#修改文件句柄為讀事件 ? ? ? ? ? ?epoll.modify(fd, select.EPOLLIN) ? ? ? ? else : ? ? ? ? ? ?print "發(fā)送數(shù)據(jù):" , data , "客戶端:" , socket.getpeername() ? ? ? ? ? ?#發(fā)送數(shù)據(jù) ? ? ? ? ? ?socket.send(msg) #在epoll中注銷服務(wù)端文件句柄 epoll.unregister(serversocket.fileno()) #關(guān)閉epoll epoll.close() #關(guān)閉服務(wù)器socket serversocket.close()
客戶端代碼:
#!/usr/bin/env python #-*- coding:utf-8 -*- import socket #創(chuàng)建客戶端socket對(duì)象 clientsocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #服務(wù)端IP地址和端口號(hào)元組 server_address = ('127.0.0.1',8888) #客戶端連接指定的IP地址和端口號(hào) clientsocket.connect(server_address) while True: ? ? #輸入數(shù)據(jù) ? ? data = raw_input('please input:') ? ? #客戶端發(fā)送數(shù)據(jù) ? ? clientsocket.sendall(data) ? ? #客戶端接收數(shù)據(jù) ? ? server_data = clientsocket.recv(1024) ? ? print '客戶端收到的數(shù)據(jù):'server_data ? ? #關(guān)閉客戶端socket ? ? clientsocket.close()
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Python異常對(duì)象Exception基礎(chǔ)類異常捕捉
這篇文章主要為大家介紹了Python異常對(duì)象異常捕捉及Exception基礎(chǔ)類,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06解決pytorch 交叉熵?fù)p失輸出為負(fù)數(shù)的問題
這篇文章主要介紹了解決pytorch 交叉熵?fù)p失輸出為負(fù)數(shù)的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07Python 實(shí)現(xiàn)opencv所使用的圖片格式與 base64 轉(zhuǎn)換
今天小編就為大家分享一篇Python 實(shí)現(xiàn)opencv所使用的圖片格式與 base64 轉(zhuǎn)換,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-01-01Python列表刪除元素del、pop()和remove()的區(qū)別小結(jié)
這篇文章主要給大家介紹了關(guān)于Python列表刪除元素del、pop()和remove()的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Python具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09django實(shí)現(xiàn)類似觸發(fā)器的功能
今天小編就為大家分享一篇django實(shí)現(xiàn)類似觸發(fā)器的功能,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-11-11Python的Django框架中forms表單類的使用方法詳解
用戶表單是Web端的一項(xiàng)基本功能,大而全的Django框架中自然帶有現(xiàn)成的基礎(chǔ)form對(duì)象,Python的Django框架中forms表單類的使用方法詳解2016-06-06DjangoWeb使用Datatable進(jìn)行后端分頁(yè)的實(shí)現(xiàn)
這篇文章主要介紹了DjangoWeb使用Datatable進(jìn)行后端分頁(yè)的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-05-05Python實(shí)時(shí)監(jiān)控網(wǎng)站瀏覽記錄實(shí)現(xiàn)過程詳解
這篇文章主要介紹了Python實(shí)時(shí)監(jiān)控網(wǎng)站瀏覽記錄實(shí)現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07