Epoll and poll are better than select because the Python program does not have to inspect each socket for events of interest. Instead it can rely on the operating system to tell it which sockets may have these events.
And epoll is better than poll because it does not require the operating system to inspect all sockets for events of interest each time it is queried by the Python program. Rather Linux tracks these events as they occur, and returns a list when queried by Python.
Programs using epoll often perform actions in this sequence:
Create an epoll object
Tell the epoll object to monitor specific events on specific sockets
Ask the epoll object which sockets may have had the specified event since the last query
Perform some action on those sockets
Tell the epoll object to modify the list of sockets and/or events to monitor
Repeat steps 3 through 5 until finished
Destroy the epoll object
The server.py looks very like the poll in #15, actually we just replace poll with epoll.
(click to unfold)
```python
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import select
import Queue
import sys
# Create a TCP/IP socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(0)
# Bind the socket to the port
server_address = ('localhost', 10000)
print >> sys.stderr, 'starting up on %s port %s' % server_address
server.bind(server_address)
# Listen for incoming connections
server.listen(5)
# Keep up with the queues of outgoing messages
message_queues = {}
# Do not block forever(milliseconds)
TIMEOUT = 1000
# Commonly used flag setes
READ_ONLY = select.EPOLLIN | select.EPOLLPRI | select.EPOLLHUP | select.EPOLLERR
READ_WRITE = READ_ONLY | select.EPOLLOUT
# Set up the epoller
epoller = select.epoll()
epoller.register(server, READ_ONLY)
# Map file description to socket objects
fd_to_sockets = {server.fileno(): server}
while True:
# Wait for at least one of the sockets to be ready for processing
print >> sys.stderr, '\nWaiting for the next event'
events = epoller.poll(TIMEOUT)
for fd, flag in events:
s = fd_to_sockets[fd]
# Handle inputs
if flag & (select.EPOLLIN | select.EPOLLPRI):
# A readable server is ready to accept a connection
if s is server:
connection, client_address = s.accept()
print >> sys.stderr, 'new connection from', client_address
connection.setblocking(0)
fd_to_sockets[connection.fileno()] = connection
epoller.register(connection, READ_ONLY)
# Give the connection a queue for the data we want to send
message_queues[connection] = Queue.Queue()
else:
data = s.recv(1024)
# A readable client socket has data
if data:
print >> sys.stderr, 'received "%s" from %s' % (data, s.getpeername())
# Add output channel for response
message_queues[s].put(data)
epoller.modify(s, READ_WRITE)
else:
# Interpret empty result as closed connection
print >> sys.stderr, 'closing ', client_address, 'after reading no data'
# Stop listening for input on the connection
epoller.unregister(s)
s.close()
# Remove the message queue
del message_queues[s]
elif flag & select.EPOLLHUP:
# Client hung up
print >> sys.stderr, 'closing', client_address, ' after receiving HUP'
# Stop listening for input on the connection
epoller.unregister(s)
s.close()
elif flag & select.EPOLLOUT:
# Socket is ready to send data, if there is any to send
try:
next_msg = message_queues[s].get_nowait()
except Queue.Empty:
# No message waiting so stop checking for writability
print >> sys.stderr, 'output queue for', s.getpeername(), 'is empty'
epoller.modify(s, READ_ONLY)
else:
print >> sys.stderr, 'sending "%s" to "%s"' % (next_msg, s.getpeername())
s.send(next_msg)
elif flag & select.POLLERR:
print >> sys.stderr, 'handling exceptional condition for', s.getpeername()
epoller.unregister(s)
s.close()
# Remove message queue
del message_queues[s]
```
Client is just like the #11 client.py.
Modes of operation
Epoll has two modes of operation, called edge-triggered and level-triggered(which is the default mode of operation). In the edge-triggered mode of operation a call to epoll.poll() will return an event on a socket only once after the read or write event occurred on that socket. The calling program must process all of the data associated with that event without further notifications on subsequent calls to epoll.poll(). When the data from a particular event is exhausted, additional attempts to operate on the socket will cause an exception. Conversely, in the level-triggered mode of operation, repeated calls to epoll.poll() will result in repeated notifications of the event of interest, until all data associated with that event has been processed. No exceptions normally occur in level-triggered mode.
# Set Edge Trigger behavior, the default is Level Trigger behavior
epoll.register(serversocket.fileno(), select.EPOLLIN | select.EPOLLET)
For example, suppose a server socket has been registered with an epoll object for read events. In edge-triggered mode the program would need to accept() new socket connections until a socket.error exception occurs. Whereas in the level-triggered mode of operation a single accept() call can be made and then the epoll object can be queried again for new events on the server socket indicating that additional calls to accept() should be made.
Epoll and poll are better than select because the Python program does not have to inspect each socket for events of interest. Instead it can rely on the operating system to tell it which sockets may have these events.
And epoll is better than poll because it does not require the operating system to inspect all sockets for events of interest each time it is queried by the Python program. Rather Linux tracks these events as they occur, and returns a list when queried by Python.
Programs using epoll often perform actions in this sequence:
The server.py looks very like the poll in #15, actually we just replace poll with epoll.
(click to unfold)
```python #! /usr/bin/env python # -*- coding: utf-8 -*- import socket import select import Queue import sys # Create a TCP/IP socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setblocking(0) # Bind the socket to the port server_address = ('localhost', 10000) print >> sys.stderr, 'starting up on %s port %s' % server_address server.bind(server_address) # Listen for incoming connections server.listen(5) # Keep up with the queues of outgoing messages message_queues = {} # Do not block forever(milliseconds) TIMEOUT = 1000 # Commonly used flag setes READ_ONLY = select.EPOLLIN | select.EPOLLPRI | select.EPOLLHUP | select.EPOLLERR READ_WRITE = READ_ONLY | select.EPOLLOUT # Set up the epoller epoller = select.epoll() epoller.register(server, READ_ONLY) # Map file description to socket objects fd_to_sockets = {server.fileno(): server} while True: # Wait for at least one of the sockets to be ready for processing print >> sys.stderr, '\nWaiting for the next event' events = epoller.poll(TIMEOUT) for fd, flag in events: s = fd_to_sockets[fd] # Handle inputs if flag & (select.EPOLLIN | select.EPOLLPRI): # A readable server is ready to accept a connection if s is server: connection, client_address = s.accept() print >> sys.stderr, 'new connection from', client_address connection.setblocking(0) fd_to_sockets[connection.fileno()] = connection epoller.register(connection, READ_ONLY) # Give the connection a queue for the data we want to send message_queues[connection] = Queue.Queue() else: data = s.recv(1024) # A readable client socket has data if data: print >> sys.stderr, 'received "%s" from %s' % (data, s.getpeername()) # Add output channel for response message_queues[s].put(data) epoller.modify(s, READ_WRITE) else: # Interpret empty result as closed connection print >> sys.stderr, 'closing ', client_address, 'after reading no data' # Stop listening for input on the connection epoller.unregister(s) s.close() # Remove the message queue del message_queues[s] elif flag & select.EPOLLHUP: # Client hung up print >> sys.stderr, 'closing', client_address, ' after receiving HUP' # Stop listening for input on the connection epoller.unregister(s) s.close() elif flag & select.EPOLLOUT: # Socket is ready to send data, if there is any to send try: next_msg = message_queues[s].get_nowait() except Queue.Empty: # No message waiting so stop checking for writability print >> sys.stderr, 'output queue for', s.getpeername(), 'is empty' epoller.modify(s, READ_ONLY) else: print >> sys.stderr, 'sending "%s" to "%s"' % (next_msg, s.getpeername()) s.send(next_msg) elif flag & select.POLLERR: print >> sys.stderr, 'handling exceptional condition for', s.getpeername() epoller.unregister(s) s.close() # Remove message queue del message_queues[s] ```Client is just like the #11 client.py.
Modes of operation
Epoll has two modes of operation, called
edge-triggered
andlevel-triggered
(which is the default mode of operation). In the edge-triggered mode of operation a call to epoll.poll() will return an event on a socket only once after the read or write event occurred on that socket. The calling program must process all of the data associated with that event without further notifications on subsequent calls to epoll.poll(). When the data from a particular event is exhausted, additional attempts to operate on the socket will cause an exception. Conversely, in the level-triggered mode of operation, repeated calls to epoll.poll() will result in repeated notifications of the event of interest, until all data associated with that event has been processed. No exceptions normally occur in level-triggered mode.For example, suppose a server socket has been registered with an epoll object for read events. In edge-triggered mode the program would need to accept() new socket connections until a
socket.error
exception occurs. Whereas in the level-triggered mode of operation a single accept() call can be made and then the epoll object can be queried again for new events on the server socket indicating that additional calls to accept() should be made.Ref
How To Use Linux epoll with Python
IO Model
epoll(4) - Linux man page