selfboot / AnnotatedShadowSocks

Annotated shadowsocks(python version)
Other
3 stars 1 forks source link

Details about select.epoll() #16

Open selfboot opened 7 years ago

selfboot commented 7 years ago

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:

  1. Create an epoll object
  2. Tell the epoll object to monitor specific events on specific sockets
  3. Ask the epoll object which sockets may have had the specified event since the last query
  4. Perform some action on those sockets
  5. Tell the epoll object to modify the list of sockets and/or events to monitor
  6. Repeat steps 3 through 5 until finished
  7. 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.

Ref
How To Use Linux epoll with Python
IO Model
epoll(4) - Linux man page