目录:Tornado其他篇

01: tornado基础篇

02: tornado进阶篇

03: 自定义异步非阻塞tornado框架

04: 打开tornado源码剖析处理过程

目录:

1.1 tornado处理的两个阶段:启动程序阶段 & 处理请求阶段返回顶部

  1、第一阶段:启动程序阶段(也叫待处理请求阶段)

      1. 第一步,获取配置文件然后生成url映射(即:一个url对应一个XXRequestHandler,从而让XXRequestHandler来处理指定url发送的请求);

      2. 第二步,创建服务器socket对象并添加到epoll中;

      3. 第三步,创建无线循环去监听epoll,当请求到达时交由HttpServer类的_handle_events方法处理请求

  2、第二阶段:接收并处理请求阶段

      1. 第一步,接收客户端socket发送的请求(socket.accept);

      2. 第二步,从请求中获取请求头信息,再然后根据请求头中的请求url去匹配某个XXRequestHandler;

      3. 第三步,匹配成功的XXRequestHandler处理请求;

      4. 第四步,将处理后的请求发送给客户端;

      5. 第五步,关闭客户端socket。

  3、tornado使用最基本代码

  1. import tornado.ioloop
  2. import tornado.web
  3.  
  4. #1、 处理访问/index/的get请求: http://127.0.0.1:8888/index/
  5. class MainHandler(tornado.web.RequestHandler):
  6. def get(self):
  7. self.write("I am index!!")
  8.  
  9. #2、 配置settings
  10. settings = {
  11. 'template_path': 'template', # 配置html文件模板位置
  12. 'static_path': 'static', # 配置静态文件路径(图片等)
  13. 'static_url_prefix': '/static/', # 前端引入静态文件路径
  14. }
  15.  
  16. #3、路由系统
  17. application = tornado.web.Application([
  18. (r"/index/", MainHandler),
  19. ],**settings)
  20.  
  21. #4、启动这个tornado这个程序
  22. if __name__ == "__main__":
  23. application.listen(8888)
  24. print('http://127.0.0.1:8888/index/')
  25. tornado.ioloop.IOLoop.instance().start()

tornado使用最基本代码

