首頁  >  文章  >  後端開發  >  python之IO多路復用之epoll

python之IO多路復用之epoll

巴扎黑
巴扎黑原創
2017-08-21 14:48:531657瀏覽

什麼是epoll

epoll是什麼?在linux的網路程式設計中,很長的時間都在使用select來做事件觸發。在linux新的核心中,有了一個取代它的機制,就是epoll。當然,這不是2.6核心才有的,它是在2.5.44核心中被引進的(epoll(4) is a new API introduced in Linux kernel 2.5.44),它幾乎具備了之前所說的一切優點,被公認為Linux2.6下性能最好的多路復用I/O就緒通知方法。

比起select,epoll最大的好處是它不會隨著監聽fd數目的成長而降低效率。因為在核心中的select實作中,它是採用輪詢來處理的,輪詢的fd數目越多,自然耗時越多。

epoll工作原理

epoll同樣只告知那些就緒的檔案描述符,而且當我們調用epoll_wait()獲得就緒檔案描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值,你只需要去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這裡也使用了內存映射(mmap)技術,這樣便徹底省掉了這些文件描述符在系統呼叫時複製的開銷。

 

另一個本質的改進在於epoll採用基於事件的就緒通知方式。在select/poll中,進程只有在呼叫一定的方法後,核心才對所有監視的檔案描述符進行掃描,而epoll事先透過epoll_ctl()來註冊一個檔案描述符,一旦基於某個檔案描述符就緒時,核心會採用類似callback的回呼機制,迅速啟動這個檔案描述符,當進程呼叫epoll_wait()時便得到通知。

從以上可知,epoll是對select、poll模型的改進,提升了網路程式設計的效能,廣泛應用於大規模並發請求的C/S架構。

python中的epoll

1、觸發方式:

     邊緣觸發/水平觸發,只適用於Unix/Linux作業系統

2、原理圖片

python之IO多路復用之epoll

3、一般步驟

Create an epoll object-建立1個epoll物件

Tell the epoll object to monitor specific events on specific sockets——告訴epoll對象,在指定的socket上監聽指定的事件

Ask the epoll object which sockets may have had the specified event since the last query——詢問epoll對象,從上次詢問查詢以來,哪些socket發生了哪些指定的事件

Perform some action on those sockets——在這些socket上執行一些操作

Tell the epoll object to modify the list of sockets and/或 events to monitor——告訴epoll對象,修改socket列表和(或)事件,並監控

Repeat steps 3 through 5 until finished——重複步驟3-5,直到完成

# Destroy the epoll object——銷毀epoll物件

4、相關用法

import select 導入select模組

epoll = select.epoll()建立一個epoll物件

epoll.register(檔案句柄,事件類型)註冊要監控的檔案句柄與事件

事件類型:

  select.EPOLLIN    可讀事件

  select. EPOLLOUT   可寫事件

  select.EPOLLERR   錯誤事件

  select.EPOLLHUP   客戶端斷開事件

epoll.unregister(文件句柄)   客戶端斷開事件

epoll.unregister(檔案句柄)   銷毀句柄#epoll.poll(timeout) 當檔案句柄發生變化,則會以清單的形式主動回報給使用者流程,timeout

                     為逾時時間,預設為-1,即等待直到檔案句柄發生變化,直到檔案句柄發生變化,如果指定為1

                     那麼epoll每1秒回報一次目前檔案句柄的變更情況,如果無變更則傳回空白

##epoll.fileno() 傳回epoll的控制檔描述符the epoll control file descriptor)

epoll.modfiy(fineno,event)fineno為檔案描述子event為事件類型 作用是修改檔案描述子所對應的事件

#epoll.fromfd(fileno )從1個指定的檔案描述子建立1個epoll物件

epoll.close()   關閉epoll物件的控制檔描述符

5 實例:客戶端傳送資料服務端將接收的資料傳回給客戶端

服務端程式碼

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import socket
import select
import Queue
#创建socket对象
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#设置IP地址复用
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#ip地址和端口号
server_address = ("127.0.0.1", 8888)
#绑定IP地址
serversocket.bind(server_address)
#监听,并设置最大连接数
serversocket.listen(10)
print  "服务器启动成功,监听IP:" , server_address
#服务端设置非阻塞
serversocket.setblocking(False)  
#超时时间
timeout = 10
#创建epoll事件对象,后续要监控的事件添加到其中
epoll = select.epoll()
#注册服务器监听fd到等待读事件集合
epoll.register(serversocket.fileno(), select.EPOLLIN)
#保存连接客户端消息的字典,格式为{}
message_queues = {}
#文件句柄到所对应对象的字典,格式为{句柄:对象}
fd_to_socket = {serversocket.fileno():serversocket,}
while True:
  print "等待活动连接......"
  #轮询注册的事件集合,返回值为[(文件句柄,对应的事件),(...),....]
  events = epoll.poll(timeout)
  if not events:
     print "epoll超时无活动连接,重新轮询......"
     continue
  print "有" , len(events), "个新事件,开始处理......"
  
  for fd, event in events:
     socket = fd_to_socket[fd]
     #如果活动socket为当前服务器socket,表示有新连接
     if socket == serversocket:
            connection, address = serversocket.accept()
            print "新连接:" , address
            #新连接socket设置为非阻塞
            connection.setblocking(False)
            #注册新连接fd到待读事件集合
            epoll.register(connection.fileno(), select.EPOLLIN)
            #把新连接的文件句柄以及对象保存到字典
            fd_to_socket[connection.fileno()] = connection
            #以新连接的对象为键值,值存储在队列中,保存每个连接的信息
            message_queues[connection]  = Queue.Queue()
     #关闭事件
     elif event & select.EPOLLHUP:
        print 'client close'
        #在epoll中注销客户端的文件句柄
        epoll.unregister(fd)
        #关闭客户端的文件句柄
        fd_to_socket[fd].close()
        #在字典中删除与已关闭客户端相关的信息
        del fd_to_socket[fd]
     #可读事件
     elif event & select.EPOLLIN:
        #接收数据
        data = socket.recv(1024)
        if data:
           print "收到数据:" , data , "客户端:" , socket.getpeername()
           #将数据放入对应客户端的字典
           message_queues[socket].put(data)
           #修改读取到消息的连接到等待写事件集合(即对应客户端收到消息后,再将其fd修改并加入写事件集合)
           epoll.modify(fd, select.EPOLLOUT)
     #可写事件
     elif event & select.EPOLLOUT:
        try:
           #从字典中获取对应客户端的信息
           msg = message_queues[socket].get_nowait()
        except Queue.Empty:
           print socket.getpeername() , " queue empty"
           #修改文件句柄为读事件
           epoll.modify(fd, select.EPOLLIN)
        else :
           print "发送数据:" , data , "客户端:" , socket.getpeername()
           #发送数据
           socket.send(msg)
#在epoll中注销服务端文件句柄
epoll.unregister(serversocket.fileno())
#关闭epoll
epoll.close()
#关闭服务器socket
serversocket.close()

客戶端程式碼:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import socket
#创建客户端socket对象
clientsocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#服务端IP地址和端口号元组
server_address = ('127.0.0.1',8888)
#客户端连接指定的IP地址和端口号
clientsocket.connect(server_address)
while True:
    #输入数据
    data = raw_input('please input:')
    #客户端发送数据
    clientsocket.sendall(data)
    #客户端接收数据
    server_data = clientsocket.recv(1024)
    print '客户端收到的数据:'server_data
    #关闭客户端socket
    clientsocket.close()

以上是python之IO多路復用之epoll的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn