python basis: Detailed usage in Python Twisted framework reactor Event Manager

This article describes the event manager in the reactor Detailed usage of Python's Twisted framework, Twisted is an asynchronous Python development framework a high popularity, a friend in need can under reference
bedding
in a lot of practice, it seems that we are always in a similar way to use asynchronous programming:

Monitor events
events executes the corresponding callback function
callback is completed (possible new events added to the listen queue)
back to 1, listen for events
so we will this asynchronous mode is called the Reactor pattern, such as Run Loop concept in iOS Development, in fact, very similar to the Reactor loop, Run Loop main thread monitor screen UI event, once the UI event handling code corresponding to the event is executed, can also generate an event to the main thread execution by other means GCD. Here Insert Picture Description
The figure is a depiction of Reactor boost mode, Twisted design is based on the Reactor pattern, Twisted program is the continuous loop while waiting for the event, handling events.

from twisted.internet import reactor
reactor.run()

Twisted reactor is a singleton object program.

the Reactor
the Reactor is the event manager for registration, cancellation of the event, running event loop, call the callback function to handle when an event occurs. There are several conclusions about the reactor the following:

Twisted's reactor only be started by calling reactor.run ().
loop reactor is running at the beginning of its process, which is running in the primary process.
Once started, it will run forever. reactor will be under control of a program (or in particular under the control of a promoter of its thread).
reactor cycle and does not consume any CPU resources.
You do not need to explicitly create a reactor, just introduced on OK.
Finally, a need to explain. In Twisted in, reactor is Singleton (ie singleton), that there is only one program in a reactor, and as long as you introduce it accordingly to create one. This embodiment is a method of introducing the above-twisted default, of course, there are other methods twisted the reactor can be introduced. For example, instead poll call to select the method twisted.internet.pollreactor system.

To use another reactor, it needs to be installed prior to introduction twisted.internet.reactor. The following is a method of mounting pollreactor:

from twisted.internet import pollreactor
pollreactor.install()

If you do not have to install other special reactor and introduced twisted.internet.reactor, then Twisted will be based on the operating system install the default reactor. Because of this, customary practice not to introduce reactor in the topmost module to avoid installing the default reactor, but is mounted in the region you want to use the reactor.
Here is the rewritable pollreactor above procedure:

from twited.internet import pollreactor
pollreactor.install()
from twisted.internet import reactor
reactor.run()

Then the reactor is how to achieve a single case? Look from twisted.internet import reactor do what things and understand.

The following is a part of the code twisted / internet / reactor.py of:

# twisted/internet/reactor.py
import sys
del sys.modules['twisted.internet.reactor']
from twisted.internet import default
default.install()

Note: Python all loaded into memory modules are placed sys.modules, it is a global dictionary. Will first look when import a module in the list is already loaded this module, if the load is simply added to the name of the module is calling import module namespace. If not loaded from sys.path directory to find the name of the module by module file, find the module will be loaded into memory and added to the sys.modules, and the name into the current namespace.

If we are first run from twisted.internet import reactor, because sys.modules has not twisted.internet.reactor, it will run the code reactory.py, install the default reactor. After that, if you import it, because sys.modules in the module already exists, it will directly sys.modules in twisted.internet.reactor into the current namespace.

The default install:

注:Python中所有加载到内存的模块都放在sys.modules,它是一个全局字典。当import一个模块时首先会在这个列表中查找是否已经加载了此模块,如果加载了则只是将模块的名字加入到正在调用import的模块的命名空间中。如果没有加载则从sys.path目录中按照模块名称查找模块文件,找到后将模块载入内存,并加入到sys.modules中,并将名称导入到当前的命名空间中。

假如我们是第一次运行from twisted.internet import reactor,因为sys.modules中还没有twisted.internet.reactor,所以会运行reactory.py中的代码,安装默认的reactor。之后,如果导入的话,因为sys.modules中已存在该模块,所以会直接将sys.modules中的twisted.internet.reactor导入到当前命名空间。

default中的install:

