ryu源码阅读(一)

资料

  • ryu官方文档
    • 英文资料可以从官方文档中的各个资源链接深入
  • ryu的python文档
    • 既可了解整体架构,又可查询详细方法和参数
  • ryubook
    • 主要讲了ryu使用案例,路由器防火墙等等
  • sdnlab
    • sdnlab是比较好的中文资料来源,里面有很多源码解读、操作使用上的文章

起因

  • 在学校做网络安全或者软件定义网络等领域的研究时,并不太关心所使用的sdn控制器的水平扩展、对数据中心的支持等
  • 更多的场景就是几台甚至一台主机的ovs上对理论模型进行实验仿真,得到仿真结果后就可以写在论文里了
  • 我这次遇到的场景是需要在软件交换机ovs中做802.1x认证者的逻辑,不涉及什么创新
  • 但还是想把过程中对ryu的学习进行总结,后面如果再用的话,争取现在留下的文章能够让我快速重新捡起来
  • 本篇初探主要内容是ryu主线源码解读ryu的设计很优美,但如果不去读源码的话,很多约定会觉得像魔法
  • 此外ryu并发模型使用的协程,导致不读源码的话,编写的app如果使用多线程,调试的时候也可能会一头雾水

功能

  • 在读ryu源码前,可以想象一下sdn控制器要做的事情都有哪些
  • 带着ryu如何实现这些功能的疑问去读源码会更加清晰
  • 我认为的几个主要的功能点:
    1. 通过openflow协议管理多个交换机
    2. 通过netconf协议管理多个交换机
    3. 可选支持ovsdb等协议完善控制能力
    4. 支持用户编写数据包处理逻辑,并提供合适的框架简化代码
    5. 可以同时管理运行用户编写的多个sdn应用,并使其协作
    6. 最好能对http restful api提供框架支持
    7. 统一的网络并发模型管理上述涉及到的网络通信
    • 对接交换机
    • 对接restapi调用者
    • 多应用之间进行内部通信协作
    • 通信方式扩展,如unix socket、AMPQ协议等
  1. 可选实现一些数据中心常见的控制协议,比如bgp

阅读

一级文件目录

╭─root@testdevice ~/ryu ‹master›
╰─# tree -L 1
.
├── CONTRIBUTING.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── bin
├── debian
├── doc
├── etc
├── run_tests.sh
├── ryu
├── setup.cfg
├── setup.py
├── tools
└── tox.ini
  • 先来看文件目录,几个rst我们不用关心
  • bin文件夹里面是ryu用来启动的可执行脚本
from ryu.cmd.manager import main
main()
  • 可以看下ryu-manager启动脚本内容,除了开源许可注释,只有两行,从ryu包的cmd.manager导入了main函数并执行
  • 因此我们也就知道了,我们通过ryu-manager --verbose myapp.py命令启动的控制器实例,是由cmd.manager.main开始的
  • bin文件夹中还有一个ryu可执行脚本,允许通过run的subcommand执行ryu-manager的逻辑ryu run myapp.py
    • 此外ryu命令通过调用yu.cmd路径下的其他函数,实现了[run|of-config-cli|rpc-cli]这三个子命令

ryu二级目录

╭─root@testdevice ~/ryu/ryu ‹master›
╰─# tree -L 1
.
├── __init__.py
├── app
├── base
├── cfg.py
├── cmd
├── contrib
├── controller
├── exception.py
├── flags.py
├── hooks.py
├── lib
├── log.py
├── ofproto
├── services
├── tests
├── topology
└── utils.py

10 directories, 7 files
  • 可以看到文件内容不少,但都是实现ryu控制器的核心逻辑代码
  • 接下来我们继续跟启动的主线

启动主线

ryu/ryu/cmd/manager.py

包导入

    19  import os
    20  import sys
    21
    22  from ryu.lib import hub
    23  hub.patch(thread=False)
    24
    25  from ryu import cfg
    26
    27  import logging
    28  from ryu import log
    29  log.early_init_log(logging.DEBUG)
    30
    31  from ryu import flags
    32  from ryu import version
    33  from ryu.app import wsgi
    34  from ryu.base.app_manager import AppManager
    35  from ryu.controller import controller
    36  from ryu.topology import switches
  • 注意在入口文件的第四行就已经通过ryu.lib.hub引入了协程库,并对除线程以外的库进行了patch
    • patch即为将目标模块,替换为协程形式的实现,但实现的并不完美,有一些地方会导致bug
  • 中间几行导入了配置管理和日志管理模块
  • 最后几行引入了
    • wsgi:用于提供web服务
    • AppManager:管理多app
    • controller:openflow控制器,使其能够控制交换机的核心功能
    • switches:未知!!!

配置管理

    37
    38
    39  CONF = cfg.CONF
    40  CONF.register_cli_opts([
    41      cfg.ListOpt('app-lists', default=[],
    42                  help='application module name to run'),
    43      cfg.MultiStrOpt('app', positional=True, default=[],
    44                      help='application module name to run'),
    45      cfg.StrOpt('pid-file', default=None, help='pid file name'),
    46      cfg.BoolOpt('enable-debugger', default=False,
    47                  help='don\'t overwrite Python standard threading library'
    48                  '(use only for debugging)'),
    49      cfg.StrOpt('user-flags', default=None,
    50                 help='Additional flags file for user applications'),
    51  ])
    52
    53
    54  def _parse_user_flags():
    55      """
    56      Parses user-flags file and loads it to register user defined options.
    57      """
    58      try:
    59          idx = list(sys.argv).index('--user-flags')
    60          user_flags_file = sys.argv[idx + 1]
    61      except (ValueError, IndexError):
    62          user_flags_file = ''
    63
    64      if user_flags_file and os.path.isfile(user_flags_file):
    65          from ryu.utils import _import_module_file
    66          _import_module_file(user_flags_file)
    67
    68
    69  def main(args=None, prog=None):
    70      _parse_user_flags()
    71      try:
    72          CONF(args=args, prog=prog,
    73               project='ryu', version='ryu-manager %s' % version,
    74               default_config_files=['/usr/local/etc/ryu/ryu.conf'])
    75      except cfg.ConfigFilesNotFoundError:
    76          CONF(args=args, prog=prog,
    77               project='ryu', version='ryu-manager %s' % version)
    78
    79      log.init_log()
    80      logger = logging.getLogger(__name__)
    81
  • 配置管理和日志管理不是本篇重点,就不赘述
  • 思考了一下还是把完整的内容贴在这里,让以后的我和读者有个直观的感受
  • 因为我在读其他人的分析源码的的文章的时候,经常出现的疑问就是,这段代码在哪?

