Iostream has a great effect on the efficiency of tornado, which encapsulates the socket's non-blocking IO read and write operations. Generally speaking, when the connection is established, the server and client request response is based on iostream, that is to say: iostream is used to handle the connection.
Now let's talk about the general flow of receiving requests:
When the connection is established, the server generates a socket to be connected and encapsulates the socket into a iostream instance (which represents the initialization of iostream).
We know that Tornado is based on Io multiplexing (take epoll for example), at this point the socket is register, the event is readable, this step is not much related to iostream.
When the socket event occurs, which means that there is data sent from the connection to the system buffer, it is necessary to read the chunk into the _read_buffer that we have opened in memory and use deque as buffer in iostream. _read_buffer is a read buffer, of course, there are _write_buffer, but whether it is read buffer or write buffer is essentially the tornado process open up a section of memory used to store data.
These chunk are generally the requests sent by the client, but we also need to do further work on these chunk, such as the chunk may contain multiple requests, how to separate the request? (The Terminator for each request header is B ' \r\n\r\n '), where the read_until is used to separate the request and set the callback. The detached request data is removed from the _read_buffer.
It then adds callback and his parameters (separated request data) to Ioloop._callbacks, waiting for the next ioloop to execute, iterating over the _callbacks and executing the callback function.
Add: Tornado is a horizontal trigger, so if there is still data in the system buffers after reading the chunk, the next epoll.poll () will still return the socket.
In IOStream, there is a class called: IOStream
There are several more important properties:
Def __init__ (): self.socket = Socket # package Socket self.socket.setblocking (False) # set socket as non-blocking Self.io_loop = Io_loop or Ioloop. Ioloop.current () self._read_buffer = Deque () # read buffer Self._write_buffer = deque () # Write buffer Self._read _callback = None # reads to the specified byte data, or when the flag string is specified, the callback function that needs to be executed self._write_callback = None # When the data is sent out _write_buffer Callback functions that need to be executed
There are several more important ways to
Class IOStream (object): def read_until (self, delimiter, callback): def read_bytes (self, num_bytes, callback, Streaming_callback=none): def read_until_regex (self, Regex, callback): def read_until_close (self, callback, Streaming_callback=none): def write (self, data, Callback=none):
All of the above methods require an optional callback parameter, and if the parameter is none then the method returns a future object.
All of the above reading methods essentially read the data sent by the socket, then stop reading when the specified delimiter or tag is read, and then use the delimiter and the data in front of it as callback (if there is no callback, Set the data to the result of the future object), and then add the callback to Ioloop._callbacks. Of course all of these "read" operations are non-blocking!
For the most common Read_until method, here is the Code lite:
def read_until (self, delimiter, Callback=none, Max_bytes=none): the Future = Self._set_read_callback (callback) # It could be a future object or none Self._read_delimiter = delimiter # set delimiter self._read_max_bytes = max_bytes # Set maximum read bytes self._try_inline_read () return future
Where _set_read_callback returns None or a future object based on whether the callback exists (returns NONE, otherwise returns a future instance object)
If we
Then look at the simplified version of the _try_inline_read method:
def _try_inline_read (self): "" "try to read the required data from _read_buffer" "" # to see if we've got the data in the previous read operation Self._run_streaming_callback () # character Stream callback, generally read operation is not fully read enough and in the streaming state, the general default is none, if the call Read_bytes and Read_until_ Close and specifying the Streaming_callback parameter will cause this callback pos = Self._find_read_pos () # to try to locate the delimiter in _read_buffer. If found, returns the position at the end of the delimiter, or none if not. If POS is not None:self._read_from_buffer (POS) return
Self._check_closed () # checks whether the current iostream closes pos = Self._read_to_buffer_loop () # reads a chunk from the system buffer, checks for delimiters, no Continue reading a chunk, merge two chunk, check again for function separators ... If a delimiter is found, it returns the position at the end of the delimiter in _read_buffer if POS is not None: # If a delimiter is found, Self._read_from_buff ER (POS) # Removes the required data from the _read_buffer and takes it as a callback parameter and then adds callback to Ioloop._callbacks return
# no delimiter found, either close iostream, or register event for the socket in Ioloop if self.closed (): Self._maybe_run_close_callback () Else:self._add_io_state (Ioloop. Ioloop.read)
The above code is divided into three parts by my blank line, each of which corresponds to each of the following words
Analyze the method:
1 First find the delimiter in the first item of _read_buffer, find the delimiter and its previous data removed from the _read_buffer and passed it as a parameter to the callback function, not found the second item with the first merge and then continue to find ... ;
2 If it is not found in all _read_buffer, read the data in the system cache to _read_buffer, then merge again to find,
3 If the system cache data are not found, then wait for the next time the socket read event after the search, then the search is: the system cache data read into the _read_buffer and then find, that is, to perform the 2nd step.
Let's take a look at what the three parts call each method:
_find_read_pos and _read_from_buffer in the first part
The former is mainly to find the delimiter in _read_buffer, and return the position of the delimiter, the latter is the delimiter and all the data before the delimiter is removed from the _read_buffer and as a callback parameter, Then add the callback package to the Ioloop._callbacks
See the simplified version of the _find_read_pos method:
_find_read_pos _read_from_buffer _run_read_callback
It also uses a very interesting function: _merge_prefix, this function is to adjust the first item of deque to a specified size
_merge_prefix
The second part of the _read_to_buffer_loop
_read_to_buffer_loop
The third part _add_io_state, the function and Ioloop asynchronous correlation
_add_io_state
Reference:
http://www.nowamagic.net/academy/detail/13321051
In-depth iostream in tornado