selfboot / AnnotatedShadowSocks

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

Details about select.select() #11

Open selfboot opened 7 years ago

selfboot commented 7 years ago

Python’s select.select() function is a direct interface to the underlying operating system implementation. It monitors sockets, open files, and pipes (anything with a fileno() method that returns a valid file descriptor) until they become readable or writable, or a communication error occurs.

Note that on Windows, it only works for sockets. on other operating systems, it also works for other file types (in particular, on Unix, it works on pipes).

Select Echo Server

The socket echo server example from the #13 can be extended to watch for more than one connection at a time by using select().

  1. Creating a non-blocking TCP/IP socket and configuring it to listen on an address.

  2. Set up the lists containing input sources and output destinations to be passed to select(). The arguments to select() are three lists containing communication channels to monitor. The first is a list of the objects to be checked for incoming data to be read, the second contains objects that will receive outgoing data when there is room in their buffer, and the third those that may have an error (usually a combination of the input and output channel objects).

  3. The main portion of the server program loops, calling select() to block and wait for network activity.

  4. select() returns three new lists, containing subsets of the contents of the lists passed in.

  5. All of the sockets in the readable list have incoming data buffered and available to be read. The “readable” sockets represent three possible cases.

    • If the socket is the main “server” socket, the one being used to listen for connections, then the “readable” condition means it is ready to accept another incoming connection. In addition to adding the new connection to the list of inputs to monitor, this section sets the client socket to not block.
    • The next case is an established connection with a client that has sent data. The data is read with recv(), then placed on the queue so it can be sent through the socket and back to the client.
    • A readable socket without data available is from a client that has disconnected, and the stream is ready to be closed.
  6. All of the sockets in the writable list have free space in their buffer and can be written to. There are fewer cases for the writable connections. If there is data in the queue for a connection, the next message is sent. Otherwise, the connection is removed from the list of output connections so that the next time through the loop select() does not indicate that the socket is ready to send data.

  7. Finally, if there is an error with a socket, it is closed.

server.py is here. (click to unfold) ```python import select import socket import sys import Queue # 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 connetions server.listen(5) # Sockets from which we except to read. inputs = [server] # Sockets to which we except to write. outputs = [] # Outgoing message queues (socket:Queue) message_queues = {} while inputs: # Wait for at least one of the sockets to be ready for processing print >> sys.stderr, '\nWaiting for the next event' readable, writable, exceptional = select.select(inputs, outputs, inputs) # Handle inputs for s in readable: if s is server: # A 'readable' server socket is ready to accept a connection connection, client_address = s.accept() print >> sys.stderr, 'new connection from', client_address connection.setblocking(0) inputs.append(connection) # Give the connection a queue for data we want to send message_queues[connection] = Queue.Queue() else: data = s.recv(1024) if data: # A readable client socket has data print >> sys.stderr, 'received "%s" from %s' % (data, s.getpeername()) message_queues[s].put(data) # Add output channel for response if s not in outputs: outputs.append(s) else: # Interpret empty result as closed connection print >> sys.stderr, 'closing', client_address, 'after reading no data' # Stop lestening for input on the connection if s in outputs: outputs.remove(s) inputs.remove(s) s.close() # Remove message queue del message_queues[s] for s in writable: 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' outputs.remove(s) else: print >> sys.stderr, 'sending "%s" to %s' % (next_msg, s.getpeername()) s.send(next_msg) # If there is an error with a socket, it is closed. for s in exceptional: print >> sys.stderr, 'handling exceptional condition for', s.getpeername() # Stop listening for input on the connection inputs.remove(s) if s in outputs: outputs.remove(s) s.close() # Remove message queue del message_queues[s] ```

Select Echo Client

The example client program uses two sockets to demonstrate how the server with select() manages multiple connections at the same time. The client starts by connecting each TCP/IP socket to the server.

client.py is here. (click to unfold) ```python #! /usr/bin/env python # -*- coding: utf-8 -*- import socket import sys messages = ['This is the message.', 'It will be sent', 'in parts'] server_address = ('localhost', 10000) # Create a TCP/IP socket socks = [socket.socket(socket.AF_INET, socket.SOCK_STREAM), socket.socket(socket.AF_INET, socket.SOCK_STREAM)] # Connect the socket to the port where the server is listening print >>sys.stderr, 'connecting to %s port %s' % server_address for s in socks: s.connect(server_address) for message in messages: # Send message on both sockets for s in socks: print >>sys.stderr, '%s: sending "%s"' % (s.getsockname(), message) s.send(message) # Read response on both sockets for s in socks: data = s.recv(1024) print >>sys.stderr, '%s: received "%s"' % (s.getsockname(), data) if not data: print >> sys.stderr, 'closing socket', s.getsockname() s.close() ```

Ref:
select – Wait for I/O Efficiently