核心逻辑

    82      if CONF.enable_debugger:
    83          msg = 'debugging is available (--enable-debugger option is turned on)'
    84          logger.info(msg)
    85      else:
    86          hub.patch(thread=True)
    87
    88      if CONF.pid_file:
    89          with open(CONF.pid_file, 'w') as pid_file:
    90              pid_file.write(str(os.getpid()))
    91
    92      app_lists = CONF.app_lists + CONF.app
    93      # keep old behavior, run ofp if no application is specified.
    94      if not app_lists:
    95          app_lists = ['ryu.controller.ofp_handler']
    96
    97      app_mgr = AppManager.get_instance()
    98      app_mgr.load_apps(app_lists)
    99      contexts = app_mgr.create_contexts()
   100      services = []
   101      services.extend(app_mgr.instantiate_apps(**contexts))
   102
   103      webapp = wsgi.start_service(app_mgr)
   104      if webapp:
   105          thr = hub.spawn(webapp)
   106          services.append(thr)
   107
   108      try:
   109          hub.joinall(services)
   110      except KeyboardInterrupt:
   111          logger.debug("Keyboard Interrupt received. "
   112                       "Closing RYU application manager...")
   113      finally:
   114          app_mgr.close()
   115
   116
   117  if __name__ == "__main__":
   118      main()
  • 首先一个比较有意思的是82行会对是否启动debugger调试模式进行判断,如果启动了的话,就不会对线程进行协程patch
    • 因此如果我们写的app代码调用的库不得不调用多线程时,可以尝试开启debugger,看情况是否有所变化
  • 接下来92到95行,读取配置文件或命令行中指定的app列表,如果没有指定,那么会运行ryu.controller.ofp_handler
    • ryu.controller.ofp_handler就是控制器与交换机进行openflow协议交互的核心app
    • 那么如果我们指定了自己的app,就不会运行ryu.controller.ofp_handler这样核心的逻辑了吗?
    • 后面我们会发现,就算app_lists不是空,我们依旧会在运行自己指定app的同时,运行起来ryu.controller.ofp_handler
    • 这就涉及到了ryu对app的依赖管理和导入机制的设计,如果不看源码可能会很膜法
  • 下面97行,get_instance可以猜个大概app_manager使用的单例模式
  • 98行,调用manager的load_apps函数把要运行的app的py代码都动态的加载进内存
  • 99行创建并初始化了应用上下文,后面会详细说明
    • 这里可以简单理解为我们编写的app以来的其他的库
    • ryu会为我们初始化好,并传入供我们的app调用
    • context是个字典,注意相同名字的依赖会只初始化一次,可以存在多app都调用同一个context的情况,因此也可作为多app共享内容的方式之一
  • 100-101行,services是一个列表,存储了app_manager初始化所有app后返回的内容
  • 103-106行,wsgi启动了他自己的服务,
    • 如果初始化成功,会通过hub.spawn注册进协程管理
    • 并将返回结果append到了services里
    • 由此我们可以理解,services里其实都是hub.spawn返回的每个协程的句柄
  • 最后的108行开始
    • 尝试hub.joinall等待所有的协程实例运行结束
    • 如果遇到了ctrl+c,或者遇到异常,无论如何调用app_manager的close方法进行清理
    • app_manager作为app们的管理者,同样会调用各app的close方法,完成树状的资源清理
  • 92行到114行,这短短的二十多行就是ryu运行控制器核心逻辑的树根,后面的一切逻辑上的树枝,都由此而来
    • 其中的逻辑又可主要分为几大块:
      • app读取、拉起,涉及到app间的依赖管理
      • app对其他类实例的依赖,涉及到app对其他类的依赖
      • wsgi对app中涉及restful应用的管理
      • 协程贯穿在ryu中的使用
    • 接下来我们就对其逐一进行分析

app依赖管理

  • 启动主线中的app_manager是从ryu/ryu/base/app_manager.py中实例化的
  • 而base这个文件夹中,只有一个app_manager.py,可见其重要性
  • 其中不止实现了app管理器,还规定了了我们编写的app需要继承的基类

ryu/ryu/base/app_manager.py

包导入

    26  import inspect
    27  import itertools
    28  import logging
    29  import sys
    30  import os
    31  import gc
    32
    33  from ryu import cfg
    34  from ryu import utils
    35  from ryu.app import wsgi
    36  from ryu.controller.handler import register_instance, get_dependent_services
    37  from ryu.controller.controller import Datapath
    38  from ryu.controller import event
    39  from ryu.controller.event import EventRequestBase, EventReplyBase
    40  from ryu.lib import hub
    41  from ryu.ofproto import ofproto_protocol
    42
    43  LOG = logging.getLogger('ryu.base.app_manager')
    44
    45  SERVICE_BRICKS = {}
  • app_manager引入的内容与刚刚cmd中的manager类似
  • 需要注意37行导入了datapath,用于管理网桥的库
  • 以及38-39行引入的ryu的事件管理库
  • 42行进行了日志配置
  • 45行文件全局变量SERVICE_BRICKS这个字典,是一个app名称映射到app实例的字典
    • 但为什么不叫app,突然改叫brick,是阅读过程中比较困惑的地方
    • 猜测是app在event管理路由这个场景就给他换了个名字
    • 后面会多处出现对这个字典的操作

app管理器

  • 注意45行后到351行间是app基类与工具方法,此处跳跃进行介绍
   351  class AppManager(object):
   352      # singleton
   353      _instance = None
   354
   355      @staticmethod
   356      def run_apps(app_lists):
   357          """Run a set of Ryu applications
   358
   359          A convenient method to load and instantiate apps.
   360          This blocks until all relevant apps stop.
   361          """
   362          app_mgr = AppManager.get_instance()
   363          app_mgr.load_apps(app_lists)
   364          contexts = app_mgr.create_contexts()
   365          services = app_mgr.instantiate_apps(**contexts)
   366          webapp = wsgi.start_service(app_mgr)
   367          if webapp:
   368              services.append(hub.spawn(webapp))
   369          try:
   370              hub.joinall(services)
   371          finally:
   372              app_mgr.close()
   373              for t in services:
   374                  t.kill()
   375              hub.joinall(services)
   376              gc.collect()
   377
   378      @staticmethod
   379      def get_instance():
   380          if not AppManager._instance:
   381              AppManager._instance = AppManager()
   382          return AppManager._instance
   383
   384      def __init__(self):
   385          self.applications_cls = {}
   386          self.applications = {}
   387          self.contexts_cls = {}
   388          self.contexts = {}
   389          self.close_sem = hub.Semaphore()
  • 首先看379行即我们看到在上面的代码中调用的实例获取函数,也确实是单实例模式
  • 再看384行的初始化函数,不需要传入任何参数,仅对app和context的加载所需的变量进行初始化
  • 而这个run_apps函数,与我们在manager.py中的run_apps函数名字都一样,做的事情也一致
    • 经过搜索调用此函数的文件后,发现此函数仅在ryu/ryu/cmd/ofa_neutron_agent.py被使用
    • 猜测当openstack的neutron插件使用ryu控制器时,会与平时ryu-manager的逻辑有所不同
   391      def load_app(self, name):
   392          mod = utils.import_module(name)
   393          clses = inspect.getmembers(mod,
   394                                     lambda cls: (inspect.isclass(cls) and
   395                                                  issubclass(cls, RyuApp) and
   396                                                  mod.__name__ ==
   397                                                  cls.__module__))
   398          if clses:
   399              return clses[0][1]
   400          return None
   401
   402      def load_apps(self, app_lists):
   403          app_lists = [app for app
   404                       in itertools.chain.from_iterable(app.split(',')
   405                                                        for app in app_lists)]
   406          while len(app_lists) > 0:
   407              app_cls_name = app_lists.pop(0)
   408
   409              context_modules = [x.__module__ for x in self.contexts_cls.values()]
   410              if app_cls_name in context_modules:
   411                  continue
   412
   413              LOG.info('loading app %s', app_cls_name)
   414
   415              cls = self.load_app(app_cls_name)
   416              if cls is None:
   417                  continue
   418
   419              self.applications_cls[app_cls_name] = cls
   420
   421              services = []
   422              for key, context_cls in cls.context_iteritems():
   423                  v = self.contexts_cls.setdefault(key, context_cls)
   424                  assert v == context_cls
   425                  context_modules.append(context_cls.__module__)
   426
   427                  if issubclass(context_cls, RyuApp):
   428                      services.extend(get_dependent_services(context_cls))
   429
   430              # we can't load an app that will be initiataed for
   431              # contexts.
   432              for i in get_dependent_services(cls):
   433                  if i not in context_modules:
   434                      services.append(i)
   435              if services:
   436                  app_lists.extend([s for s in set(services)
   437                                    if s not in app_lists])
  • 接下来就是关键的app加载相关的函数
  • 上面的分析我们知道manager.py调用app_manager的load_apps作为加载app的入口函数
  • 首先403行对app_list中的内容进行扁平化处理,将逗号分隔的app都拆分成单独的app重新存入列表
  • 接下的循环就是加载app类以及处理依赖的逻辑
    • 406行,可以看到循环的结束条件是app_list为空
    • 而在407行从app_list中取出一个app进行处理
    • 一般来说这样使用while 列表不为空来处理列表中的内容,都是列表的内容会随着处理而动态改变
    • 而动态变化的原因,正是因为只有加载了一个app类之后,才知道他的依赖是什么,才能将其动态的加入app_list中
      • ryu没有强硬的规定依赖关系必须显式的写在配置中或者app类中(除了context)
      • 而是通过调用关系,隐含式的给我们实现的类中添加了对应的依赖关系,存疑???
      • 这也是ryu实现非常优雅的地方之一
    • 接下来409行确认当前处理的app是不是只是一个context而已
    • 415行调用load_app加载单个app
    • 419行可以看出,app_manager.applications_cls的键是类名,值是类,而不是实例
      • 注意此阶段并没有对app类进行实例化,只是记录类的类别
    • 421行初始化了一个列表叫services
      • 这里的services与上文manage.py中用来hub.joinall的services含义完全不同
      • 这里的services仅用于存储当前正在处理的app所依赖的app们
    • 422行迭代app类所声明的context
      • 如果是context只是一个其他的类,就加入到app_manager的contexts_cls字典中
      • 注意同样仅存了类的类别,没有初始化
    • 而425行的context_module列表既存储了之前app声明的context,也添加了当前app声明的context
    • 427行,如果context中的类同样实现了ryu的app_base,那么把他添加到services里,后续添加到app_list里
      • 说明如果context中声明的一个依赖同样实现了ryu的app基类,那么同样将其以app看待进行加载
    • 432行的get_dependent_services函数并不在本文件中
      • 但可以根据其名称知其含义,即获取本app所依赖的app们
      • 后续会对此函数详细解释
    • 433行的判断逻辑避免了context中的app类被重复添加至app_list,而维护context_modules变量的意义即如此
    • 435行将services变量中的app,即本app所依赖的app,添加到待加载的app_list中去
  • 而前面391行的load_app函数,也很简单,利用python动态的特性,从对应对的模块中找继承了app基类的那个用户实现的app类
   439      def create_contexts(self):
   440          for key, cls in self.contexts_cls.items():
   441              if issubclass(cls, RyuApp):
   442                  # hack for dpset
   443                  context = self._instantiate(None, cls)
   444              else:
   445                  context = cls()
   446              LOG.info('creating context %s', key)
   447              assert key not in self.contexts
   448              self.contexts[key] = context
   449          return self.contexts
  • 接下来create_contexts,也是前文manage.py中调用的函数
  • 从内容可以看出本函数对context中记录的类进行是初始化
  • 将各类的实例以kv的形式存储在app_manager.context中,并最后返回这个字典
  • 所以在load_apps中context进行了加载,而本函数进行了context的初始化,生成类的实例
   451      def _update_bricks(self):
   452          for i in SERVICE_BRICKS.values():
   453              for _k, m in inspect.getmembers(i, inspect.ismethod):
   454                  if not hasattr(m, 'callers'):
   455                      continue
   456                  for ev_cls, c in m.callers.items():
   457                      if not c.ev_source:
   458                          continue
   459
   460                      brick = _lookup_service_brick_by_mod_name(c.ev_source)
   461                      if brick:
   462                          brick.register_observer(ev_cls, i.name,
   463                                                  c.dispatchers)
   464
   465                      # allow RyuApp and Event class are in different module
   466                      for brick in SERVICE_BRICKS.values():
   467                          if ev_cls in brick._EVENTS:
   468                              brick.register_observer(ev_cls, i.name,
   469                                                      c.dispatchers)
   470
   471      @staticmethod
   472      def _report_brick(name, app):
   473          LOG.debug("BRICK %s", name)
   474          for ev_cls, list_ in app.observers.items():
   475              LOG.debug("  PROVIDES %s TO %s", ev_cls.__name__, list_)
   476          for ev_cls in app.event_handlers.keys():
   477              LOG.debug("  CONSUMES %s", ev_cls.__name__)
   478
   479      @staticmethod
   480      def report_bricks():
   481          for brick, i in SERVICE_BRICKS.items():
   482              AppManager._report_brick(brick, i)
   483
  • 上面的update_brickcs函数涉及到了ryu的一个魔法设计,即caller,后面还会详细说
  • 下面的两个report_bricks是用来打印当前控制器所管理的bricks,而这个砖头就是从文件开头的全局变量取出
  • 而bricks砖头,对应着我们指定的app,以及我们的app所依赖的app
  • 如下是我运行ryu时verbose模式下打印的BRICK内容
    • 其中openflow、hostapd_wif、hostapd_eth、pusher是我编写的app
    • 而ofp_event对应着我们的app依赖的app:ryu.controller.ofp_handler
  • 下面更重要的内容是每个BRICK,即app,消费以及产生的事件类型,对于了解多app情况下的event路由情况非常有帮助
    • 其中PROVIDES EventX To {'openflow': {'main'}}中的openflow指的是app名字,main指定是当前控制器所处于的状态disaptcher,如config配置状态,main运行状态
BRICK openflow
  CONSUMES EventOFPPacketIn
  CONSUMES EventJoin
  CONSUMES EventLeave
  CONSUMES EventOFPSwitchFeatures
BRICK hostapd_wif
  PROVIDES EventJoin TO {'openflow': {'main'}, 'pusher': {'main'}}
  PROVIDES EventLeave TO {'openflow': {'main'}, 'pusher': {'main'}}
BRICK hostapd_eth
  PROVIDES EventJoin TO {'openflow': {'main'}, 'pusher': {'main'}}
  PROVIDES EventLeave TO {'openflow': {'main'}, 'pusher': {'main'}}
BRICK pusher
  CONSUMES EventJoin
  CONSUMES EventLeave
BRICK ofp_event
  PROVIDES EventOFPPacketIn TO {'openflow': {'main'}}
  PROVIDES EventOFPSwitchFeatures TO {'openflow': {'config'}}
  CONSUMES EventOFPEchoReply
  CONSUMES EventOFPEchoRequest
  CONSUMES EventOFPErrorMsg
  CONSUMES EventOFPHello
  CONSUMES EventOFPPortDescStatsReply
  CONSUMES EventOFPPortStatus
  CONSUMES EventOFPSwitchFeatures
   484      def _instantiate(self, app_name, cls, *args, **kwargs):
   485          # for now, only single instance of a given module
   486          # Do we need to support multiple instances?
   487          # Yes, maybe for slicing.
   488          LOG.info('instantiating app %s of %s', app_name, cls.__name__)
   489
   490          if hasattr(cls, 'OFP_VERSIONS') and cls.OFP_VERSIONS is not None:
   491              ofproto_protocol.set_app_supported_versions(cls.OFP_VERSIONS)
   492
   493          if app_name is not None:
   494              assert app_name not in self.applications
   495          app = cls(*args, **kwargs)
   496          register_app(app)
   497          assert app.name not in self.applications
   498          self.applications[app.name] = app
   499          return app
   500
   501      def instantiate(self, cls, *args, **kwargs):
   502          app = self._instantiate(None, cls, *args, **kwargs)
   503          self._update_bricks()
   504          self._report_brick(app.name, app)
   505          return app
   506
   507      def instantiate_apps(self, *args, **kwargs):
   508          for app_name, cls in self.applications_cls.items():
   509              self._instantiate(app_name, cls, *args, **kwargs)
   510
   511          self._update_bricks()
   512          self.report_bricks()
   513
   514          threads = []
   515          for app in self.applications.values():
   516              t = app.start()
   517              if t is not None:
   518                  app.set_main_thread(t)
   519                  threads.append(t)
   520          return threads
  • 接下来是用来进行app初始化的函数
  • 首先manager.py调用的app初始化入口函数是instantiate_apps,
    • 在本函数里可以看到遍历了app_manager的app class,并调用_instantiate实际初始化
    • 后面调用_update_bricks更新app间的事件依赖关系,并通过report打印结果
    • 最后如同我们上面分析manager.py中的猜测一样,instantiate_apps函数返回了注册的所有app的协程句柄,用于在main函数中统一join
  • 在_instantiate函数中实现了app类的动态的实例化,并在实例化后调用register_app注册,最后在app_manager自身的application字典中注册新初始化的app
   522      @staticmethod
   523      def _close(app):
   524          close_method = getattr(app, 'close', None)
   525          if callable(close_method):
   526              close_method()
   527
   528      def uninstantiate(self, name):
   529          app = self.applications.pop(name)
   530          unregister_app(app)
   531          for app_ in SERVICE_BRICKS.values():
   532              app_.unregister_observer_all_event(name)
   533          app.stop()
   534          self._close(app)
   535          events = app.events
   536          if not events.empty():
   537              app.logger.debug('%s events remains %d', app.name, events.qsize())
   538
   539      def close(self):
   540          def close_all(close_dict):
   541              for app in close_dict.values():
   542                  self._close(app)
   543              close_dict.clear()
   544
   545          # This semaphore prevents parallel execution of this function,
   546          # as run_apps's finally clause starts another close() call.
   547          with self.close_sem:
   548              for app_name in list(self.applications.keys()):
   549                  self.uninstantiate(app_name)
   550              assert not self.applications
   551              close_all(self.contexts)
  • 最后是一些关闭、清理、删除当前app的函数
  • app_manager至此就分析结束了

工具方法

    48  def lookup_service_brick(name):
    49      return SERVICE_BRICKS.get(name)
    50
    51
    52  def _lookup_service_brick_by_ev_cls(ev_cls):
    53      return _lookup_service_brick_by_mod_name(ev_cls.__module__)
    54
    55
    56  def _lookup_service_brick_by_mod_name(mod_name):
    57      return lookup_service_brick(mod_name.split('.')[-1])
    58
    59
    60  def register_app(app):
    61      assert isinstance(app, RyuApp)
    62      assert app.name not in SERVICE_BRICKS
    63      SERVICE_BRICKS[app.name] = app
    64      register_instance(app)
    65
    66
    67  def unregister_app(app):
    68      SERVICE_BRICKS.pop(app.name)
    69
    70
    71  def require_app(app_name, api_style=False):
    72      """
    73      Request the application to be automatically loaded.
    74
    75      If this is used for "api" style modules, which is imported by a client
    76      application, set api_style=True.
    77
    78      If this is used for client application module, set api_style=False.
    79      """
    80      iterable = (inspect.getmodule(frame[0]) for frame in inspect.stack())
    81      modules = [module for module in iterable if module is not None]
    82      if api_style:
    83          m = modules[2]  # skip a frame for "api" module
    84      else:
    85          m = modules[1]
    86      m._REQUIRED_APP = getattr(m, '_REQUIRED_APP', [])
    87      m._REQUIRED_APP.append(app_name)
    88      LOG.debug('require_app: %s is required by %s', app_name, m.__name__)
    89
    90
  • 这些方法在app_manager的函数中有所调用
  • 可以看出,注册方法对应字典的赋值,删除方法对应字典的删除,搜索方法对应字典的取值
  • 读起来SERVICE_BRICKS与app_manager自身的appcalitions没有什么区别
  • 可能随着理解的加深能会慢慢了解这样的用意叭
  • 最后require_app函数的逻辑是添加依赖app名字到某个module的_REQUIRED_APP列表
    • _REQUIRED_APP下文我们还会见到

app基类

  • 由于app在app_manager视角只是一个被管理者,在下文中的事件收发小节中介绍app基类更好,这里暂且跳过

ryu/ryu/controller/handler.py

120 def get_dependent_services(cls):
121     services = []
122     for _k, m in inspect.getmembers(cls, _is_method):
123         if _has_caller(m):
124             for ev_cls, c in m.callers.items():
125                 service = getattr(sys.modules[ev_cls.__module__],
126                                   '_SERVICE_NAME', None)
127                 if service:
128                     # avoid cls that registers the own events (like
129                     # ofp_handler)
130                     if cls.__module__ != service:
131                         services.append(service)
132
133     m = sys.modules[cls.__module__]
134     services.extend(getattr(m, '_REQUIRED_APP', []))
135     services = list(set(services))
136     return services
  • 现在回来填坑上文所调用的获取本app依赖关系的函数get_dependent_services
  • 这个函数位于controller/handler.py,原因是因为涉及到了caller这个ryu用来管理事件依赖的魔法
  • 简单来说是我们通过set_ev_cls注册监听事件的时候,ryu会通过caller机制计算哪些app会生产此事件,从而计算出当前app所依赖的app有哪些
    • 例如我们指定了一个自己编写的app,监听了ofp_event.EventOFPPacketIn这个openflow的packet in事件
    • 但这个事件是由ryu.controller.ofp_handler这个app产生的,所以我们的app就对这个app产生了依赖关系
    • 很好理解,本app使用了其他app产生了事件,自然我们就对其产生了依赖
    • 而ryu正是通过实现了他自己的caller机制,管理这种依赖
  • 134行,可以看出通过设置_REQUIRED_APP可以显示的指定我们app所依赖的app
    • 前文提过,require_app函数是用于增加_REQUIRED_APP列表内容的方法
    • 通过grep搜索require_app函数的调用,可以看出是用于api调用方被调用方之间的依赖指定
    • 所以也就好理解require_app的api_style参数了
╭─root@testdevice ~/ryu/ryu ‹master›
╰─# grep -R require_app
app/gui_topology/gui_topology.py:app_manager.require_app('ryu.app.rest_topology')
app/gui_topology/gui_topology.py:app_manager.require_app('ryu.app.ws_topology')
app/gui_topology/gui_topology.py:app_manager.require_app('ryu.app.ofctl_rest')
app/ofctl/api.py:app_manager.require_app('ryu.app.ofctl.service', api_style=True)
base/app_manager.py:def require_app(app_name, api_style=False):
base/app_manager.py:    LOG.debug('require_app: %s is required by %s', app_name, m.__name__)
services/protocols/vrrp/api.py:app_manager.require_app('ryu.services.protocols.vrrp.manager', api_style=True)
topology/api.py:app_manager.require_app('ryu.topology.switches', api_style=True)
  • 除了get_dependent_services,ryu/ryu/controller/handler.py文件其他的函数或者类的定义,也都是围绕着caller有关

app管理小总结

   420
   421              services = []
   422              for key, context_cls in cls.context_iteritems():
   423                  v = self.contexts_cls.setdefault(key, context_cls)
   424                  assert v == context_cls
   425                  context_modules.append(context_cls.__module__)
   426
   427                  if issubclass(context_cls, RyuApp):
   428                      services.extend(get_dependent_services(context_cls))
   429
   430              # we can't load an app that will be initiataed for
   431              # contexts.
   432              for i in get_dependent_services(cls):
   433                  if i not in context_modules:
   434                      services.append(i)
   435              if services:
   436                  app_lists.extend([s for s in set(services)
   437                                    if s not in app_lists])
  • 经过阅读后可以看出,整个app依赖管理的核心就在十多行行代码
  • app的依赖来源也读到了三种
    1. 通过context指定(如果context中是一个app的话)
    2. 通过事件生产消费关系隐式的指定
    3. 通过app_manager.require_app函数显示指定
  • 第一点是在427行进行的判断
  • 第二三点,是在432行进行的获取
  • 同时会在433行进行避免第一点与第二三点之间依赖重复添加的逻辑

事件管理

  • 事件管理正是我们在前文不断提到的caller机制所实现的

ryu/ryu/controller/handler.py

  • 前文提到的get_dependent_services函数同样位于本文件中,现在开始分析caller相关机制

包引入

 17 import inspect
 18 import logging
 19 import sys
 20
 21 LOG = logging.getLogger('ryu.controller.handler')
 22
 23 # just represent OF datapath state. datapath specific so should be moved.
 24 HANDSHAKE_DISPATCHER = "handshake"
 25 CONFIG_DISPATCHER = "config"
 26 MAIN_DISPATCHER = "main"
 27 DEAD_DISPATCHER = "dead"
  • 导入了三个官方库后,ryu规定了四个当前运行阶段
    • 握手阶段、配置阶段、运行阶段、清理阶段

caller

 30 class _Caller(object):
 31     """Describe how to handle an event class.
 32     """
 33
 34     def __init__(self, dispatchers, ev_source):
 35         """Initialize _Caller.
 36
 37         :param dispatchers: A list of states or a state, in which this
 38                             is in effect.
 39                             None and [] mean all states.
 40         :param ev_source: The module which generates the event.
 41                           ev_cls.__module__ for set_ev_cls.
 42                           None for set_ev_handler.
 43         """
 44         self.dispatchers = dispatchers
 45         self.ev_source = ev_source
 46
 47
 48 # should be named something like 'observe_event'
 49 def set_ev_cls(ev_cls, dispatchers=None):
.....省略文档
 77     def _set_ev_cls_dec(handler):
 78         if 'callers' not in dir(handler):
 79             handler.callers = {}
 80         for e in _listify(ev_cls):
 81             handler.callers[e] = _Caller(_listify(dispatchers), e.__module__)
 82         return handler
 83     return _set_ev_cls_dec
 84
 85
 86 def set_ev_handler(ev_cls, dispatchers=None):
 87     def _set_ev_cls_dec(handler):
 88         if 'callers' not in dir(handler):
 89             handler.callers = {}
 90         for e in _listify(ev_cls):
 91             handler.callers[e] = _Caller(_listify(dispatchers), None)
 92         return handler
 93     return _set_ev_cls_dec
  • 30行见到了caller的真身,只有两个参数阶段和来源,也没有任何方法
    • caller可以这样理解:
      • 事件有生产者和消费者,当生产者生产了对应的事件后,消费者会触发执行相应的处理函数
      • 因此生产者就好像消费者的函数的调用者一样,作为一个caller,去call监听这个事件的那些函数
  • 49行见到了我们熟悉的set_ev_cls,他是一个带参数的装饰器
    • 带参数的装饰器返回值是装饰器
    • 装饰器返回值是一个新的函数
    • set_ev_cls这个装饰器的作用很简单
      • 78行判断被装饰的函数有没有callers属性
      • 80行遍历本需要监听的事件列表,并在函数的caller属性设置对应的kv
      • k是事件类型,v是30行的Caller类的实例,其中包含了在哪个运行阶段事件产生后会进行调用,和事件来源的信息
  • 86行的set_ev_handler函数与set_ev_cls非常相似,只有一点不同:
    • Caller类型的ev_source属性是None
    • 那么这个ev_source属性有什么用呢?与自动加载依赖有关,继续看代码会有更多解释
  • 于是我们明白了,set_ev_cls(handler)仅仅为我们的自定义函数添加了一个callers变量,记录着有哪些事件产生时,要调用本函数
  • 但我们这时候只知道有这样的事件存在,我要监听他,那么谁会产生这样的事件呢?我们回顾一下get_dependent_services函数:
120 def get_dependent_services(cls):
121     services = []
122     for _k, m in inspect.getmembers(cls, _is_method):
123         if _has_caller(m):
124             for ev_cls, c in m.callers.items():
125                 service = getattr(sys.modules[ev_cls.__module__],
126                                   '_SERVICE_NAME', None)
127                 if service:
128                     # avoid cls that registers the own events (like
129                     # ofp_handler)
130                     if cls.__module__ != service:
131                         services.append(service)
132
133     m = sys.modules[cls.__module__]
134     services.extend(getattr(m, '_REQUIRED_APP', []))
135     services = list(set(services))
136     return services
137
138
139 def register_service(service):
140     """
141     Register the ryu application specified by 'service' as
142     a provider of events defined in the calling module.
143
144     If an application being loaded consumes events (in the sense of
145     set_ev_cls) provided by the 'service' application, the latter
146     application will be automatically loaded.
147
148     This mechanism is used to e.g. automatically start ofp_handler if
149     there are applications consuming OFP events.
150     """
151     frame = inspect.currentframe()
152     m_name = frame.f_back.f_globals['__name__']
153     m = sys.modules[m_name]
154     m._SERVICE_NAME = service
  • 这次我们来详细回顾get_dependent_services函数:
    • 121行初始化services列表用于记录当前cls所依赖的app
    • 122行遍历app的所有函数,查看是否含有caller属性,也就是是否被set_ev_xx修饰过
    • 124行遍历当前函数监听的事件
    • 125行再次出现service变量名,这里的service表示当前app依赖的app
      • 获取依赖app是通过查找事件类所在模块的_SERVICE_NAME变量得到的
      • 恰好139行的register_service,本文件最后一个函数,就是用来为模块的_SERVICE_NAME变量赋值的
      • 赋值的value是通过参数指定的
      • 搜索调用register_service的函数,会发现我们熟悉的ofp_event,而经过查看发现ofp_event.py文件在文件的最末尾处直接调用了这句话
      • 也就是说,只要我们import了ofp_event模块,就会自动执行这条register语句,在ofp_event模块自身中植入_SERVICE_NAME变量,指示当前文件中的事件,是由哪个app产生的
      • 就解释了我们没有显式指定ofp_handler这个app,他同样会被拉起执行
controller/dpset.py:handler.register_service('ryu.controller.dpset')
controller/handler.py:def register_service(service):
controller/ofp_event.py:handler.register_service('ryu.controller.ofp_handler')
services/protocols/ovsdb/event.py:handler.register_service('ryu.services.protocols.ovsdb.manager')
services/protocols/vrrp/event.py:handler.register_service('ryu.services.protocols.vrrp.manager')
topology/event.py:handler.register_service('ryu.topology.switches')
 94
 95
 96 def _has_caller(meth):
 97     return hasattr(meth, 'callers')
 98
 99
100 def _listify(may_list):
101     if may_list is None:
102         may_list = []
103     if not isinstance(may_list, list):
104         may_list = [may_list]
105     return may_list
106
107
108 def register_instance(i):
109     for _k, m in inspect.getmembers(i, inspect.ismethod):
110         # LOG.debug('instance %s k %s m %s', i, _k, m)
111         if _has_caller(m):
112             for ev_cls, c in m.callers.items():
113                 i.register_handler(ev_cls, m)
114
115
116 def _is_method(f):
117     return inspect.isfunction(f) or inspect.ismethod(f)
118
119
  • 最后是本文件剩余的部分,除了几个工具函数,还有一个register_instance函数
  • 根据传参具有caller属性的方法知道,入参i是一个app,而在113行调用了app的register_handler方法
  • 搜索后得知,app_manager会在_instantiate函数,即对app进行初始化时,
  • 调用如下的register_app函数,其中调用了我们现在关注的register_instance函数
  • 因此在本节事件管理分析完毕后,会接着分析事件路由
def register_app(app):
    assert isinstance(app, RyuApp)
    assert app.name not in SERVICE_BRICKS
    SERVICE_BRICKS[app.name] = app
    register_instance(app)

事件管理以及依赖计算小结

  1. 编写特定协议的event模块时,在文件中执行register_service函数,表明当前模块中的event们是由哪个app产生
  2. 当我们的app导入了对应的event模块时,就已经执行了register_service
  3. 当我们的函数使用set_ev_cls修饰,并指定监听特定的event时,修饰器会在这个函数的callers变量中添加相应的监听事件信息
  4. 在run我们编写的app时,app_manager会使用get_dependent_services函数寻找当前app监听的事件
  5. 从而找到这个事件所属的event模块,再找到event模块的_SERVICE_NAME变量
  6. 最终得知如果我要监听在这个事件的话,需要拉起这个app,从而让其产生这样的事件以供我消费
  7. 如果我们没有在event模块中对事件生产者进行注册,或者使用的是set_ev_handler修饰器,那么ryu也就不会自动导入拉起对应的app了

事件收发

  • 事件的收发是多个app在运行阶段,进行的事件接收发送

ryu/ryu/base/app_manager.py

 91 class RyuApp(object):
...省略文档
105
106     _CONTEXTS = {}
...省略文档
124
125     _EVENTS = []
...省略文档
132     OFP_VERSIONS = None
...省略文档
146     @classmethod
147     def context_iteritems(cls):
148         """
149         Return iterator over the (key, contxt class) of application context
150         """
151         return iter(cls._CONTEXTS.items())
153     def __init__(self, *_args, **_kwargs):
154         super(RyuApp, self).__init__()
155         self.name = self.__class__.__name__
156         self.event_handlers = {}        # ev_cls -> handlers:list
157         self.observers = {}     # ev_cls -> observer-name -> states:set
158         self.threads = []
159         self.main_thread = None
160         self.events = hub.Queue(128)
161         self._events_sem = hub.BoundedSemaphore(self.events.maxsize)
162         if hasattr(self.__class__, 'LOGGER_NAME'):
163             self.logger = logging.getLogger(self.__class__.LOGGER_NAME)
164         else:
165             self.logger = logging.getLogger(self.name)
166         self.CONF = cfg.CONF
167
168         # prevent accidental creation of instances of this class outside RyuApp
169         class _EventThreadStop(event.EventBase):
170             pass
171         self._event_stop = _EventThreadStop()
172         self.is_active = True
  • 涉及到了app之间的事件收发,就不得不补一下前面剩下还没有分析的RyuAPP的app基类
  • app基类中定义了app与app间收、发事件的函数,也定义了app在协程中运行的事件循环
  • 在app基类的开头首先定义了类属性_CONTEXT,我们已经很熟悉了,是用来定义本app所依赖的外部类的字典
  • 还定义了_EVENTS类属性用来定义本app能够产生的事件,后面还会详细分析
  • OFP_VERSIONS是本app工作于的openflow版本
  • app基类初始化函数中
    • event_handlers变量存储接事件到函数的映射
    • observers是对本app产生的事件监听的app们,也就是本app的消费者
    • threads是本app产生的多个协程
    • main_thread用于main函数join,main_thread退出了才是真推出
    • events变量是个队列,用于存储其他app发送来的事件
    • 其他变量见名知意
194
195     def register_handler(self, ev_cls, handler):
196         assert callable(handler)
197         self.event_handlers.setdefault(ev_cls, [])
198         self.event_handlers[ev_cls].append(handler)
199
200     def unregister_handler(self, ev_cls, handler):
201         assert callable(handler)
202         self.event_handlers[ev_cls].remove(handler)
203         if not self.event_handlers[ev_cls]:
204             del self.event_handlers[ev_cls]
...省略发送相关函数,以及协程相关函数
229     def get_handlers(self, ev, state=None):
230         """Returns a list of handlers for the specific event.
231
232         :param ev: The event to handle.
233         :param state: The current state. ("dispatcher")
234                       If None is given, returns all handlers for the event.
235                       Otherwise, returns only handlers that are interested
236                       in the specified state.
237                       The default is None.
238         """
239         ev_cls = ev.__class__
240         handlers = self.event_handlers.get(ev_cls, [])
241         if state is None:
242             return handlers
243
244         def test(h):
245             if not hasattr(h, 'callers') or ev_cls not in h.callers:
246                 # dynamically registered handlers does not have
247                 # h.callers element for the event.
248                 return True
249             states = h.callers[ev_cls].dispatchers
250             if not states:
251                 # empty states means all states
252                 return True
253             return state in states
254
255         return filter(test, handlers)
  • 接下来分析app基类中事件钩子相关的函数
  • 首先我们看到了前文提到的调用链,即instantiate_apps -> _instantiate -> register_app -> register_instance -> register_handler,中的最后一环register_handler
  • register_handler做的事情也很简单,把本app中各函数的callers收集起来,整理成一个字典,也就是self.event_handlers
  • unregister_handler也就是相应的删除函数了
  • 229行get_handlers函数,获取相应ev所对应的handlers,并进行dispatcher阶段的过滤
206     def register_observer(self, ev_cls, name, states=None):
207         states = states or set()
208         ev_cls_observers = self.observers.setdefault(ev_cls, {})
209         ev_cls_observers.setdefault(name, set()).update(states)
210
211     def unregister_observer(self, ev_cls, name):
212         observers = self.observers.get(ev_cls, {})
213         observers.pop(name)
214
215     def unregister_observer_all_event(self, name):
216         for observers in self.observers.values():
217             observers.pop(name, None)
218
219     def observe_event(self, ev_cls, states=None):
220         brick = _lookup_service_brick_by_ev_cls(ev_cls)
221         if brick is not None:
222             brick.register_observer(ev_cls, self.name, states)
223
224     def unobserve_event(self, ev_cls):
225         brick = _lookup_service_brick_by_ev_cls(ev_cls)
226         if brick is not None:
227             brick.unregister_observer(ev_cls, self.name)
...省略其他函数
256
257     def get_observers(self, ev, state):
258         observers = []
259         for k, v in self.observers.get(ev.__class__, {}).items():
260             if not state or not v or state in v:
261                 observers.append(k)
262
263         return observers
264
...省略其他函数
320     def send_event_to_observers(self, ev, state=None):
321         """
322         Send the specified event to all observers of this RyuApp.
323         """
324
325         for observer in self.get_observers(ev, state):
326             self.send_event(observer, ev, state)
  • 接下来分析app基类中注册事件相关的函数
  • self.observers是一个字典,key是本app能够产生的事件类型,value还是一个字典,key是监听这个事件的app名字,value是他指定的监听生效的阶段
  • register_observer与unregister_observer函数被其他逻辑调用,从而向本app注册或取消注册事件的目的地
  • observe_event和unobserve_event则被本app调用,调用对方app的register_observer,向其他app声明自己的监听
  • 320行send_event_to_observers被本app中产生事件的函数所调用,向已经跟本app注册了监听的app们发送事件
265     def send_request(self, req):
266         """
267         Make a synchronous request.
268         Set req.sync to True, send it to a Ryu application specified by
269         req.dst, and block until receiving a reply.
270         Returns the received reply.
271         The argument should be an instance of EventRequestBase.
272         """
273
274         assert isinstance(req, EventRequestBase)
275         req.sync = True
276         req.reply_q = hub.Queue()
277         self.send_event(req.dst, req)
278         # going to sleep for the reply
279         return req.reply_q.get()
...省略其他函数
301     def _send_event(self, ev, state):
302         self._events_sem.acquire()
303         self.events.put((ev, state))
304
305     def send_event(self, name, ev, state=None):
306         """
307         Send the specified event to the RyuApp instance specified by name.
308         """
309
310         if name in SERVICE_BRICKS:
311             if isinstance(ev, EventRequestBase):
312                 ev.src = self.name
313             LOG.debug("EVENT %s->%s %s",
314                       self.name, name, ev.__class__.__name__)
315             SERVICE_BRICKS[name]._send_event(ev, state)
316         else:
317             LOG.debug("EVENT LOST %s->%s %s",
318                       self.name, name, ev.__class__.__name__)
319
320     def send_event_to_observers(self, ev, state=None):
321         """
322         Send the specified event to all observers of this RyuApp.
323         """
324
325         for observer in self.get_observers(ev, state):
326             self.send_event(observer, ev, state)
327
328     def reply_to_request(self, req, rep):
329         """
330         Send a reply for a synchronous request sent by send_request.
331         The first argument should be an instance of EventRequestBase.
332         The second argument should be an instance of EventReplyBase.
333         """
334
335         assert isinstance(req, EventRequestBase)
336         assert isinstance(rep, EventReplyBase)
337         rep.dst = req.src
338         if req.sync:
339             req.reply_q.put(rep)
340         else:
341             self.send_event(rep.dst, rep)
  • 接下来分析app基类中事件发送相关的函数
  • 305行是刚刚send_event_to_observers中调用的send_event函数
  • send_event在设置了event的src后,调用了目的app的_send_event函数
  • _send_event只需要在app的events列表中put相应的事件
  • 最后265和328行是ryu基于当前even机制实现的同步的请求响应机制
    • 请求时发送的事件中包含了同步请求的标识,还包含了取得结果的返回队列
    • 响应时只需把向req带来的队列中put返回值即可
174     def start(self):
175         """
176         Hook that is called after startup initialization is done.
177         """
178         self.threads.append(hub.spawn(self._event_loop))
179
180     def stop(self):
181         if self.main_thread:
182             hub.kill(self.main_thread)
183         self.is_active = False
184         self._send_event(self._event_stop, None)
185         hub.joinall(self.threads)
186
187     def set_main_thread(self, thread):
188         """
189         Set self.main_thread so that stop() can terminate it.
190
191         Only AppManager.instantiate_apps should call this function.
192         """
193         self.main_thread = thread
...省略其他事件收发相关函数
281     def _event_loop(self):
282         while self.is_active or not self.events.empty():
283             ev, state = self.events.get()
284             self._events_sem.release()
285             if ev == self._event_stop:
286                 continue
287             handlers = self.get_handlers(ev, state)
288             for handler in handlers:
289                 try:
290                     handler(ev)
291                 except hub.TaskExit:
292                     # Normal exit.
293                     # Propagate upwards, so we leave the event loop.
294                     raise
295                 except:
296                     LOG.exception('%s: Exception occurred during handler processing. '
297                                   'Backtrace from offending handler '
298                                   '[%s] servicing event [%s] follows.',
299                                   self.name, handler.__name__, ev.__class__.__name__)
  • 接下来分析app基类中事件接收相关的函数
  • 还记得我们在分析app_manager的instantiate_apps函数时,对每个app都调用了start,并将返回值作为协程句柄返回以供main函数join
  • 174行可以看到start函数做的事情只有在hub中添加本app的事件循环,并且如果不继承覆盖start函数的话,并不会返回协程句柄
  • 180行stop函数则进行相应的kill
  • 281行就是本app的事件循环
    • 282行可以看出本app处于活跃阶段,并且events队列中有事件时,才会进入循环
    • 283行从队列中取出事件,287行从本app的handlers变量取出ev事件对应的事件处理函数
    • 最后288行依次执行这个event对应的handler,完成事件的接收工作
451     def _update_bricks(self):
452         for i in SERVICE_BRICKS.values():
453             for _k, m in inspect.getmembers(i, inspect.ismethod):
454                 if not hasattr(m, 'callers'):
455                     continue
456                 for ev_cls, c in m.callers.items():
457                     if not c.ev_source:
458                         continue
459
460                     brick = _lookup_service_brick_by_mod_name(c.ev_source)
461                     if brick:
462                         brick.register_observer(ev_cls, i.name,
463                                                 c.dispatchers)
464
465                     # allow RyuApp and Event class are in different module
466                     for brick in SERVICE_BRICKS.values():
467                         if ev_cls in brick._EVENTS:
468                             brick.register_observer(ev_cls, i.name,
469                                                     c.dispatchers)
  • 最后还有一个疑问是,我们知道了handler变量通过怎么样的调用链完成初始化,那么observers呢?
  • 前文提到了app_manager中instantiate_apps函数调用了_update_bricks,这里回顾_update_bricks函数,前面我们没有详细分析,现在可以回头再来看一下
  • 452行遍历了BRICK,即app
  • 453行遍历了app的所有方法
  • 454行过滤留下有callers属性,即监听了事件的函数
  • 456行过滤掉没有事件来源的事件
  • 460行寻找产生这个事件的模块
  • 462行调用前文提到的register_observer,向对方app表明自己的存在,声明自己的监听事件和监听阶段
  • 最后466行,如果事件的产生者并不在event这个类的模块里,ryu还会寻找所有brick(app)的_EVENTS属性
    • 如果app在_EVENTS中声明了自己会产生这个事件的话,那么同样在这个app进行注册
    • 这也就填坑了前面没有细说的_EVENTS,就是在这里对其进行了读取使用
    • 因此如果我们自己要编写新的event类型,同时event定义与事件产生的app在不同模块,就需要要在_EVENTS指定事件信息

事件收发小结

  • app的handler和observers两个变量均在instantiate_apps阶段完成变量的计算和赋值,为后续的事件路由提供信息
  • 事件的接收是通过app基类中的事件循环完成的
  • 事件的发送是通过本app的某函数主动调用send_event_to_observers等函数实现的

TODO

  • ryu的wsgi
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容