Iostream in tornado encapsulates non-blocking Io read/write operations on the socket. I personally think it is interesting to use the read_util () interface: Set a flag string and callback function, the rest of the work can be omitted. When iostream reads the flag string, the callback function is automatically called. The entire interface is user-friendly and convenient.
Attribute:
Self. sockt: encapsulated socket, nonblocking mode;
Self. _ read_buffer: Read cache, collections. deque type, similar to self. _ write_buffer
Self. io_loop: event-driven model, because you need to add/modify the read/write event of F listener D.
Self. _ state: the event-driven model listens to the socket event (read/write/error)
Self. _ read_callback: the callback function to be executed when reading specified bytes of data or specifying a flag string
Self. _ write_callback: the callback function to be executed when _ write_buffer data is sent
Self. _ connect_callback: At this time, self. _ socket (nonblocking) is a client and is sending a request to the server. If the connection is established successfully, the callback function to be executed
Self. _ connecting: At this time, self. _ socket (nonblocking) is a client and is waiting for connection establishment.
External interface:
1. constructor: Initialize the attributes of the iostream instance, and add the socket error event to the ioloop (epoll) listener. The corresponding handler is self. _ handle_events.
def __init__(self, socket, io_loop=None, max_buffer_size=104857600, read_chunk_size=4096): .... self.io_loop.add_handler( self.socket.fileno(), self._handle_events, self._state)
2. Connect function: iostream is used on the client. Note that the connection is established because the socket is nonblocking.
def connect(self, address, callback=None): self._connecting = True try: self.socket.connect(address) except socket.error, e: if e.args[0] not in (errno.EINPROGRESS, errno.EWOULDBLOCK): raise self._connect_callback = stack_context.wrap(callback) self._add_io_state(self.io_loop.WRITE)
3. read_util function: it mainly serves to set the flag string and the corresponding callback function. By the way, scan the current buffer for a flag string and try to read new data from the socket. _ Handle_events is analyzed in detail.
Def read_until (self, delimiter, callback): Assert not self. _ read_callback, "already reading" self. _ read_delimiter = delimiter self. _ read_callback = stack_context.wrap (callback) While true: # see if we 've already got the data from a previous read if self. _ read_from_buffer (): return self. _ check_closed () # HP: Continue to read data into _ read_buffer = 0, indicating eagain errno or closed if self. _ read_to_buffer () = 0: break # HP: callback when will it be executed? handle_events # HP: continue to listen to the socket read event and call ioloop. update_hander () # HP: Self. read_handler = self_handle_events self. _ add_io_state (self. io_loop.read)
4. The Write function is similar to read_util. It mainly serves to set the data to be sent and the callback function to be executed after the data is sent.
Def write (self, Data, callback = none): Self. _ check_closed () self. _ write_buffer.append (data) self. _ add_io_state (self. io_loop.write) # HP: starts listening for the socket write event self. _ write_callback = stack_context.wrap (callback)
5. Other Reading Writing closed operations are simple.
The focus of iostream analysis is: Self. socket's read/write processing function self. _ handle_events
Def _ handle_events (self, FD, events ):... try: if events & self. io_loop.read: Self. _ handle_read () if not self. socket: return if events & self. io_loop.write: If self. _ connecting: # HP: establish a connection with the server self. _ handle_connect () # HP: it calls the hook Self set by connect. _ connect_callback self. _ handle_write () # HP: Write the data in _ write_buffer to the socket. If all data is written, run the callback function if not self. socket: return if events & self. io_loop.error: # HP: epoll Error. Close connection self directly. close () return # HP: update the epoll listener state = self. io_loop.error if self. reading (): # self. _ read_callback is not none state | = self. io_loop.read if self. writing (): state | = self. io_loop.write if state! = Self. _ state: Self. _ state = State self. io_loop.update_handler (self. socket. fileno (), self. _ state) logging T: logging. error ("uncaught exception, closing connection. ", exc_info = true) self. close () raise
When the socket is readable, self. _ handle_read () to process read events, first call _ read_to_buffer to read the data prepared by the protocol stack into the _ read_buffer cache, and then call _ read_from_buffer to analyze the data in the cache, check whether the conditions set by read_util/read_bytes are met.
Def _ handle_read (Self): While true: Try: # Read from the socket until we get ewouldblock or equivalent. # SSL sockets do some internal buffering, and if the data is # Sitting in the SSL object's buffer select () and friends # Can't see it; the only way to find out if it's there is to # Try to read it. result = self. _ read_to_buffer () handle T exception: # HP: exception (ewouldblock/eagain is not counted) self. close () return if result = 0: # HP: closed or eagain break else: # HP: Check whether self exists. _ read_delimiter/self. _ read_callback if self. _ read_from_buffer (): Return
Because the socket is ready to read, _ read_from_socket directly calls Recv () to read data cyclically until the eagain/ewouldblock error occurs or the connection is closed.
Def _ read_to_buffer (Self): Try: Chunk = self. _ read_from_socket () # HP: The Recv () socket is called. error, E: # SSL. sslerror is a subclass of socket. error logging. warning ("read error on % d: % s", self. socket. fileno (), e) Self. close () raise if Chunk is none: # HP: eagain errno or peer has closed return 0 self. _ read_buffer.append (chunk) If self. _ read_buffer_size ()> = self. max_buffer_size: logging. error ("reached maximum read buffer size") self. close () Raise ioerror ("reached maximum read buffer size") return Len (chunk)
_ Read_from_buffer analyzes the cached data to see if the conditions set by read_util/read_bytes are met. If the conditions are met, the corresponding callback function is executed.
Def _ read_from_buffer (Self): If self. _ read_bytes :... elif self. _ read_delimiter: # HP: read_util setting # HP: _ read_buffer is too long. 2 ^ 32. During High concurrency, you are not afraid to squeeze out the memory !!! # HP: equivalent to storing all data on _ read_buffer [0] _ merge_prefix (self. _ read_buffer, sys. maxint) loc = self. _ read_buffer [0]. find (self. _ read_delimiter) If Loc! =-1: callback = self. _ read_callback delimiter_len = Len (self. _ read_delimiter) self. _ read_callback = none self. _ read_delimiter = none self. _ run_callback (callback, # HP: _ consume returns the data self that includes delimiter before delimiter. _ consume (loc + delimiter_len) return true return false
The _ merge_prefix (deque, size) function is interesting. It places the first size bytes of data in deque to the first position of deque. However, I personally think that this will frequently allocate and release memory, which affects performance. The advantage is that it is too convenient to manually write a cache processing structure.
In general, iostream uses the deque container and the _ merge_prefix (deque, size) function to complete the cache and reorganization of non-blocking Io read data, combined with the read_util function, it is good to encapsulate non-blocking Io shard data processing in the iostream class, and the upper layer can easily process socket read/write operations.