Obviously, default will get the appropriate install depending on the platform. Under Linux will be the first to use epollreactor, if the kernel does not support, you can only use pollreactor. Mac platform pollreactor, windows use selectreactor. Each install is almost achieved, where we extract the install selectreactor to look at.

# twisted/internet/selectreactor.py:
def install():
  """Configure the twisted mainloop to be run using the select() reactor.
  """
  # 单例
  reactor = SelectReactor()
  from twisted.internet.main import installReactor
  installReactor(reactor)

# twisted/internet/main.py:
def installReactor(reactor):
  """
  Install reactor C{reactor}.

  @param reactor: An object that provides one or more IReactor* interfaces.
  """
  # this stuff should be common to all reactors.
  import twisted.internet
  import sys
  if 'twisted.internet.reactor' in sys.modules:
    raise error.ReactorAlreadyInstalledError("reactor already installed")
  twisted.internet.reactor = reactor
  sys.modules['twisted.internet.reactor'] = reactor

In installReactor added twisted.internet.reactor key to sys.modules, singleton reactor value is created and then install. To be used later reactor, this will be introduced into a single embodiment.

SelectReactor
# twisted/internet/selectreactor.py
@implementer(IReactorFDSet)
class SelectReactor(posixbase.PosixReactorBase, _extraBase)

implementer expressed SelectReactor implements methods IReactorFDSet interface used here zope.interface, it is the interface in python, interested students can go to the next.
IReactorFDSet interfaces mainly acquisition descriptor, add, or delete method of operation. These methods will be able to see the name to know the meaning, so I did not add a comment.

# twisted/internet/interfaces.py
class IReactorFDSet(Interface):

  def addReader(reader):

  def addWriter(writer):

  def removeReader(reader):

  def removeWriter(writer):

  def removeAll():

  def getReaders():

  def getWriters():
reactor.listenTCP()

The example in reactor.listenTCP () registered a listener event, it is the parent class PosixReactorBase methods.

# twisted/internet/posixbase.py
@implementer(IReactorTCP, IReactorUDP, IReactorMulticast)
class PosixReactorBase(_SignalReactorMixin, _DisconnectSelectableMixin,
            ReactorBase):

  def listenTCP(self, port, factory, backlog=50, interface=''):
    p = tcp.Port(port, factory, backlog, interface, self)
    p.startListening()
    return p

# twisted/internet/tcp.py
@implementer(interfaces.IListeningPort)
class Port(base.BasePort, _SocketCloser):
  def __init__(self, port, factory, backlog=50, interface='', reactor=None):
    """Initialize with a numeric port to listen on.
    """
    base.BasePort.__init__(self, reactor=reactor)
    self.port = port
    self.factory = factory
    self.backlog = backlog
    if abstract.isIPv6Address(interface):
      self.addressFamily = socket.AF_INET6
      self._addressType = address.IPv6Address
    self.interface = interface
  ...

  def startListening(self):
    """Create and bind my socket, and begin listening on it.
     创建并绑定套接字,开始监听。

    This is called on unserialization, and must be called after creating a
    server to begin listening on the specified port.
    """
    if self._preexistingSocket is None:
      # Create a new socket and make it listen
      try:
        # 创建套接字
        skt = self.createInternetSocket()
        if self.addressFamily == socket.AF_INET6:
          addr = _resolveIPv6(self.interface, self.port)
        else:
          addr = (self.interface, self.port)
        # 绑定
        skt.bind(addr)
      except socket.error as le:
        raise CannotListenError(self.interface, self.port, le)
      # 监听
      skt.listen(self.backlog)
    else:
      # Re-use the externally specified socket
      skt = self._preexistingSocket
      self._preexistingSocket = None
      # Avoid shutting it down at the end.
      self._shouldShutdown = False

    # Make sure that if we listened on port 0, we update that to
    # reflect what the OS actually assigned us.
    self._realPortNumber = skt.getsockname()[1]

    log.msg("%s starting on %s" % (
        self._getLogPrefix(self.factory), self._realPortNumber))

    # The order of the next 5 lines is kind of bizarre. If no one
    # can explain it, perhaps we should re-arrange them.
    self.factory.doStart()
    self.connected = True
    self.socket = skt
    self.fileno = self.socket.fileno
    self.numberAccepts = 100

    # startReading调用reactor的addReader方法将Port加入读集合
    self.startReading()