1.2 application = tornado.web.Application([(xxx,xxx)]) : 启动程序阶段(第一步)返回顶部

   1、此句代码处理过程

      1. 执行Application类的构造函数,并传入一个列表类型的参数,这个列表里保存的是url规则和对应的处理类

      2. 即:当客户端的请求url可以配置这个规则时,那么该请求就交由对应的Handler去执行

      3. 注意:Handler泛指继承自RequestHandler的所有类,Handlers泛指继承自RequestHandler的所有类的集合

  2、处理此句代码对应源码

      1. Application.__init__:加载配置信息和生成url映射,并且把所有的信息封装在一个application对象中。

      2. Application.add_handlers: 遍历我们设置的和构造函数中添加的【url->Handler】映射,将url和对应的Handler封装到URLSpec类中

      3. URLSpec : 接收传入的url映射,生成URLSpec类

  1. class Application(object):
  2. def __init__(self, handlers=None, default_host="", transforms=None, wsgi=False, **settings):
  3. # 设置响应的编码和返回方式,对应的http响应头:Content-Encoding和Transfer-Encoding
  4. # Content-Encoding:gzip 表示对数据进行压缩,然后再返回给用户,从而减少流量的传输。
  5. # Transfer-Encoding:chunck 表示数据的传送方式通过一块一块的传输。
  6. if transforms is None:
  7. self.transforms = []
  8. if settings.get("gzip"):
  9. self.transforms.append(GZipContentEncoding)
  10. self.transforms.append(ChunkedTransferEncoding)
  11. else:
  12. self.transforms = transforms
  13. # 将参数赋值为类的变量
  14. self.handlers = []
  15. self.named_handlers = {}
  16. self.default_host = default_host
  17. self.settings = settings
  18. # ui_modules和ui_methods用于在模版语言中扩展自定义输出
  19. # 这里将tornado内置的ui_modules和ui_methods添加到类的成员变量self.ui_modules和self.ui_methods中
  20. self.ui_modules = {'linkify': _linkify,
  21. 'xsrf_form_html': _xsrf_form_html,
  22. 'Template': TemplateModule,
  23. }
  24. self.ui_methods = {}
  25. self._wsgi = wsgi
  26. # 获取获取用户自定义的ui_modules和ui_methods,并将他们添加到之前创建的成员变量self.ui_modules和self.ui_methods中
  27. self._load_ui_modules(settings.get("ui_modules", {}))
  28. self._load_ui_methods(settings.get("ui_methods", {}))
  29.  
  30. # 设置静态文件路径,设置方式则是通过正则表达式匹配url,让StaticFileHandler来处理匹配的url
  31. if self.settings.get("static_path"):
  32. # 从settings中读取key为static_path的值,用于设置静态文件路径
  33. path = self.settings["static_path"]
  34. # 获取参数中传入的handlers,如果空则设置为空列表
  35. handlers = list(handlers or [])
  36. # 静态文件前缀,默认是/static/
  37. static_url_prefix = settings.get("static_url_prefix", "/static/")
  38. # 在参数中传入的handlers前再添加三个映射:
  39. # 【/static/.*】 --> StaticFileHandler
  40. # 【/(favicon\.ico)】 --> StaticFileHandler
  41. # 【/(robots\.txt)】 --> StaticFileHandler
  42. handlers = [
  43. (re.escape(static_url_prefix) + r"(.*)", StaticFileHandler, dict(path=path)),
  44. (r"/(favicon\.ico)", StaticFileHandler, dict(path=path)),
  45. (r"/(robots\.txt)", StaticFileHandler, dict(path=path)),
  46. ] + handlers
  47. # 执行本类的Application的add_handlers方法
  48. # 此时,handlers是一个列表,其中的每个元素都是一个对应关系,即:url正则表达式和处理匹配该正则的url的Handler
  49. if handlers: self.add_handlers(".*$", handlers)
  50.  
  51. # Automatically reload modified modules
  52. # 如果settings中设置了 debug 模式,那么就使用自动加载重启
  53. if self.settings.get("debug") and not wsgi:
  54. import autoreload
  55. autoreload.start()

Application.__init__

  1. class Application(object):
  2. def add_handlers(self, host_pattern, host_handlers):
  3. # 如果主机模型最后没有结尾符,那么就为他添加一个结尾符。
  4. if not host_pattern.endswith("$"):
  5. host_pattern += "$"
  6. handlers = []
  7. # 对主机名先做一层路由映射,例如:http://www.wupeiqi.com 和 http://safe.wupeiqi.com
  8. # 即:safe对应一组url映射,www对应一组url映射,那么当请求到来时,先根据它做第一层匹配,之后再继续进入内部匹配。
  9.  
  10. # 对于第一层url映射来说,由于.*会匹配所有的url,所将 .* 的永远放在handlers列表的最后,不然 .* 就会截和了...
  11. # re.complie是编译正则表达式,以后请求来的时候只需要执行编译结果的match方法就可以去匹配了
  12. if self.handlers and self.handlers[-1][0].pattern == '.*$':
  13. self.handlers.insert(-1, (re.compile(host_pattern), handlers))
  14. else:
  15. self.handlers.append((re.compile(host_pattern), handlers))
  16.  
  17. # 遍历我们设置的和构造函数中添加的【url->Handler】映射,将url和对应的Handler封装到URLSpec类中(构造函数中会对url进行编译)
  18. # 并将所有的URLSpec对象添加到handlers列表中,而handlers列表和主机名模型组成一个元祖,添加到self.Handlers列表中。
  19. for spec in host_handlers:
  20. if type(spec) is type(()):
  21. assert len(spec) in (2, 3)
  22. pattern = spec[0]
  23. handler = spec[1]
  24. if len(spec) == 3:
  25. kwargs = spec[2]
  26. else:
  27. kwargs = {}
  28. spec = URLSpec(pattern, handler, kwargs)
  29. handlers.append(spec)
  30.  
  31. if spec.name:
  32. # 未使用该功能,默认spec.name = None
  33. if spec.name in self.named_handlers:
  34. logging.warning("Multiple handlers named %s; replacing previous value", spec.name)
  35. self.named_handlers[spec.name] = spec

