原文地址: http://scotdoyle.com/python-epoll-howto.html ,我這裡取精簡內容翻譯過來。
============ 本文開始 ============ 介紹
Python 從 2.6 開始支援 epoll。現在我們用 Python3 來寫基於這些 API的 epoll 範例。 阻塞的 Socket 通訊範例
import socketEOL1 = b'\n\n'EOL2 = b'\n\r\n'response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'response += b'Hello, world!'serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)serversocket.bind(('0.0.0.0', 8080))serversocket.listen(1)try: while True: connectiontoclient, address = serversocket.accept() request = b'' while EOL1 not in request and EOL2 not in request: request += connectiontoclient.recv(1024) print('-'*40 + '\n' + request.decode()[:-2]) connectiontoclient.send(response) connectiontoclient.close()finally: serversocket.close()
這個範例中的代碼在 accept() 、 recv() 和 send() 時候會發生阻塞,導致其他串連無法完成。
通常情況下,在我們使用阻塞模型時候,會專門建立一個主線程來進行監聽,將建立成功的串連交給其他線程操作,然後繼續在主線程上面監聽。這樣一來,就不會受單次請求阻塞的限制。
C10K 問題描述了其他處理高並發方法,比如非同步 Socket,通過監聽事件來觸發預設的響應。非同步 Socket 可以是單線程,也可以是多線程。
Python 的 API 中包含了 select / poll / epoll,具體的可用性依賴於作業系統。他們的效率是 epoll > poll > select,從這個 效能測試文章 就可以看出來。 epoll 非同步編程範例
epoll 的流程是這樣的: 建立 epoll 執行個體 告訴 epoll 去監聽哪幾種類型事件 向 epoll 查詢最近已監聽事件的變化 根據不同的類型做不同的處理 讓 epoll 修改監聽列表 重複 3-5 直到結束 消滅 epoll 執行個體
範例代碼:
import socket, selectEOL1 = b'\n\n'EOL2 = b'\n\r\n'response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'response += b'Hello, world!'serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)serversocket.bind(('0.0.0.0', 8080))serversocket.listen(1)serversocket.setblocking(0)epoll = select.epoll()epoll.register(serversocket.fileno(), select.EPOLLIN)try: connections = {}; requests = {}; responses = {} while True: events = epoll.poll(1) for fileno, event in events: if fileno == serversocket.fileno(): connection, address = serversocket.accept() connection.setblocking(0) epoll.register(connection.fileno(), select.EPOLLIN) connections[connection.fileno()] = connection requests[connection.fileno()] = b'' responses[connection.fileno()] = response elif event & select.EPOLLIN: requests[fileno] += connections[fileno].recv(1024) if EOL1 in requests[fileno] or EOL2 in requests[fileno]: epoll.modify(fileno, select.EPOLLOUT) print('-'*40 + '\n' + requests[fileno].decode()[:-2]) elif event & select.EPOLLOUT: byteswritten = connections[fileno].send(responses[fileno]) responses[fileno] = responses[fileno][byteswritten:] if len(responses[fileno]) == 0: epoll.modify(fileno, 0) connections[fileno].shutdown(socket.SHUT_RDWR) elif event & select.EPOLLHUP: epoll.unregister(fileno) connections[fileno].close() del connections[fileno]finally: epoll.unregister(serversocket.fileno()) epoll.close() serversocket.close()
最關鍵的幾行如下: 16:註冊感興趣的事件 23:如果發現是監聽 socket,則建立串連 30:讀事件處理 33:讀事件完成後,修改 epoll 對應的狀態到寫事件 35:寫事件 41:釋放對應的串連
Epoll 分邊緣觸發(edge-triggered)和水平觸發(level-triggered)兩種,前者只被核心觸發一次通知(除非狀態被改變為未就緒),後者在觸發後如果不做操作,以後仍然會收到核心的觸發通知。 更多最佳化 串連等待池大小
我們之前的代碼直接使用 listen() 建立串連,可以通過設定一個隊列大小,在隊列滿了時候,就不再接受新的串連,從而保證已經接受的串連順利完成。 TCP 選項
使用 [TCP_CORK][] 功能,可以將小資料包封裝成大包傳輸,提高效率。
[TCP_NODELAY][] 則作用相反,將大包分成小包發送出去。比較適合即時應用比如 SSH。
(譯者:Linux下高效能網路編程中的幾個TCP/IP選項介紹這幾個 HTTP,寫的不錯。
範例中的源碼在 source code 下載。 原文連結: http://blog.log4d.com/2013/01/python-epoll/
3a1ff193cee606bd1e2ea554a16353ee