The whole logic is simple, and the server-side as normal, creates a socket, bind, listen. Except that socket descriptor added to the read set of reactor. So if the connection with the client over the words, reactor will be monitored, then the event handler is triggered.

reacotr.run () main event loop

# twisted/internet/posixbase.py
@implementer(IReactorTCP, IReactorUDP, IReactorMulticast)
class PosixReactorBase(_SignalReactorMixin, _DisconnectSelectableMixin,
            ReactorBase)

# twisted/internet/base.py
class _SignalReactorMixin(object):

  def startRunning(self, installSignalHandlers=True):
    """
    PosixReactorBase的父类_SignalReactorMixin和ReactorBase都有该函数,但是
    _SignalReactorMixin在前,安装mro顺序的话,会先调用_SignalReactorMixin中的。
    """
    self._installSignalHandlers = installSignalHandlers
    ReactorBase.startRunning(self)

  def run(self, installSignalHandlers=True):
    self.startRunning(installSignalHandlers=installSignalHandlers)
    self.mainLoop()

  def mainLoop(self):
    while self._started:
      try:
        while self._started:
          # Advance simulation time in delayed event
          # processors.
          self.runUntilCurrent()
          t2 = self.timeout()
          t = self.running and t2
          # doIteration是关键,select,poll,epool实现各有不同
          self.doIteration(t)
      except:
        log.msg("Unexpected error in main loop.")
        log.err()
      else:
        log.msg('Main loop terminated.')

mianLoop the main loop is the ultimate in recycling, call doIteration method for monitoring a set of descriptors to read and write, if found to have descriptors are ready to read and write, then calls the event handler.

# twisted/internet/selectreactor.py
@implementer(IReactorFDSet)
class SelectReactor(posixbase.PosixReactorBase, _extraBase):

  def __init__(self):
    """
    Initialize file descriptor tracking dictionaries and the base class.
    """
    self._reads = set()
    self._writes = set()
    posixbase.PosixReactorBase.__init__(self)

  def doSelect(self, timeout):
    """
    Run one iteration of the I/O monitor loop.

    This will run all selectables who had input or output readiness
    waiting for them.
    """
    try:
      # 调用select方法监控读写集合,返回准备好读写的描述符
      r, w, ignored = _select(self._reads,
                  self._writes,
                  [], timeout)
    except ValueError:
      # Possibly a file descriptor has gone negative?
      self._preenDescriptors()
      return
    except TypeError:
      # Something *totally* invalid (object w/o fileno, non-integral
      # result) was passed
      log.err()
      self._preenDescriptors()
      return
    except (select.error, socket.error, IOError) as se:
      # select(2) encountered an error, perhaps while calling the fileno()
      # method of a socket. (Python 2.6 socket.error is an IOError
      # subclass, but on Python 2.5 and earlier it is not.)
      if se.args[0] in (0, 2):
        # windows does this if it got an empty list
        if (not self._reads) and (not self._writes):
          return
        else:
          raise
      elif se.args[0] == EINTR:
        return
      elif se.args[0] == EBADF:
        self._preenDescriptors()
        return
      else:
        # OK, I really don't know what's going on. Blow up.
        raise

    _drdw = self._doReadOrWrite
    _logrun = log.callWithLogger
    for selectables, method, fdset in ((r, "doRead", self._reads),
                      (w,"doWrite", self._writes)):
      for selectable in selectables:
        # if this was disconnected in another thread, kill it.
        # ^^^^ --- what the !@#*? serious! -exarkun
        if selectable not in fdset:
          continue
        # This for pausing input when we're not ready for more.

        # 调用_doReadOrWrite方法
        _logrun(selectable, _drdw, selectable, method)

  doIteration = doSelect

  def _doReadOrWrite(self, selectable, method):
    try:
      # 调用method,doRead或者是doWrite,
      # 这里的selectable可能是我们监听的tcp.Port
      why = getattr(selectable, method)()
    except:
      why = sys.exc_info()[1]
      log.err()
    if why:
      self._disconnectSelectable(selectable, why, method=="doRead")

