python?IO多路復(fù)用之epoll詳解
什么是epoll
epoll是什么?在linux的網(wǎng)絡(luò)編程中,很長(zhǎng)的時(shí)間都在使用select來(lái)做事件觸發(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),它幾乎具備了之前所說(shuō)的一切優(yōu)點(diǎn),被公認(rèn)為L(zhǎng)inux2.6下性能最好的多路復(fù)用I/O就緒通知方法。
相比于select,epoll最大的好處在于它不會(huì)隨著監(jiān)聽(tīng)fd數(shù)目的增長(zhǎng)而降低效率。因?yàn)樵趦?nèi)核中的select實(shí)現(xiàn)中,它是采用輪詢來(lái)處理的,輪詢的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事先通過(guò)epoll_ctl()來(lái)注冊(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)聽(tīng)指定的事件
Ask the epoll object which sockets may have had the specified event since the last query——詢問(wèn)epoll對(duì)象,從上次查詢以來(lái),哪些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)前文件句柄的變化情況,如果無(wú)變化則返回空
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)聽(tīng),并設(shè)置最大連接數(shù) serversocket.listen(10) print ?"服務(wù)器啟動(dòng)成功,監(jiān)聽(tīng)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)聽(tīng)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í)無(wú)活動(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ù)的問(wèn)題
這篇文章主要介紹了解決pytorch 交叉熵?fù)p失輸出為負(fù)數(shù)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07Python 實(shí)現(xiàn)opencv所使用的圖片格式與 base64 轉(zhuǎn)換
今天小編就為大家分享一篇Python 實(shí)現(xiàn)opencv所使用的圖片格式與 base64 轉(zhuǎn)換,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-01-01Python列表刪除元素del、pop()和remove()的區(qū)別小結(jié)
這篇文章主要給大家介紹了關(guān)于Python列表刪除元素del、pop()和remove()的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Python具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09django實(shí)現(xiàn)類似觸發(fā)器的功能
今天小編就為大家分享一篇django實(shí)現(xiàn)類似觸發(fā)器的功能,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧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ì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-05-05Python實(shí)時(shí)監(jiān)控網(wǎng)站瀏覽記錄實(shí)現(xiàn)過(guò)程詳解
這篇文章主要介紹了Python實(shí)時(shí)監(jiān)控網(wǎng)站瀏覽記錄實(shí)現(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07