Application.add_handlers

  1. class URLSpec(object):
  2. def __init__(self, pattern, handler_class, kwargs={}, name=None):
  3. if not pattern.endswith('$'):
  4. pattern += '$'
  5. self.regex = re.compile(pattern)
  6. self.handler_class = handler_class
  7. self.kwargs = kwargs
  8. self.name = name
  9. self._path, self._group_count = self._find_groups()

URLSpec

  3、加载的配置信息包括:

      1. 编码和返回方式信息
      2. 静态文件路径
      3. ui_modules(模版语言中使用,暂时忽略)
      4. ui_methods(模版语言中使用,暂时忽略)
      5. 是否debug模式运行

      注: 以上的所有配置信息,都可以在settings中配置,然后在创建Application对象时候,传入参数即可。

            如:application = tornado.web.Application([(r"/index", MainHandler),],**settings)

  4、生成url映射:

      将url和对应的Handler添加到对应的主机前缀中,如:safe.index.com、www.auto.com

   5、 封装数据:

      将配置信息和url映射关系封装到Application对象中,信息分别保存在Application对象的以下字段中:
      1. self.transforms,     保存着编码和返回方式信息
      2. self.settings,          保存着配置信息
      3. self.ui_modules,    保存着ui_modules信息
      4. self.ui_methods,    保存这ui_methods信息
      5. self.handlers,         保存着所有的主机名对应的Handlers,每个handlers则是url正则对应的Handler

1.3 application.listen(xxx) : 启动程序阶段(第二步)返回顶部

   1、此句代码处理过程

      1. 第一步操作将配置和url映射等信息封装到了application对象中, 而这第二步执行application对象的listen方法,

      2. 该方法内部又把之前包含各种信息的application对象封装到了一个HttpServer对象中,然后继续调用HttpServer对象的liseten方法。

  2、处理此句代码对应源码(简要代码)

  3、处理此句代码对应源码(详细代码)

  1. class HTTPServer(object):
  2. def __init__(self, request_callback, no_keep_alive=False, io_loop=None,xheaders=False, ssl_options=None):
  3. #Application对象
  4. self.request_callback = request_callback
  5. #是否长连接
  6. self.no_keep_alive = no_keep_alive
  7. #IO循环
  8. self.io_loop = io_loop
  9. self.xheaders = xheaders
  10. #Http和Http
  11. self.ssl_options = ssl_options
  12. self._socket = None
  13. self._started = False
  14.  
  15. def listen(self, port, address=""):
  16. self.bind(port, address)
  17. self.start(1)
  18.  
  19. def bind(self, port, address=None, family=socket.AF_UNSPEC):
  20. assert not self._socket
  21. #创建服务端socket对象,IPV4和TCP连接
  22. self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
  23. flags = fcntl.fcntl(self._socket.fileno(), fcntl.F_GETFD)
  24. flags |= fcntl.FD_CLOEXEC
  25. fcntl.fcntl(self._socket.fileno(), fcntl.F_SETFD, flags)
  26. #配置socket对象
  27. self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  28. self._socket.setblocking(0)
  29. #绑定IP和端口
  30. self._socket.bind((address, port))
  31. #最大阻塞数量
  32. self._socket.listen(128)
  33.  
  34. def start(self, num_processes=1):
  35. assert not self._started
  36. self._started = True
  37. if num_processes is None or num_processes <= 0:
  38. num_processes = _cpu_count()
  39. if num_processes > 1 and ioloop.IOLoop.initialized():
  40. logging.error("Cannot run in multiple processes: IOLoop instance "
  41. "has already been initialized. You cannot call "
  42. "IOLoop.instance() before calling start()")
  43. num_processes = 1
  44. #如果进程数大于1
  45. if num_processes > 1:
  46. logging.info("Pre-forking %d server processes", num_processes)
  47. for i in range(num_processes):
  48. if os.fork() == 0:
  49. import random
  50. from binascii import hexlify
  51. try:
  52. # If available, use the same method as
  53. # random.py
  54. seed = long(hexlify(os.urandom(16)), 16)
  55. except NotImplementedError:
  56. # Include the pid to avoid initializing two
  57. # processes to the same value
  58. seed(int(time.time() * 1000) ^ os.getpid())
  59. random.seed(seed)
  60. self.io_loop = ioloop.IOLoop.instance()
  61. self.io_loop.add_handler(
  62. self._socket.fileno(), self._handle_events,
  63. ioloop.IOLoop.READ)
  64. return
  65. os.waitpid(-1, 0)
  66. #进程数等于1,默认
  67. else:
  68. if not self.io_loop:
  69. #设置成员变量self.io_loop为IOLoop的实例,注:IOLoop使用methodclass完成了一个单例模式
  70. self.io_loop = ioloop.IOLoop.instance()
  71. #执行IOLoop的add_handler方法,将socket句柄、self._handle_events方法和IOLoop.READ当参数传入
  72. self.io_loop.add_handler(self._socket.fileno(),
  73. self._handle_events,
  74. ioloop.IOLoop.READ)
  75. def _handle_events(self, fd, events):
  76. while True:
  77. try:
  78. #====important=====#
  79. connection, address = self._socket.accept()
  80. except socket.error, e:
  81. if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
  82. return
  83. raise
  84. if self.ssl_options is not None:
  85. assert ssl, "Python 2.6+ and OpenSSL required for SSL"
  86. try:
  87. #====important=====#
  88. connection = ssl.wrap_socket(connection,server_side=True,do_handshake_on_connect=False,**self.ssl_options)
  89. except ssl.SSLError, err:
  90. if err.args[0] == ssl.SSL_ERROR_EOF:
  91. return connection.close()
  92. else:
  93. raise
  94. except socket.error, err:
  95. if err.args[0] == errno.ECONNABORTED:
  96. return connection.close()
  97. else:
  98. raise
  99. try:
  100. if self.ssl_options is not None:
  101. stream = iostream.SSLIOStream(connection, io_loop=self.io_loop)
  102. else:
  103. stream = iostream.IOStream(connection, io_loop=self.io_loop)
  104. #====important=====#
  105. HTTPConnection(stream, address, self.request_callback,self.no_keep_alive, self.xheaders)
  106. except:
  107. logging.error("Error in connection callback", exc_info=True)

HTTPServer

  1. class IOLoop(object):
  2. # Constants from the epoll module
  3. _EPOLLIN = 0x001
  4. _EPOLLPRI = 0x002
  5. _EPOLLOUT = 0x004
  6. _EPOLLERR = 0x008
  7. _EPOLLHUP = 0x010
  8. _EPOLLRDHUP = 0x2000
  9. _EPOLLONESHOT = (1 << 30)
  10. _EPOLLET = (1 << 31)
  11.  
  12. # Our events map exactly to the epoll events
  13. NONE = 0
  14. READ = _EPOLLIN
  15. WRITE = _EPOLLOUT
  16. ERROR = _EPOLLERR | _EPOLLHUP | _EPOLLRDHUP
  17.  
  18. def __init__(self, impl=None):
  19. self._impl = impl or _poll()
  20. if hasattr(self._impl, 'fileno'):
  21. self._set_close_exec(self._impl.fileno())
  22. self._handlers = {}
  23. self._events = {}
  24. self._callbacks = []
  25. self._timeouts = []
  26. self._running = False
  27. self._stopped = False
  28. self._blocking_signal_threshold = None
  29.  
  30. # Create a pipe that we send bogus data to when we want to wake
  31. # the I/O loop when it is idle
  32. if os.name != 'nt':
  33. r, w = os.pipe()
  34. self._set_nonblocking(r)
  35. self._set_nonblocking(w)
  36. self._set_close_exec(r)
  37. self._set_close_exec(w)
  38. self._waker_reader = os.fdopen(r, "rb", 0)
  39. self._waker_writer = os.fdopen(w, "wb", 0)
  40. else:
  41. self._waker_reader = self._waker_writer = win32_support.Pipe()
  42. r = self._waker_writer.reader_fd
  43. self.add_handler(r, self._read_waker, self.READ)
  44.  
  45. @classmethod
  46. def instance(cls):
  47. if not hasattr(cls, "_instance"):
  48. cls._instance = cls()
  49. return cls._instance
  50.  
  51. def add_handler(self, fd, handler, events):
  52. """Registers the given handler to receive the given events for fd."""
  53. self._handlers[fd] = stack_context.wrap(handler)
  54. self._impl.register(fd, events | self.ERROR)

IOLoop

  1. def wrap(fn):
  2. '''Returns a callable object that will resore the current StackContext
  3. when executed.
  4.  
  5. Use this whenever saving a callback to be executed later in a
  6. different execution context (either in a different thread or
  7. asynchronously in the same thread).
  8. '''
  9. if fn is None:
  10. return None
  11. # functools.wraps doesn't appear to work on functools.partial objects
  12. #@functools.wraps(fn)
  13. def wrapped(callback, contexts, *args, **kwargs):
  14. # If we're moving down the stack, _state.contexts is a prefix
  15. # of contexts. For each element of contexts not in that prefix,
  16. # create a new StackContext object.
  17. # If we're moving up the stack (or to an entirely different stack),
  18. # _state.contexts will have elements not in contexts. Use
  19. # NullContext to clear the state and then recreate from contexts.
  20. if (len(_state.contexts) > len(contexts) or
  21. any(a[1] is not b[1]
  22. for a, b in itertools.izip(_state.contexts, contexts))):
  23. # contexts have been removed or changed, so start over
  24. new_contexts = ([NullContext()] +
  25. [cls(arg) for (cls,arg) in contexts])
  26. else:
  27. new_contexts = [cls(arg)
  28. for (cls, arg) in contexts[len(_state.contexts):]]
  29. if len(new_contexts) > 1:
  30. with contextlib.nested(*new_contexts):
  31. callback(*args, **kwargs)
  32. elif new_contexts:
  33. with new_contexts[0]:
  34. callback(*args, **kwargs)
  35. else:
  36. callback(*args, **kwargs)
  37. if getattr(fn, 'stack_context_wrapped', False):
  38. return fn
  39. contexts = _state.contexts
  40. result = functools.partial(wrapped, fn, contexts)
  41. result.stack_context_wrapped = True
  42. return result

stack_context.wrap

    1. HTTPServer实质做了以下四件事

        1. 调用自己的构造方法,把包含了各种配置信息的application对象封装到了HttpServer对象的request_callback字段

        2. 调用socketsocket() , 创建服务端socket对象

        3. 单例模式创建IOLoop对象,然后将socket对象句柄作为key,被封装了的函数_handle_events作为value,添加到IOLoop对象的_handlers字段中

        4. 向epoll中注册监听服务端socket对象的读可用事件

    2. IOLoop作用

        1. 在HttpServer中生成IOLoop对象,并调用 IOLoop的add_handler方法

        2. add_handler 方法内部,将_handle_events函数封装到stack_context.wrap中,然后保存到IOLoop对象的self._handlers[fd]字段中

        3. 然后调用epoll对象,向epoll中注册监听socket事件

    3. stack_context.wrap作用

        1. stack_context.wrap其实就是对函数进行一下封装,即:函数在不同情况下上下文信息可能不同。

  4、使用epoll创建服务端socket(补充)

  1. import socket, select
  2.  
  3. EOL1 = b'/n/n'
  4. EOL2 = b'/n/r/n'
  5. response = b'HTTP/1.0 200 OK/r/nDate: Mon, 1 Jan 1996 01:01:01 GMT/r/n'
  6. response += b'Content-Type: text/plain/r/nContent-Length: 13/r/n/r/n'
  7. response += b'Hello, world!'
  8.  
  9. serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  10. serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  11. serversocket.bind(('0.0.0.0', 8080))
  12. serversocket.listen(1)
  13. serversocket.setblocking(0)
  14.  
  15. epoll = select.epoll()
  16. epoll.register(serversocket.fileno(), select.EPOLLIN)
  17.  
  18. try:
  19. connections = {}; requests = {}; responses = {}
  20. while True:
  21. events = epoll.poll(1)
  22. for fileno, event in events:
  23. if fileno == serversocket.fileno():
  24. connection, address = serversocket.accept()
  25. connection.setblocking(0)
  26. epoll.register(connection.fileno(), select.EPOLLIN)
  27. connections[connection.fileno()] = connection
  28. requests[connection.fileno()] = b''
  29. responses[connection.fileno()] = response
  30. elif event & select.EPOLLIN:
  31. requests[fileno] += connections[fileno].recv(1024)
  32. if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
  33. epoll.modify(fileno, select.EPOLLOUT)
  34. print('-'*40 + '/n' + requests[fileno].decode()[:-2])
  35. elif event & select.EPOLLOUT:
  36. byteswritten = connections[fileno].send(responses[fileno])
  37. responses[fileno] = responses[fileno][byteswritten:]
  38. if len(responses[fileno]) == 0:
  39. epoll.modify(fileno, 0)
  40. connections[fileno].shutdown(socket.SHUT_RDWR)
  41. elif event & select.EPOLLHUP:
  42. epoll.unregister(fileno)
  43. connections[fileno].close()
  44. del connections[fileno]
  45. finally:
  46. epoll.unregister(serversocket.fileno())
  47. epoll.close()
  48. serversocket.close()

使用epoll创建服务端socket

    1.  上述,其实就是利用epoll对象的poll(timeout)方法去轮询已经注册在epoll中的socket句柄,

    2.  当有读可用的信息时候,则返回包含当前句柄和Event Code的序列,然后在通过句柄对客户端的请求进行处理

1.4 tornado.ioloop.IOLoop.instance().start() : 启动程序阶段(第三步)返回顶部

   1、此句代码处理过程

      1. 执行IOLoop实例的start()方法后程序就进入“死循环”,也就是会一直不停的轮询的去检查是否有请求到来

      2. 如果有请求到达,则执行封装了HttpServer类的_handle_events方法和相关上下文的stack_context.wrap(handler)
           (其实就是执行HttpServer类的_handle_events方法)

  2、处理此句代码对应源码(简要代码)

  1. class IOLoop(object):
  2. def add_handler(self, fd, handler, events):
  3. # HttpServer的Start方法中会调用该方法
  4. self._handlers[fd] = stack_context.wrap(handler)
  5. self._impl.register(fd, events | self.ERROR)
  6.  
  7. def start(self):
  8. while True:
  9. poll_timeout = 0.2
  10. try:
  11. # epoll中轮询
  12. event_pairs = self._impl.poll(poll_timeout)
  13. except Exception, e:
  14. # 省略其他
  15. # 如果有读可用信息,则把该socket对象句柄和Event Code序列添加到self._events中
  16. self._events.update(event_pairs)
  17. # 遍历self._events,处理每个请求
  18. while self._events:
  19. fd, events = self._events.popitem()
  20. try:
  21. # 以socket为句柄为key,取出self._handlers中的stack_context.wrap(handler),并执行
  22. # stack_context.wrap(handler)包装了HTTPServer类的_handle_events函数的一个函数
  23. # 是在上一步中执行add_handler方法时候,添加到self._handlers中的数据。
  24. self._handlers[fd](fd, events)
  25. except:
  26. # 省略其他

IOLoop.start

  1. class HTTPServer(object):
  2. def _handle_events(self, fd, events):
  3. while True:
  4. try:
  5. connection, address = self._socket.accept()
  6. except socket.error, e:
  7. if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
  8. return
  9. raise
  10. if self.ssl_options is not None:
  11. assert ssl, "Python 2.6+ and OpenSSL required for SSL"
  12. try:
  13. connection = ssl.wrap_socket(connection,
  14. server_side=True,
  15. do_handshake_on_connect=False,
  16. **self.ssl_options)
  17. except ssl.SSLError, err:
  18. if err.args[0] == ssl.SSL_ERROR_EOF:
  19. return connection.close()
  20. else:
  21. raise
  22. except socket.error, err:
  23. if err.args[0] == errno.ECONNABORTED:
  24. return connection.close()
  25. else:
  26. raise
  27. try:
  28. if self.ssl_options is not None:
  29. stream = iostream.SSLIOStream(connection, io_loop=self.io_loop)
  30. else:
  31. stream = iostream.IOStream(connection, io_loop=self.io_loop)
  32. HTTPConnection(stream, address, self.request_callback,
  33. self.no_keep_alive, self.xheaders)
  34. except:
  35. logging.error("Error in connection callback", exc_info=True)

HTTPServer._handle_events

