天天看點

python 網絡程式設計之epoll使用

 從以上可知,epoll是對select、poll模型的改進,提高了網絡程式設計的性能,廣泛應用于大規模并發請求的C/S架構中。

  1、觸發方式:

     邊緣觸發/水準觸發,隻适用于Unix/Linux作業系統

   2、原理圖

python 網絡程式設計之epoll使用

  3、一般步驟

  1. Create an epoll object——建立1個epoll對象
  2. Tell the epoll object to monitor specific events on specific sockets——告訴epoll對象,在指定的socket上監聽指定的事件
  3. Ask the epoll object which sockets may have had the specified event since the last query——詢問epoll對象,從上次查詢以來,哪些socket發生了哪些指定的事件
  4. Perform some action on those sockets——在這些socket上執行一些操作
  5. Tell the epoll object to modify the list of sockets and/or events to monitor——告訴epoll對象,修改socket清單和(或)事件,并監控
  6. Repeat steps 3 through 5 until finished——重複步驟3-5,直到完成
  7. 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.poll(timeout)  當檔案句柄發生變化,則會以清單的形式主動報告給使用者程序,timeout

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

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

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

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

epoll.fromfd(fileno) 從1個指定的檔案描述符建立1個epoll對象

epoll.close()   關閉epoll對象的控制檔案描述符

示例如下:

client:

#!/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() 

           

伺服器端:

#!/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()

服務端代碼