Then the client if the connection request, it will call the set tcp.Port doRead method of reading.

# twisted/internet/tcp.py

@implementer(interfaces.IListeningPort)
class Port(base.BasePort, _SocketCloser):

  def doRead(self):
    """Called when my socket is ready for reading.
    当套接字准备好读的时候调用

    This accepts a connection and calls self.protocol() to handle the
    wire-level protocol.
    """
    try:
      if platformType == "posix":
        numAccepts = self.numberAccepts
      else:
        numAccepts = 1
      for i in range(numAccepts):
        if self.disconnecting:
          return
        try:
          # 调用accept
          skt, addr = self.socket.accept()
        except socket.error as e:
          if e.args[0] in (EWOULDBLOCK, EAGAIN):
            self.numberAccepts = i
            break
          elif e.args[0] == EPERM:
            continue
          elif e.args[0] in (EMFILE, ENOBUFS, ENFILE, ENOMEM, ECONNABORTED):
            log.msg("Could not accept new connection (%s)" % (
              errorcode[e.args[0]],))
            break
          raise

        fdesc._setCloseOnExec(skt.fileno())
        protocol = self.factory.buildProtocol(self._buildAddr(addr))
        if protocol is None:
          skt.close()
          continue
        s = self.sessionno
        self.sessionno = s+1
        # transport初始化的过程中,会将自身假如到reactor的读集合中,那么当它准备
        # 好读的时候,就可以调用它的doRead方法读取客户端发过来的数据了
        transport = self.transport(skt, protocol, addr, self, s, self.reactor)
        protocol.makeConnection(transport)
      else:
        self.numberAccepts = self.numberAccepts+20
    except:
      log.deferr()

doRead method, for receiving a call accept generating client data socket, transport the socket and bind, transport and then added to the reactor of the read set. When a client has data arrives, it will call doRead transport method for data read.

Connection is Server (transport instance of the class) of the parent class, which implements the method doRead.

# twisted/internet/tcp.py
@implementer(interfaces.ITCPTransport, interfaces.ISystemHandle)
class Connection(_TLSConnectionMixin, abstract.FileDescriptor, _SocketCloser,
         _AbortingMixin):

  def doRead(self):
    try:
      # 接收数据
      data = self.socket.recv(self.bufferSize)
    except socket.error as se:
      if se.args[0] == EWOULDBLOCK:
        return
      else:
        return main.CONNECTION_LOST

    return self._dataReceived(data)

  def _dataReceived(self, data):
    if not data:
      return main.CONNECTION_DONE
    # 调用我们自定义protocol的dataReceived方法处理数据
    rval = self.protocol.dataReceived(data)
    if rval is not None:
      offender = self.protocol.dataReceived
      warningFormat = (
        'Returning a value other than None from %(fqpn)s is '
        'deprecated since %(version)s.')
      warningString = deprecate.getDeprecationWarningString(
        offender, versions.Version('Twisted', 11, 0, 0),
        format=warningFormat)
      deprecate.warnAboutFunction(offender, warningString)
    return rval

_dataReceived call a method of processing data dataReceived example EchoProtocol of our custom.

I write to you, for everyone to recommend a very wide python learning resource gathering, click to enter , there is a senior programmer before learning to share

Experience, study notes, there is a chance of business experience, and for everyone to carefully organize a python to combat zero-based item of information,

Python day to you on the latest technology, prospects, learning small details that need to comment on
this point, a simple process, from creation to listen to events, to the receiving client data thus concluded

Published 47 original articles · won praise 34 · views 60000 +

Guess you like

Origin blog.csdn.net/haoxun08/article/details/104887218