04: 打开tornado源码剖析处理过程的更多相关文章

  1. 老李推荐:第8章5节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-运行测试脚本

    老李推荐:第8章5节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-运行测试脚本   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化 ...

  2. 老李推荐:第8章1节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-运行环境初始化

    老李推荐:第8章1节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-运行环境初始化   首先大家应该清楚的一点是,MonkeyRunner的运行是牵涉到主机端和目 ...

  3. 老李推荐:第8章7节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-小结

    老李推荐:第8章7节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-小结   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性 ...

  4. 04 drf源码剖析之版本

    04 drf源码剖析之版本 目录 04 drf源码剖析之版本 1. 版本简述 2. 版本使用 3.源码剖析 4. 总结 1. 版本简述 API版本控制使您可以更改不同客户端之间的行为.REST框架提供 ...

  5. 第五篇:白话tornado源码之褪去模板的外衣

    上一篇<白话tornado源码之请求来了>介绍了客户端请求在tornado框架中的生命周期,其本质就是利用epoll和socket来获取并处理请求.在上一篇的内容中,我们只是给客户端返回了 ...

  6. Python框架之Tornado (源码之褪去模板外衣)

    上一篇介绍了客户端请求在tornado框架中的生命周期,其本质就是利用epoll和socket来获取并处理请求.在上一篇的内容中,我们只是给客户端返回了简单的字符串,如:“Hello World”,而 ...

  7. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  8. 第三篇:白话tornado源码之请求来了

    上一篇<白话tornado源码之待请求阶段>中介绍了tornado框架在客户端请求之前所做的准备(下图1.2部分),本质上就是创建了一个socket服务端,并进行了IP和端口的绑定,但是未 ...

  9. SpringMVC源码剖析(二)- DispatcherServlet的前世今生

    上一篇文章<SpringMVC源码剖析(一)- 从抽象和接口说起>中,我介绍了一次典型的SpringMVC请求处理过程中,相继粉墨登场的各种核心类和接口.我刻意忽略了源码中的处理细节,只列 ...

随机推荐

  1. POJ2387-Till the cows come home【最短路】

    A - Til the Cows Come Home POJ - 2387 Bessie is out in the field and wants to get back to the barn t ...

  2. JAVA补充-接口

    1.接口详解 package com.neusoft.interfaced; /** * 接口: * 语法:interface 接口名{ * public static final 变量1=值1: * ...

  3. Java基础类编程集锦

    1.计算1+2+3+4+5+6+7+8+9的值 package com.neusoft.chapter1; /** * @author zhao-chj *题:计算1+2+3+4+5+6+7+8+9的 ...

  4. 计蒜客 31447 - Fantastic Graph - [有源汇上下界可行流][2018ICPC沈阳网络预赛F题]

    题目链接:https://nanti.jisuanke.com/t/31447 "Oh, There is a bipartite graph.""Make it Fan ...

  5. HDU 1711 - Number Sequence - [KMP模板题]

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1711 Time Limit: 10000/5000 MS (Java/Others) Memory L ...

  6. The History of Operating Systems

    COMPPUTER SCIENCE AN OVERVIEW 11th Edition job 作业 batch processing 批处理 queue 队列 job queue 作业队列 first ...

  7. 【pip uninstall 无法卸载】Not uninstalling numpy at /usr/lib/python2.7/dist-packages, outside environment /usr

    想卸载python的库numpy,执行pip uninstall gunicorn,报错如下: Not uninstalling numpy at /usr/lib/python2.7/dist-pa ...

  8. 【python+opencv】直线检测+圆检测

     Python+OpenCV图像处理—— 直线检测 直线检测理论知识: 1.霍夫变换(Hough Transform) 霍夫变换是图像处理中从图像中识别几何形状的基本方法之一,应用很广泛,也有很多改进 ...

  9. (3.14)mysql基础深入——mysql 日志分析工具之pt-querty-digest【待完善】

    (3.14)mysql基础深入——mysql 日志分析工具之pt-querty-digest 关键字:Mysql日志分析工具.mysqlsla 常用工具 [1]mysqldumpslow:官方提供的慢 ...

  10. 创建list方法总结

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/sheismylife/article/details/28878593 构建一个list 注意要标记 ...