├── Makefile ├── README.rst ├── _static ├── p01_async.png ├── p01_block.png ├── p01_sync.png ├── p01_threaded.png ├── p02_reactor-1.png ├── p03_reactor-callback.png ├── p04_reactor-doread.png ├── p05_protocols-1.png ├── p05_protocols-2.png ├── p05_reactor-data-received.png ├── p06_reactor-poem-callback.png ├── p07_deferred-1.png ├── p08_reactor-deferred-callback.png ├── p08_sync-async.png ├── p09_async-exceptions4.png ├── p09_deferred-2.png ├── p09_deferred-31.png ├── p09_sync-exceptions1.png ├── p10_deferred-10.png ├── p10_deferred-42.png ├── p10_deferred-5.png ├── p10_deferred-6.png ├── p10_deferred-7.png ├── p10_deferred-8.png ├── p10_deferred-9.png ├── p11_server-1.png ├── p12_server-21.png ├── p13_deferred-111.png ├── p13_deferred-12.png ├── p14_deferred-13.png ├── p14_deferred-14.png ├── p14_proxy1.png ├── p15_test-1.png ├── p16_application.png ├── p17_generator-callbacks1.png ├── p17_inline-callbacks1.png ├── p18_deferred-list.png ├── p19_deferred-cancel.png ├── p19_sync-cancel.png ├── p20_callback-loop.png ├── p20_erlang-11.png ├── p20_erlang-2.png ├── p20_erlang-3.png ├── p20_reactor-2.png ├── p20_reactor-3.png ├── p21_haskell.png └── p22_theend1.png ├── conf.py ├── index.rst ├── make.bat ├── p01.rst ├── p02.rst ├── p03.rst ├── p04.rst ├── p05.rst ├── p06.rst ├── p07.rst ├── p08.rst ├── p09.rst ├── p10.rst ├── p11.rst ├── p12.rst ├── p13.rst ├── p14.rst ├── p15.rst ├── p16.rst ├── p17.rst ├── p18.rst ├── p19.rst ├── p20.rst ├── p21.rst └── p22.rst /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/twisted-intro-cn.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/twisted-intro-cn.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/twisted-intro-cn" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/twisted-intro-cn" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | Dave's An Introduction to Asynchronous Programming and Twisted in Chinese 3 | ================================================================================ 4 | 本项目是"Twisted与异步编程入门"系列文章的简体中文翻译. 5 | 原文由Dave撰写,参见 `krondo.com `_. 6 | 7 | 如果您是Twisted新手, `Twisted 文档`_ 也是不错的选择. 8 | 9 | This is a translation project of "An Introduction to Asynchronous Programming and Twisted" in Chinese. 10 | The original materials are `krondo.com `_. 11 | 12 | If you are new to Twisted, `Twisted Documentation`_ is a good starting point. 13 | 14 | .. _Twisted 文档: http://twistedmatrix.com/documents/10.0.0/core/howto/index.html 15 | 16 | .. _Twisted Documentation: http://twistedmatrix.com/documents/10.0.0/core/howto/index.html 17 | -------------------------------------------------------------------------------- /_static/p01_async.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p01_async.png -------------------------------------------------------------------------------- /_static/p01_block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p01_block.png -------------------------------------------------------------------------------- /_static/p01_sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p01_sync.png -------------------------------------------------------------------------------- /_static/p01_threaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p01_threaded.png -------------------------------------------------------------------------------- /_static/p02_reactor-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p02_reactor-1.png -------------------------------------------------------------------------------- /_static/p03_reactor-callback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p03_reactor-callback.png -------------------------------------------------------------------------------- /_static/p04_reactor-doread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p04_reactor-doread.png -------------------------------------------------------------------------------- /_static/p05_protocols-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p05_protocols-1.png -------------------------------------------------------------------------------- /_static/p05_protocols-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p05_protocols-2.png -------------------------------------------------------------------------------- /_static/p05_reactor-data-received.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p05_reactor-data-received.png -------------------------------------------------------------------------------- /_static/p06_reactor-poem-callback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p06_reactor-poem-callback.png -------------------------------------------------------------------------------- /_static/p07_deferred-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p07_deferred-1.png -------------------------------------------------------------------------------- /_static/p08_reactor-deferred-callback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p08_reactor-deferred-callback.png -------------------------------------------------------------------------------- /_static/p08_sync-async.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p08_sync-async.png -------------------------------------------------------------------------------- /_static/p09_async-exceptions4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p09_async-exceptions4.png -------------------------------------------------------------------------------- /_static/p09_deferred-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p09_deferred-2.png -------------------------------------------------------------------------------- /_static/p09_deferred-31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p09_deferred-31.png -------------------------------------------------------------------------------- /_static/p09_sync-exceptions1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p09_sync-exceptions1.png -------------------------------------------------------------------------------- /_static/p10_deferred-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p10_deferred-10.png -------------------------------------------------------------------------------- /_static/p10_deferred-42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p10_deferred-42.png -------------------------------------------------------------------------------- /_static/p10_deferred-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p10_deferred-5.png -------------------------------------------------------------------------------- /_static/p10_deferred-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p10_deferred-6.png -------------------------------------------------------------------------------- /_static/p10_deferred-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p10_deferred-7.png -------------------------------------------------------------------------------- /_static/p10_deferred-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p10_deferred-8.png -------------------------------------------------------------------------------- /_static/p10_deferred-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p10_deferred-9.png -------------------------------------------------------------------------------- /_static/p11_server-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p11_server-1.png -------------------------------------------------------------------------------- /_static/p12_server-21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p12_server-21.png -------------------------------------------------------------------------------- /_static/p13_deferred-111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p13_deferred-111.png -------------------------------------------------------------------------------- /_static/p13_deferred-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p13_deferred-12.png -------------------------------------------------------------------------------- /_static/p14_deferred-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p14_deferred-13.png -------------------------------------------------------------------------------- /_static/p14_deferred-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p14_deferred-14.png -------------------------------------------------------------------------------- /_static/p14_proxy1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p14_proxy1.png -------------------------------------------------------------------------------- /_static/p15_test-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p15_test-1.png -------------------------------------------------------------------------------- /_static/p16_application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p16_application.png -------------------------------------------------------------------------------- /_static/p17_generator-callbacks1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p17_generator-callbacks1.png -------------------------------------------------------------------------------- /_static/p17_inline-callbacks1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p17_inline-callbacks1.png -------------------------------------------------------------------------------- /_static/p18_deferred-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p18_deferred-list.png -------------------------------------------------------------------------------- /_static/p19_deferred-cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p19_deferred-cancel.png -------------------------------------------------------------------------------- /_static/p19_sync-cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p19_sync-cancel.png -------------------------------------------------------------------------------- /_static/p20_callback-loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p20_callback-loop.png -------------------------------------------------------------------------------- /_static/p20_erlang-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p20_erlang-11.png -------------------------------------------------------------------------------- /_static/p20_erlang-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p20_erlang-2.png -------------------------------------------------------------------------------- /_static/p20_erlang-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p20_erlang-3.png -------------------------------------------------------------------------------- /_static/p20_reactor-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p20_reactor-2.png -------------------------------------------------------------------------------- /_static/p20_reactor-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p20_reactor-3.png -------------------------------------------------------------------------------- /_static/p21_haskell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p21_haskell.png -------------------------------------------------------------------------------- /_static/p22_theend1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luocheng/twisted-intro-cn/ae47177c2ecbd414d5ac9d080b79f52acf410b4d/_static/p22_theend1.png -------------------------------------------------------------------------------- /conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # twisted-intro-cn documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Jul 17 16:11:02 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'twisted-intro-cn' 44 | copyright = u'2011, Cheng Luo' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.1' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '1' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | language = 'zh_CN' 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'haiku' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | html_title = u"Twisted 入门|Twisted Introduction" 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | html_short_title = 'Twisted Introduction' 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'twisted-intro-cndoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | # The paper size ('letter' or 'a4'). 173 | #latex_paper_size = 'letter' 174 | 175 | # The font size ('10pt', '11pt' or '12pt'). 176 | #latex_font_size = '10pt' 177 | 178 | # Grouping the document tree into LaTeX files. List of tuples 179 | # (source start file, target name, title, author, documentclass [howto/manual]). 180 | latex_documents = [ 181 | ('index', 'twisted-intro-cn.tex', u'twisted-intro-cn', 182 | u'Cheng Luo', 'manual'), 183 | ] 184 | 185 | # The name of an image file (relative to this directory) to place at the top of 186 | # the title page. 187 | #latex_logo = None 188 | 189 | # For "manual" documents, if this is true, then toplevel headings are parts, 190 | # not chapters. 191 | #latex_use_parts = False 192 | 193 | # If true, show page references after internal links. 194 | #latex_show_pagerefs = False 195 | 196 | # If true, show URL addresses after external links. 197 | #latex_show_urls = False 198 | 199 | # Additional stuff for the LaTeX preamble. 200 | #latex_preamble = '' 201 | 202 | # Documents to append as an appendix to all manuals. 203 | #latex_appendices = [] 204 | 205 | # If false, no module index is generated. 206 | #latex_domain_indices = True 207 | 208 | 209 | # -- Options for manual page output -------------------------------------------- 210 | 211 | # One entry per manual page. List of tuples 212 | # (source start file, name, description, authors, manual section). 213 | man_pages = [ 214 | ('index', 'twisted-intro-cn', u'twisted-intro-cn Documentation', 215 | [u'Cheng Luo'], 1) 216 | ] 217 | -------------------------------------------------------------------------------- /index.rst: -------------------------------------------------------------------------------- 1 | .. twisted-intro-cn documentation master file, created by 2 | sphinx-quickstart on Sun Jul 17 16:11:02 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Twisted 入门! 7 | ============================================ 8 | 本系列是 `Dave `_ 的 "关于 `Twisted `_ 网络框架和异步编程入门教程" 的简体中文翻译, 原文参见: `krondo.com `_. 9 | 10 | 本系列1-14部分由 ``杨晓伟`` 翻译, 15-22部分由 ``罗诚`` 翻译, 献给广大Twisted爱好者! 文中谬误 `敬请指正 `_:-) 11 | 12 | 13 | 目 录: 14 | 15 | .. toctree:: 16 | :maxdepth: 1 17 | 18 | 1. Twisted 理论基础 19 | 2. 异步编程模式与Reactor初探 20 | 3. 初识Twisted 21 | 4. 由twisted支持的客户端 22 | 5. 由Twisted扶持的客户端 23 | 6. 更加“抽象”的运用Twisted 24 | 7. 小插曲,Deferred 25 | 8. 使用Deferred的诗歌下载客户端 26 | 9. 第二个小插曲,deferred 27 | 10. 增强defer功能的客户端 28 | 11. 改进诗歌下载服务器 29 | 12. 改进诗歌下载服务器 30 | 13. 使用Deferred新功能实现新客户端 31 | 14. Deferred用于同步环境 32 | 15. 测试诗歌 33 | 16. Twisted 进程守护 34 | 17. 构造"回调"的另一种方法 35 | 18. Deferreds 全貌 36 | 19. 取消之前的意图 37 | 20. 轮子内的轮子: Twisted和Erlang 38 | 21. 惰性不是迟缓: Twisted和Haskell 39 | 22. 结束 40 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^` where ^ is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. text to make text files 30 | echo. man to make manual pages 31 | echo. changes to make an overview over all changed/added/deprecated items 32 | echo. linkcheck to check all external links for integrity 33 | echo. doctest to run all doctests embedded in the documentation if enabled 34 | goto end 35 | ) 36 | 37 | if "%1" == "clean" ( 38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 39 | del /q /s %BUILDDIR%\* 40 | goto end 41 | ) 42 | 43 | if "%1" == "html" ( 44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 45 | if errorlevel 1 exit /b 1 46 | echo. 47 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 48 | goto end 49 | ) 50 | 51 | if "%1" == "dirhtml" ( 52 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 53 | if errorlevel 1 exit /b 1 54 | echo. 55 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 56 | goto end 57 | ) 58 | 59 | if "%1" == "singlehtml" ( 60 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 61 | if errorlevel 1 exit /b 1 62 | echo. 63 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 64 | goto end 65 | ) 66 | 67 | if "%1" == "pickle" ( 68 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 69 | if errorlevel 1 exit /b 1 70 | echo. 71 | echo.Build finished; now you can process the pickle files. 72 | goto end 73 | ) 74 | 75 | if "%1" == "json" ( 76 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished; now you can process the JSON files. 80 | goto end 81 | ) 82 | 83 | if "%1" == "htmlhelp" ( 84 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished; now you can run HTML Help Workshop with the ^ 88 | .hhp project file in %BUILDDIR%/htmlhelp. 89 | goto end 90 | ) 91 | 92 | if "%1" == "qthelp" ( 93 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 94 | if errorlevel 1 exit /b 1 95 | echo. 96 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 97 | .qhcp project file in %BUILDDIR%/qthelp, like this: 98 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\twisted-intro-cn.qhcp 99 | echo.To view the help file: 100 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\twisted-intro-cn.ghc 101 | goto end 102 | ) 103 | 104 | if "%1" == "devhelp" ( 105 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 106 | if errorlevel 1 exit /b 1 107 | echo. 108 | echo.Build finished. 109 | goto end 110 | ) 111 | 112 | if "%1" == "epub" ( 113 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 117 | goto end 118 | ) 119 | 120 | if "%1" == "latex" ( 121 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 122 | if errorlevel 1 exit /b 1 123 | echo. 124 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 125 | goto end 126 | ) 127 | 128 | if "%1" == "text" ( 129 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 130 | if errorlevel 1 exit /b 1 131 | echo. 132 | echo.Build finished. The text files are in %BUILDDIR%/text. 133 | goto end 134 | ) 135 | 136 | if "%1" == "man" ( 137 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 141 | goto end 142 | ) 143 | 144 | if "%1" == "changes" ( 145 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.The overview file is in %BUILDDIR%/changes. 149 | goto end 150 | ) 151 | 152 | if "%1" == "linkcheck" ( 153 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Link check complete; look for any errors in the above output ^ 157 | or in %BUILDDIR%/linkcheck/output.txt. 158 | goto end 159 | ) 160 | 161 | if "%1" == "doctest" ( 162 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 163 | if errorlevel 1 exit /b 1 164 | echo. 165 | echo.Testing of doctests in the sources finished, look at the ^ 166 | results in %BUILDDIR%/doctest/output.txt. 167 | goto end 168 | ) 169 | 170 | :end 171 | -------------------------------------------------------------------------------- /p01.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | 第一部分 Twist理论基础 3 | ============================= 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /p02.rst: -------------------------------------------------------------------------------- 1 | ===================================== 2 | 第二部分 异步编程初探与reactor模式 3 | ===================================== 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p03.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 第三部分 初识Twisted 3 | =========================== 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p04.rst: -------------------------------------------------------------------------------- 1 | ==================================== 2 | 第四部分 由Twisted支持的客户端 3 | ==================================== 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p05.rst: -------------------------------------------------------------------------------- 1 | ================================== 2 | 第五部分 由Twisted扶持的客户端 3 | ================================== 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p06.rst: -------------------------------------------------------------------------------- 1 | ===================================== 2 | 第六部分 更加“抽象”的运用Twisted 3 | ===================================== 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p07.rst: -------------------------------------------------------------------------------- 1 | =============================== 2 | 第七部分 小插曲,Deferred 3 | =============================== 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p08.rst: -------------------------------------------------------------------------------- 1 | ======================================== 2 | 第八部分 使用Deferred的诗歌下载客户端 3 | ======================================== 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p09.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | 第九部分 第二个小插曲,deferred 3 | =================================== 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p10.rst: -------------------------------------------------------------------------------- 1 | ================================ 2 | 第十部分 增强defer功能的客户端 3 | ================================ 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p11.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | 第十一部分 改进诗歌下载服务器 3 | ============================== 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p12.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | 第十二部分 改进诗歌下载服务器 3 | ============================== 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p13.rst: -------------------------------------------------------------------------------- 1 | ========================================= 2 | 第十三部分 使用Deferred新功能实现新客户端 3 | ========================================= 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p14.rst: -------------------------------------------------------------------------------- 1 | ================================ 2 | 第十四部分 Deferred用于同步环境 3 | ================================ 4 | | 本部分原作参见: dave @ ``_. 5 | | 本部分翻译内容参见 ``杨晓伟`` 的 `博客 `_ :-) 6 | -------------------------------------------------------------------------------- /p15.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | 第十五部分 测试诗歌 3 | ======================== 4 | 你可以从":doc:`p01`"开始阅读;也可以从":doc:`index`"浏览索引. 5 | 6 | 简介 7 | ======== 8 | 在我们探索Twisted的过程中写了很多代码,但目前我们却忽略了一些重要的东西 —— 测试.你也是会怀疑怎样用像 `unittest `_ 这样Python自带的同步框架测试异步代码.答案是你不能.正如我们已经发现的,同步代码和异步代码是不能混合的,至少不容易. 9 | 10 | 幸运地是,Twisted包含自己的测试框架,叫 `trial `_,它支持测试异步代码(当然你也可以用它测试同步代码). 11 | 12 | 我们假设你已经熟悉了 `unittest `_ 的机理和相似的测试框架,它允许你通过定义类创建测试.这个类通常是一个父类(通常叫"`TestCase`")的子类,类中的方法以"`test`"开头并被视作一个测试.框架负责发现所有的测试,一个接一个地运行它们,并伴有可选项 ``setUp`` 和 ``tearDown`` 步骤,之后报告结果. 13 | 14 | 例子 15 | ======== 16 | 你可以在 `tests/test_poetry.py `_ 中找到一些关于测试的例子.为了确保我们所有的例子是自包含的(以便你不用担心PYTHONPAYH设置),我们将所有需要的代码拷贝到测试模块中.当然正常情况,你只需导入需要测试的模块. 17 | 18 | 这个例子既测试诗歌客户端又测试服务器,通过使用客户端从测试服务器抓取一首诗. 为了提供一个可供测试的诗歌服务器, 我们在测试案例中实现 `setUp `_ 方法: 19 | :: 20 | 21 | class PoetryTestCase(TestCase): 22 | 23 | def setUp(self): 24 | factory = PoetryServerFactory(TEST_POEM) 25 | from twisted.internet import reactor 26 | self.port = reactor.listenTCP(0, factory, interface="127.0.0.1") 27 | self.portnum = self.port.getHost().port 28 | 29 | 这个 `setUp` 方法用一首测试诗建立诗歌服务器,然后监听一个随机开放端口.我们保存了端口号,以便实际测试需要时可以利用.当然测试结束时我们会用 `tearDown `_ 清除测试服务器: 30 | :: 31 | 32 | def tearDown(self): 33 | port, self.port = self.port, None 34 | return port.stopListening() 35 | 36 | 这把我们带到了第一个测试, `test_client `_, 用 `get_poetry` 从测试服务器获取诗歌并且验证这就是我们所期望的诗歌: 37 | :: 38 | 39 | def test_client(self): 40 | """The correct poem is returned by get_poetry.""" 41 | d = get_poetry('127.0.0.1', self.portnum) 42 | 43 | def got_poem(poem): 44 | self.assertEquals(poem, TEST_POEM) 45 | 46 | d.addCallback(got_poem) 47 | 48 | return d 49 | 50 | 注意我们的测试函数返回一个 ``deferred``.在 ``trial`` 中,每个测试方法都以回调的方式运行.这意味着 ``reactor`` 正在运行并且我们可以以测试的一部分执行异步操作.我们仅仅需要让框架知道测试是异步的,这可以通过采用常规的Twisted方式 —— 返回 ``deferred`` 来实现. 51 | 52 | ``trial`` 框架在调用 ``tearDown`` 方法之前将等待直到 ``deferred`` 激发,并且当 ``deferred`` 失败时将使测试失败(如,最后一个回调/错误回调对失败).如果我们的 ``deferred`` 反应太慢(默认2分钟)它同样会使测试失败.这意味着如果测试完成,我们知道 ``deferred`` 激发了,因此我们的回调激发了并且运行了 ``assertEquals`` 测试方法. 53 | 54 | 我们的第二个测试, `test_failure `_, 证实 `get_poetry` 以适当的方式失败了,如果不能连接到服务器: 55 | :: 56 | 57 | def test_failure(self): 58 | """The correct failure is returned by get_poetry when 59 | connecting to a port with no server.""" 60 | d = get_poetry('127.0.0.1', -1) 61 | return self.assertFailure(d, ConnectionRefusedError) 62 | 63 | 这里我们打算连接到一个无效端口,之后使用trial提供的 ``assertFailure`` 方法.这个方法类似于熟悉的 ``assertRaises`` 方法但是是针对异步代码的.它返回一个 ``deferred``,如果给定的 ``deferred`` 失败则返回成功,否则返回失败. 64 | 65 | 你可以用trial脚本自己运行这些测试,如下: 66 | :: 67 | 68 | trial tests/test_poetry.py 69 | 70 | 你将看到显示每个测试案例的输出,OK表示测试通过了. 71 | 72 | 讨论 73 | ========= 74 | 由于当谈到基本API时,trial与unittest十分相似,所以开始写测试十分容易.如果你的测试使用异步代码,仅仅返回 ``deferred`` 就可以了,trial将负责其余的事情.你也可以从 ``setUp`` 或 ``tearDown`` 方法返回一个 ``deferred``,如果它们也需要异步. 75 | 76 | 任何来自测试的日志消息将被收集到当前文件夹下的一个文件中,即"`_trial_temp`", trial会自动创建它. 除了打印到屏幕的错误,日志是调试失败测试的实用入口. 77 | 78 | 图33显示了一个正在进行中的假想测试: 79 | 80 | .. _图33: 81 | 82 | .. figure:: _static/p15_test-1.png 83 | 84 | | 图33: 进行中的trial测试 85 | 86 | 如果你之前使用过类似的框架,这是一个熟悉的模型,除了所有测试相关的方法可能返回 ``deferreds``. 87 | 88 | trial框架是一个关于如何"异步运作"的很好例子,包括级联在整个程序中的变化.为了使一个测试(或任何函数,方法)是异步的,它必须: 89 | 90 | 1. 非阻塞并且,通常 91 | 2. 返回一个 ``deferred``. 92 | 93 | 但这意味着无论什么调用,那个函数必须愿意接收一个 ``deferred``,并且非阻塞(如此又好像返回了一个 ``deferred``).如此这般一层又一层.这样就呼唤出现trial一样的框架,可以处理返回 ``deferreds`` 的异步测试. 94 | 95 | 总结 96 | ======== 97 | 这就是关于单元测试的内容.如果你想了解更多关于如何为Twisted代码写单元测试的例子,你只需要看看Twisted代码本身.Twisted框架自带了一套非常庞大的单元测试,而且每个新的发布又会加入很多.由于这些测试在被接受入代码库之前,经过严格的代码评论以及Twisted专家们的仔细审查,故而它们是告诉你如何以正确方式测试Twisted代码的极好例子. 98 | 99 | 在 :doc:`p16` 中,我们将使用Twisted工具将诗歌服务器转化为一个真正的守护进程. 100 | 101 | 参考练习 102 | ========= 103 | 1. 改变测试之一使其失败,然后再次运行 ``trial`` 看看输出结果. 104 | 2. 阅读 `trial 在线文档 `_. 105 | 3. 为我们这个系列中所创建的其他诗歌服务写测试. 106 | 4. 探索Twisted中的 `一些测试 `_. 107 | -------------------------------------------------------------------------------- /p16.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | 第十六部分 Twisted 进程守护 3 | ============================== 4 | 你可以从":doc:`p01`"开始阅读;也可以浏览":doc:`index`"的索引 5 | 6 | 简介 7 | ------ 8 | 目前我们所写的服务器仅仅运行在终端窗口,结果通过 ``print`` 语句输出到屏幕.这对于开发来说已经足够,但对于产品级的部署还远远不够. 健壮的产品级服务器应该: 9 | 10 | #. 运行一个 `daemon `_ 进程,这个进程不与任何终端或用户会话相关.因为没有人愿意当某用户登出时服务自动关闭. 11 | #. 将调试和错误信息发送到一系列滚转日志文件, 或者 `syslog `_ 服务. 12 | #. 放弃过高的权限,比如,在运行前切换到较低权限. 13 | #. 保存它的 `pid `_ 文件以便管理员方便地向 daemon `发送信号 `_. 14 | 15 | 我们可以利用Twisted提供的 ``twistd`` 脚本获得所有以上功能. 但是首先需要稍稍修改我们的代码. 16 | 17 | IService 18 | ----------- 19 | `IService `_ 接口定义了一个可以启动或停止的服务. 这个服务究竟做了些什么? 答案是任何你喜欢的事情——这个接口只需要自提供的一些通用属性和方法,无须用户定义特定的函数. 20 | 21 | 这边有两个需要的属性: ``name`` 和 ``running``.其中 ``name`` 属性是一个字符串,如 "`fastpoetry`",或者 `None` 如果你不想给这个服务起名字. ``running`` 属性是 **Boolean** 变量,如果服务成功启动,值为 `True`. 22 | 23 | 下面我们只涉及 ``IService`` 的某些方法, 跳过那些很显然的或者在更简单的Twisted程序中用不到的高级方法. `startService `_ 和 `stopService `_ 是 ``IService`` 的两个关键方法: 24 | :: 25 | 26 | def startService(): 27 | """ 28 | Start the service. 29 | """ 30 | 31 | def stopService(): 32 | """ 33 | Stop the service. 34 | 35 | @rtype: L{Deferred} 36 | @return: a L{Deferred} which is triggered when the service has 37 | finished shutting down. If shutting down is immediate, a 38 | value can be returned (usually, C{None}). 39 | """ 40 | 41 | 同样,这些方法做什么取决于服务的需求,比如 ``startService`` 可能会: 42 | 43 | * 加载配置数据,或 44 | * 初始化数据库,或 45 | * 开始监听某端口,或 46 | * 什么也不做. 47 | 48 | ``stopService`` 可能会: 49 | 50 | * 储存状态,或 51 | * 关闭打开的数据库连接,或 52 | * 停止监听某端口,或 53 | * 什么也不做. 54 | 55 | 当我们写自定义服务时, 要恰当地实现这些方法.对于一些通用的行为,比如监听某端口,Twisted提供了现成的服务可以使用. 56 | 57 | 注意 ``stopService`` 可以选择地返回 `deferred`,要求当服务完全关闭时被激发.这允许我们的服务在结束之后与整个程序终止之前完成清理工作.如果你需要服务立即关闭,可以仅仅返回 `None` 而不是 `deferred`. 58 | 59 | 服务可以被组织成集合以便一起启动和停止.下面来看看这里最后一个 ``IService`` 方法: `setServiceParent `_,它添加一个服务到集合: 60 | :: 61 | 62 | def setServiceParent(parent): 63 | """ 64 | Set the parent of the service. 65 | 66 | @type parent: L{IServiceCollection} 67 | @raise RuntimeError: Raised if the service already has a parent 68 | or if the service has a name and the parent already has a child 69 | by that name. 70 | """ 71 | 72 | 任何服务都可以有双亲,这意味着服务可以被组织为层级结构.这把我们引向了今天讨论的另一个接口. 73 | 74 | IServiceCollection 75 | --------------------- 76 | `IServiceCollection `_ 接口定义了一个对象,它可包含若干个 ``IService`` 对象.一个服务集合仅仅是一个普通的类容器,具有以下方法: 77 | 78 | * 通过名字查找某服务( `getServiceNamed `_ ) 79 | * 遍历集合中的服务( `__iter__ `_) 80 | * 添加一个服务到集合( `addService `_) 81 | * 从集合中移除一个服务( `removeService `_) 82 | 83 | Application 84 | -------------- 85 | 一个Twisted ``Application`` 不是通过一个单独的接口定义的.相反, ``Application`` 对象需要实现 ``IService`` 和 ``IServiceCollection`` 接口以及一些我们未曾涉及的接口. 86 | 87 | ``Application`` 是一个代表你整个Twisted应用的最顶层的服务. 在你 ``daemon`` 中的所有其他服务将是这个 ``Application`` 对象的儿子(甚至孙子,等等.). 88 | 89 | 其实需要你自己实现 ``Application`` 的机会很小,Twisted已经提供了一个当下常用的实现. 90 | 91 | Twisted Logging 92 | ------------------- 93 | Twisted在其模块 `twistd.python.log `_ 中包含了其自身的日志架构.由于写日志的基本 **API** 非常简单, 我们仅仅介绍一个小例子: `basic-twisted/log.py`,如果你感兴趣更多细节可以浏览Twisted模块. 94 | 95 | 我们也不详细介绍安装日志处理程序的 **API**,因为 `twistd` 脚本会帮我们做. 96 | 97 | FastPoetry 2.0 98 | ----------------------- 99 | 好吧,让我们看看代码.我们已经将快诗服务器升级为使用 `twistd`. 源码在 `twisted-server-3/fastpoetry.py `_. 首先我们有了 `诗歌协议 `_: 100 | :: 101 | 102 | class PoetryProtocol(Protocol): 103 | 104 | def connectionMade(self): 105 | poem = self.factory.service.poem 106 | log.msg('sending %d bytes of poetry to %s' 107 | % (len(poem), self.transport.getPeer())) 108 | self.transport.write(poem) 109 | self.transport.loseConnection() 110 | 111 | 112 | 注意没有使用 ``print`` 语句,而是使用 ``twisted.python.log.msg`` 函数去记录每个新连接. 113 | 114 | 这里是 `工厂类 `_: 115 | :: 116 | 117 | class PoetryFactory(ServerFactory): 118 | 119 | protocol = PoetryProtocol 120 | 121 | def __init__(self, service): 122 | self.service = service 123 | 124 | 正如你看到的,诗不再储存在工厂中,而是储存在一个被工厂引用的服务对象上。注意这边协议是如何通过工厂从服务获得诗歌.最后,看一下 `服务类 `_: 125 | :: 126 | 127 | class PoetryService(service.Service): 128 | 129 | def __init__(self, poetry_file): 130 | self.poetry_file = poetry_file 131 | 132 | def startService(self): 133 | service.Service.startService(self) 134 | self.poem = open(self.poetry_file).read() 135 | log.msg('loaded a poem from: %s' % (self.poetry_file,)) 136 | 137 | | 就像许多其他接口类一样,Twisted提供了一个基类供自定义实现,同时具有方便的默认行为. 138 | | 我们使用 `twisted.application.service.Service `_ 类实现 ``PoetryService``. 139 | 140 | 这个基类提供了所有必要方法的默认实现,所以我们只需要实现个性化的行为.在上面的例子中,我们只重载了 ``startService`` 方法来加载诗歌文件.注:我们仍然调用了相应的基类方法(它为我们设置 ``running`` 属性). 141 | 142 | 另外值得一提的是: ``PoetryService`` 对象不知道关于 ``PoetryProtocol`` 的任何细节.这里服务的任务仅仅是加载诗歌以及为其他需要诗歌的对象提供接口.也就是说, ``PoetryService`` 只关心提供诗歌的更高层的细节,而不是关心诸如通过 **TCP** 连接发送诗歌这样的更底层的细节.所以同样的服务可以被另外的协议使用,如 **UDP** 或 **XML-RPC**.虽然对于简单的服务好处不大,但你可以想象其在更实际服务实现中的优势. 143 | 144 | 如果这是一个典型的Twisted程序,到目前我们看到的代码都不该出现在这个文件里.它们应该在一些模块当中(也许是 ``fastpoetry`` 和 ``fastpoetry.service``).但是,遵循我们的惯例会使这些例子自包含,也就是在一个脚本中包含了所有东西. 145 | 146 | Twisted tac files 147 | ------------------------- 148 | 这个脚本的其余部分包含通常作为完整内容的 ``Twisted tac`` 文件. ``tac`` 文件是一个 ``Twisted Application Configuration`` 文件,它告诉 ``twistd`` 怎样去构建一个应用.作为一个配置文件,它负责选择设置(如端口,诗歌文件位置,等)来以一种特定的方式运行这个应用.换句话说, ``tac`` 代表我们服务的一个特定部署(在这个端口服务这首诗),而不是启动任何诗歌服务的一般脚本. 149 | 150 | 如果我们在同一个域运行多个诗歌服务,我们将为每一个服务准备一个 ``tac`` 文件(因此你可以明白为什么 ``tac`` 文件通常不包含任何一般目的的代码).在我们的例子中, ``tac`` 文件被配置为使 ``poetry/ecstasy.txt`` 运行在回环接口的10000号端口: 151 | :: 152 | 153 | # configuration parameters 154 | port = 10000 155 | iface = 'localhost' 156 | poetry_file = 'poetry/ecstasy.txt' 157 | 158 | 注意 ``twistd`` 并不知道这些特定变量,我们仅仅将这些配置值统一的放在这里.事实上, ``twistd`` 只关心整个文件中的一个变量,我们即将看到.下面我们开始建立我们的应用: 159 | :: 160 | 161 | # this will hold the services that combine to form the poetry server 162 | top_service = service.MultiService() 163 | 164 | 我们的诗歌服务器将包含两个服务, 上文定义的 ``PoetryService``,和一个Twisted的内置服务,它将建立服务我们诗歌的监听套接字.由于这两个服务明显的相关,我们用 ``MultiService`` 将它们组织在一起,一个实现 ``IServiceCollection`` 和 ``IService`` 的类. 165 | 166 | 作为一个服务集合, ``MultiService`` 把我们的诗歌服务组织在一起.同时作为一个服务, ``MultiService`` 启动时将启动它的子服务,关闭时将关闭它的子服务.让我们向服务集合 `添加 `_ 第一个诗歌服务: 167 | :: 168 | 169 | # the poetry service holds the poem. it will load the poem when it is 170 | # started 171 | poetry_service = PoetryService(poetry_file) 172 | poetry_service.setServiceParent(top_service) 173 | 174 | 这是非常简单的内容.我们仅创建了 ``PoetryService``,然后用 ``setServiceParent`` 方法将其添加到服务集合.下面我们添加 **TCP** 监听器: 175 | :: 176 | 177 | # the tcp service connects the factory to a listening socket. it will 178 | # create the listening socket when it is started 179 | factory = PoetryFactory(poetry_service) 180 | tcp_service = internet.TCPServer(port, factory, interface=iface) 181 | tcp_service.setServiceParent(top_service) 182 | 183 | Twisted为创建连接到任意工厂的 **TCP** 监听套接字提供了 ``TCPServer`` 服务(这里是 ``PoetryFactory``),我们没有直接调用 ``reactor.listenTCP`` 因为 ``tac`` 文件的工作是使我们的应用准备好开始,而不是实际启动它. 这里 ``TCPServer`` 将在被 ``twistd`` 启动后创建套接字. 184 | 185 | 你可能注意到我们没有为任何服务起名字.为服务起名不是必需的,而仅是一个可选项,如果你希望在运行时查找服务.因为我们不需要这个功能,所以这里没有为服务命名. 186 | 187 | 既然我们已经将两个服务绑定到服务集合.现只需创建我们的应用,并且将它添加到集合: 188 | :: 189 | 190 | # this variable has to be named 'application' 191 | application = service.Application("fastpoetry") 192 | 193 | # this hooks the collection we made to the application 194 | top_service.setServiceParent(application) 195 | 196 | 在这个脚本中 ``twistd`` 所关心的唯一变量就是 `application`. ``twistd`` 正是通过它找到那个需要启动的应用(所以这个变量必须被命名为 `applicaton`).当应用被启动时,我们添加到它的所有服务都会被启动. 197 | 198 | 图34显示了我们刚刚建立的应用的结构: 199 | 200 | .. _figure34: 201 | 202 | .. figure:: _static//p16_application.png 203 | 204 | | 图34: fastpoetry 应用的结构图 205 | 206 | Running the Server 207 | ------------------------ 208 | 让我们的新服务器运转起来.作为 ``tac`` 文件,我们需要用 ``twistd`` 启动它.当然,它仅仅是一个普通的Python文件.所以我们首先用 ``python`` 命令启动,再看看会发生什么: 209 | :: 210 | 211 | python twisted-server-3/fastpoetry.py 212 | 213 | 如果你这样做,会发现什么也没有发生!正如前文所述, ``tac`` 文件的工作是使我们的应用准备好运行,而不是实际运行它.作为 ``tac`` 文件这个特殊目的的提醒,人们将它的扩展名规定为 `.tac` 而不是 `.py`.但是 ``twistd`` 脚本实际并不区分扩展名. 214 | 215 | 让我们用 ``twistd`` 脚本来实际运行这个服务器: 216 | :: 217 | 218 | twistd --nodaemon --python twisted-server-3/fastpoetry.py 219 | 220 | 运行以上命令后会看到如下输出: 221 | :: 222 | 223 | 2010-06-23 20:57:14-0700 [-] Log opened. 224 | 2010-06-23 20:57:14-0700 [-] twistd 10.0.0 (/usr/bin/python 2.6.5) starting up. 225 | 2010-06-23 20:57:14-0700 [-] reactor class: twisted.internet.selectreactor.SelectReactor. 226 | 2010-06-23 20:57:14-0700 [-] __builtin__.PoetryFactory starting on 10000 227 | 2010-06-23 20:57:14-0700 [-] Starting factory <__builtin__.PoetryFactory instance at 0x14ae8c0> 228 | 2010-06-23 20:57:14-0700 [-] loaded a poem from: poetry/ecstasy.txt 229 | 230 | 需要注意的几点: 231 | 232 | 1. 你可以看到Twisted日志系统的输出, 包括 ``PoetryFactory`` 调用 ``log.msg``.但是我们在 ``tac`` 文件中没有安装 ``logger``, 所以 ``twistd`` 会帮我们安装. 233 | 2. 你可以看到我们的两个主要服务 ``PoetryService`` 和 ``TCPServer`` 启动了. 234 | 3. shell提示符不会返回. 这表明我们的服务器没有以守护进程方式运行. 默认地, ``twistd`` 会以守护进程方式运行服务器(这正是 ``twistd`` 存在的原因), 但是如果你包含"``--nodaemon``" 选项,那么 ``twistd`` 将以一个常规shell进程的方式运行你的服务器,同时会将日志输出导向到标准输出. 这对于调试 ``tac`` 文件非常有用. 235 | 236 | 下面测试取诗服务器, 通过我们的诗歌代理或者 ``netcat`` 命令: 237 | :: 238 | 239 | netcat localhost 10000 240 | 241 | 这将从服务器抓取诗歌,并且你可以看到一行如下的日志: 242 | :: 243 | 244 | 2010-06-27 22:17:39-0700 [__builtin__.PoetryFactory] sending 3003 bytes of poetry to IPv4Address(TCP, '127.0.0.1', 58208) 245 | 246 | 这个日志来自 ``PoetryProtocol.connectionMade`` 方法调用 ``log.msg``.当你向服务器发送更多请求时, 你将看到更多的日志条目. 247 | 248 | 现在可以用 ``Ctrl-C`` 来终止这个服务器. 你可以看到如下输出: 249 | :: 250 | 251 | ^C2010-06-29 21:32:59-0700 [-] Received SIGINT, shutting down. 252 | 2010-06-29 21:32:59-0700 [-] (Port 10000 Closed) 253 | 2010-06-29 21:32:59-0700 [-] Stopping factory <__builtin__.PoetryFactory instance at 0x28d38c0> 254 | 2010-06-29 21:32:59-0700 [-] Main loop terminated. 255 | 2010-06-29 21:32:59-0700 [-] Server Shut Down. 256 | 257 | 正如你看到的, Twisted并没有简单地崩溃, 而是优雅地关闭并将日志信息告诉你. 258 | 259 | 好啦, 现在再次启动服务器: 260 | :: 261 | 262 | twistd --nodaemon --python twisted-server-3/fastpoetry.py 263 | 264 | 现在打开另一个shell并切换到 ``twisted-intro`` 目录. 其中有一个叫 `twistd.pid` 的文件. 它是被 ``twistd`` 创建的, 包含我们这个运行服务器进程号. 试一下下面的方法来关闭服务器: 265 | :: 266 | 267 | kill `cat twistd.pid` 268 | 269 | 注意当服务器关闭后, `twistd.pid` 文件消失了, 它被 ``twistd`` 清理了. 270 | 271 | A Real Daemon 272 | --------------------- 273 | 现在让我们以守护进程的方式启动服务器, 这是 ``twistd`` 的默认方式: 274 | :: 275 | 276 | twistd --python twisted-server-3/fastpoetry.py 277 | 278 | 这次我们立即看到shell提示符返回. 当你列出目录中的文件时,会发现除了 `twistd.pid` 文件,又出现了 `twistd.log` 文件,它记录了之前显示在shell窗口的日志信息. 279 | 280 | 当启动一个守护进程时, ``twistd`` 安装一个日志管理器将条目写入一个文件而不是标准输出. 默认的日志文件是 `twistd.log`, 它出现在你运行 ``twistd`` 的目录中,但是你可以通过"``--logfile``"来改变它的位置. ``twistd`` 安装的的日志管理器将滚动输出日志信息, 确保其不超过 `1M`. 281 | 282 | 你可以通过列出操作系统上的所有进程来查看正在运行的服务器. 你不妨通过取另一首诗来测试这个服务器. 你可以看到记录每个诗歌请求的新条目出现在日志文件中. 283 | 284 | 由于这个服务器不再与shell相连(或者除了 `init `_ 的任何其他进程), 你不能通过 ``Ctrl-C`` 关闭它. 作为一个真的守护进程, 即使你登出它也继续运行.但是你可以通过 `twistd.pid` 文件终止这个进程: 285 | :: 286 | 287 | kill `cat twistd.pid` 288 | 289 | 随后, 关闭消息出现在日志文件中, ``twistd.pid`` 文件被移除, 服务器停止. 290 | 291 | 检查一下其他的 ``twistd`` 启动选项是个不错的主意. 例如,你可以告诉 ``twistd`` 在启动进程守护前切换到另一个用户或组账户(是一种当你的服务器不需要安全防范措施取消权限的典型方法). 我们就不进一步探讨那些额外的选项了,你可以通过 ``twistd`` 的 ``--help`` 自己研究它们. 292 | 293 | Twisted 插件系统 294 | ==================== 295 | 现在我们已经通过 ``twistd`` 启动真正的守护进程服务器. 这非常完美,而且事实上我们的配置文件是纯Python源码文件,这一点为我们设置带来巨大便利. 但是我们有时用不到这样的便利性.对于诗歌服务器,我们通常只关心一小部分选项: 296 | 297 | 1. 需要服务的诗歌 298 | 2. 服务端口 299 | 3. 监听接口 300 | 301 | 为了几个简单的变量建立一个 ``tac`` 文件显得有点小题大做. 如果我们能够通过 ``twistd`` 选项指定这些值将非常方便. Twisted的插件系统允许我们可以这样做. 302 | 303 | Twisted插件通过定义 ``Application`` 提供了一种方法, 可以实现个性化的命令行选项, 进而 ``twistd`` 动态的发现和运行. Twisted本身具有一套插件,你可以通过运行不带参数的 ``twistd`` 命令来查看它们. 现在就试一试, 在 ``twisted-intro`` 目录外. 在帮助部分后面,你可以看到如下输出: 304 | :: 305 | 306 | ... 307 | ftp An FTP server. 308 | telnet A simple, telnet-based remote debugging service. 309 | socks A SOCKSv4 proxy service. 310 | ... 311 | 312 | | 每一行显示了一个Twisted内置的插件, 你可以用 ``twistd`` 运行它们. 313 | | 每个插件同样有它们自己的选项,你可以通过 ``--help`` 来发现它们. 让我们看看 ``ftp`` 插件有什么选项: 314 | 315 | :: 316 | 317 | twistd ftp --help 318 | 319 | 注意我们需要将 ``--help`` 放在 ``ftp`` 后面而不是 ``twistd`` 后面, 因为我们想得到 ``ftp`` 的可选项. 320 | 321 | 我们可以像运行诗歌服务器一样运行 ``ftp`` 服务器. 但由于它是一个插件,我们可以仅仅通过它的名字运行: 322 | :: 323 | 324 | twistd --nodaemon ftp --port 10001 325 | 326 | 以上命令以非守护进程的方式在端口 `10001` 上运行 ``ftp`` 插件. 注意 ``twistd`` 的 `nodaemon` 选项出现在插件名字的前面,插件特定选项 `port` 出现在插件名字的后面. 正如我们的诗歌服务器一样,你可以用 ``Ctrl-C`` 停止它. 327 | 328 | OK, 让我们把诗歌服务器转化为Twisted的插件. 首先我们需要介绍一些新概念. 329 | 330 | IPlugin 331 | ------------ 332 | 任何Twisted插件都需要实现 `twisted.plugin.IPlugin `_ 接口. 如果你浏览这个接口的声明, 你会发现它没有指定任何方法. 实现 ``IPlugin`` 接口仅仅相当于一个插件在说:"你好,我是插件!"以便 ``twistd`` 找到它. 当然,出于实用考虑,它需要实现一些其他接口,我们很快会介绍. 333 | 334 | 但是你怎样知道一个对象实现了一个空接口? ``zope.interface`` 包含了一个叫做 `implements` 的函数,它可以用来声明一个特定类实现了一个特定的接口. 我们将在插件版的诗歌服务器中看到这种使用. 335 | 336 | IServiceMaker 337 | ------------------- 338 | 除了 ``IPlugin``,我们的插件还实现 ``IServiceMaker`` 接口. 一个实现了 ``IServiceMaker`` 接口的对象知道如何创建 ``IService``,它将成为运行程序的核心. ``IServiceMaker`` 指定了三个属性和一个方法: 339 | 340 | 1. `tapname`: 代表插件名字的字符串. "`tap`"代表"Twisted Application Plugin". 注:老版本的Twisted还使用"`tapfiles`"文件,不过这个功能现在已经取消了. 341 | 2. `description`: 插件的描述, ``twistd`` 将以它作为帮助信息输出. 342 | 3. `options`: 一个代表这个插件接受的命令行选项的对象. 343 | 4. `makeService`: 一个创建 ``IService`` 对象的方法,需提供一些特定的命令行选项. 344 | 345 | 我们将在下一个版本的诗歌服务器中看到怎样将上述内容组织在一起. 346 | 347 | Fast Poetry 3.0 348 | ===================== 349 | 现在我们已经为插件版本的"`Fast Poetry`"做好准备,它位于 `twisted/plugins/fastpoetry_plugin.py `_. 350 | 351 | 你可能注意到与其他例子不同, 我们命名了一个不同的目录. 这是因为 ``twistd`` 需要插件文件位于 `twisted/plugins` 目录中, 同时在你的Python搜索路径上. 这个目录不必是一个包(也就是, 不必包含任何 `__init__.py` 文件), 而且在路径上可以有多个 `twisted/plugins` 目录, ``twistd`` 都会找到它们. 这个插件的实际文件名是什么也没有关系, 但是一个好的方案是根据应用所代表的含义来命名, 就像我们在这里做的. 352 | 353 | 我们的插件开头部分同样包括诗歌协议,工厂,以及像 ``tac`` 文件中所实现的服务.如前所述,这些代码通常应该单独的存在于一个模块中,但出于我们例子自包含的目的,还是将它们放在插件文件中. 354 | 355 | 下面将 `声明 `_ 这个插件的命令行选项: 356 | :: 357 | 358 | class Options(usage.Options): 359 | 360 | optParameters = [ 361 | ['port', 'p', 10000, 'The port number to listen on.'], 362 | ['poem', None, None, 'The file containing the poem.'], 363 | ['iface', None, 'localhost', 'The interface to listen on.'], 364 | ] 365 | 366 | | 以上代码指定可以放在 ``twistd`` 命令后面使用的插件特定选项的名字. 367 | | 这里就不必进一步解释上述选项的含义了,其含义很显然. 下面我们来看一下插件的主要部分 `服务制造类 `_: 368 | 369 | :: 370 | 371 | class PoetryServiceMaker(object): 372 | 373 | implements(service.IServiceMaker, IPlugin) 374 | 375 | tapname = "fastpoetry" 376 | description = "A fast poetry service." 377 | options = Options 378 | 379 | def makeService(self, options): 380 | top_service = service.MultiService() 381 | 382 | poetry_service = PoetryService(options['poem']) 383 | poetry_service.setServiceParent(top_service) 384 | 385 | factory = PoetryFactory(poetry_service) 386 | tcp_service = internet.TCPServer(int(options['port']), factory, 387 | interface=options['iface']) 388 | 389 | tcp_service.setServiceParent(top_service) 390 | 391 | return top_service 392 | 393 | 这里你可以看到如何使用 ``zope.interface.implements`` 函数来声明我们的类同时实现 ``IServiceMaker`` 和 ``IPlugin`` 接口. 394 | 395 | 你应该从之前的 ``tac`` 文件辨认出 ``makeService`` 中的代码, 但是这次我们不需要自己建立一个 `Application` 对象, 我们仅仅创建并返回最顶层服务,这样我们的程序就可以运行, ``twistd`` 来处理其余的事情. 注意我们是如何使用 `options` 参数来提取插件传递给 ``twistd`` 的特定命令行选项. 396 | 397 | 定义了上述类, 还有 `一步 `_ : 398 | :: 399 | 400 | service_maker = PoetryServiceMaker() 401 | 402 | ``twistd`` 脚本会发现我们插件的实例并使用它构建最顶层服务. 与 ``tac`` 文件不同的是, 选择什么变量名没有关系, 关键是我们的对象实现了 ``IPlugin`` 和 ``IServiceMaker`` 接口. 403 | 404 | 既然已经创建了插件, 让我们运行它. 确保你位于 `twisted-intro` 目录中, 或者 `twisted-intro` 位于Python的搜索目录中. 下面单独运行 ``twistd``,你会看到"`fastpoetry`"是列出的插件之一,后面显示插件文件中定义的描述文字. 405 | 406 | 你同样会注意到 `twisted/plugins` 目录中出现了一个 `dropin.cache` 的新文件. 这个文件由 ``twistd`` 创建, 用来加速后续扫描插件的. 407 | 408 | 现在让我们获取一些关于插件的帮助信息: 409 | :: 410 | 411 | twistd fastpoetry --help 412 | 413 | 你可以看到关于 `fastpoetry` 插件选项的帮助性文字. 最后,运行这个插件: 414 | :: 415 | 416 | twistd fastpoetry --port 10000 --poem poetry/ecstasy.txt 417 | 418 | 这将以守护进程方式启动 `fastpoetry` 服务器. 与前面例子一样, 你会在当期文件夹看到 `twistd.pid` 和 `twistd.log` 文件. 测试完我们的服务器, 用一下命令关闭: 419 | :: 420 | 421 | kill `cat twistd.pid` 422 | 423 | 这就是如何制作Twisted插件的方法. 424 | 425 | 总 结 426 | ============= 427 | 在这个部分, 我们学习了将Twisted服务器转换到支持长时间运行的守护进程模式. 我们还涉及了Twisted日志系统以及如何使用 ``twistd`` 以守护进程模式启动一个Twisted应用程序, 即或者通过 ``tac`` 配置文件或者Twisted插件. 在 `第十七部分 <`p17`>`_ 部分, 我们将转向异步编程的更基本的主题和另外一种结构化Twisted回调函数的方法. 428 | 429 | 参 考 练 习 430 | ================ 431 | 1. 修正 ``tac`` 文件以在另外一个端口服务另外一首诗. 使用另外一个 ``MultiService`` 对象以保持每首诗的服务是分离的. 432 | 2. 创建一个新的 ``tac`` 文件来启动一个诗歌代理服务器. 433 | 3. 修正插件文件使其可接受第二个可选诗歌文件和服务端口. 434 | 4. 为诗歌代理服务器创建一个新的插件. 435 | -------------------------------------------------------------------------------- /p17.rst: -------------------------------------------------------------------------------- 1 | ===================================== 2 | 第十七部分 构造"回调"的另一种方法 3 | ===================================== 4 | 你可以从":doc:`p01`"开始阅读;也可以浏览":doc:`index`"的索引 5 | 6 | 简介 7 | ========= 8 | 这部分我们将回到"回调"这个主题.我们将介绍另外一种写回调函数的方法,即在Twisted中使用 `generators `_. 我们将演示如何使用这种方法并且与使用"纯" ``Deferreds`` 进行对比. 最后, 我们将使用这种技术重写诗歌客户端. 但首先我们来回顾一下 ``generators`` 的工作原理,以便弄清楚它为何是创建回调的候选方法. 9 | 10 | 简要回顾生成器 11 | -------------------------------- 12 | 你可能知道, 一个Python生成器是一个"可重启的函数",它是在函数体中用 ``yield`` 语句创建的. 这样做可以使这个函数变成一个"生成器函数",它返回一个"`iterator `_"可以用来以一系列步骤运行这个函数. 每个迭代循环都会重启这个函数,继续执行到下一个 ``yield`` 语句. 13 | 14 | 生成器(和迭代器)通常被用来代表以惰性方式创建的值序列. 看一下以下文件中的代码 `inline-callbacks/gen-1.py `_: 15 | :: 16 | 17 | def my_generator(): 18 | print 'starting up' 19 | yield 1 20 | print "workin'" 21 | yield 2 22 | print "still workin'" 23 | yield 3 24 | print 'done' 25 | 26 | for n in my_generator(): 27 | print n 28 | 29 | 这里我们用生成器创建了1,2,3序列. 如果你运行这些代码,会看到在生成器上做迭代时,生成器中的 ``print`` 与循环语句中的 ``print`` 语句交错出现. 30 | 31 | 以下自定义迭代器代码使上面的说法更加明显(`inline-callbacks/gen-2.py `_): 32 | :: 33 | 34 | def my_generator(): 35 | print 'starting up' 36 | yield 1 37 | print "workin'" 38 | yield 2 39 | print "still workin'" 40 | yield 3 41 | print 'done' 42 | 43 | gen = my_generator() 44 | 45 | while True: 46 | try: 47 | n = gen.next() 48 | except StopIteration: 49 | break 50 | else: 51 | print n 52 | 53 | 视作一个序列,生成器仅仅是获取连续值的一个对象.但我们也可以以生成器本身的角度看问题: 54 | 55 | 1. 生成器函数在被循环调用之前并没有执行(使用 ``next`` 方法). 56 | 2. 一旦生成器开始运行,它将一直执行直到返回"循环"(使用 ``yield``) 57 | 3. 当循环中运行其他代码时(如 ``print`` 语句),生成器则没有运行. 58 | 4. 当生成器运行时, 则循环没有运行(等待生成器返回前它被"阻滞"了). 59 | 5. 一旦生成器将控制交还到循环,再启动需要等待任意可能时间(其间任意量的代码可能被执行). 60 | 61 | 这与异步系统中的回调工作方式非常类似. 我们可以把 ``while`` 循环视作 ``reactor``, 把生成器视作一系列由 ``yield`` 语句分隔的回调函数. 有趣的是, 所有的回调分享相同的局部变量名空间, 而且名空间在不同回调中保持一致. 62 | 63 | 进一步,你可以一次激活多个生成器(参考例子 `inline-callbacks/gen-3.py `_),使得它们的"回调"互相交错,就像在Twisted系统中独立运行的异步程序. 64 | 65 | 然而,这种方法还是有一些欠缺.回调不仅仅被 ``reactor`` 调用, 它还能接受信息.作为 ``deferred`` 链的一部分,回调要么接收Python值形式的一个结果,要么接收 ``Failure`` 形式的一个错误. 66 | 67 | 从Python2.5开始,生成器功能被扩展了.当你再次启动生成器时,可以给它发送信息,如 `inline-callbacks/gen-4.py `_ 所示: 68 | :: 69 | 70 | class Malfunction(Exception): 71 | pass 72 | 73 | def my_generator(): 74 | print 'starting up' 75 | 76 | val = yield 1 77 | print 'got:', val 78 | 79 | val = yield 2 80 | print 'got:', val 81 | 82 | try: 83 | yield 3 84 | except Malfunction: 85 | print 'malfunction!' 86 | 87 | yield 4 88 | 89 | print 'done' 90 | 91 | gen = my_generator() 92 | 93 | print gen.next() # start the generator 94 | print gen.send(10) # send the value 10 95 | print gen.send(20) # send the value 20 96 | print gen.throw(Malfunction()) # raise an exception inside the generator 97 | 98 | try: 99 | gen.next() 100 | except StopIteration: 101 | pass 102 | 103 | 在Python2.5以后的版本中, ``yield`` 语句是一个计算值的表达式.重新启动生成器的代码可以使用 ``send`` 方法代替 ``next`` 决定它的值(如果使用 ``next`` 则值为 `None`), 而且你还可以在迭代器内部使用 ``throw`` 方法抛出任何异常. 是不是很酷? 104 | 105 | 内联回调 106 | -------------- 107 | 根据我们刚刚回顾的可以向生成器发送值或抛出异常的特性,可以设想它是像 ``deferred`` 中的一系列回调,即可以接收结果或错误. 每个回调被 ``yield`` 分隔,每一个 ``yield`` 表达式的值是下一个回调的结果(或者 ``yield`` 抛出异常表示错误).图35显示相应概念: 108 | 109 | .. _figure35: 110 | 111 | .. figure:: _static/p17_generator-callbacks1.png 112 | 113 | | 图35:作为回调序列的生成器 114 | 115 | 现在一系列回调以 ``deferred`` 方式被链接在一起,每个回调从它前面的回调接收结果.生成器很容易做到这一点——当再次启动生成器时,仅仅使用 ``send`` 发送上一次调用生成器的结果( ``yield`` 产生的值).但这看起来有点笨,既然生成器从开始就计算这个值,为什么还需要把它发送回来? 生成器可以将这个值储存在一个变量中供下一次使用. 因此这到底是为什么呢? 116 | 117 | 回忆一下我们在 :doc:`p13` 中所学, ``deferred`` 中的回调还可以返回 ``deferred`` 本身. 在这种情况下, 外层的 ``deferred`` 先暂停等待内层的 ``deferred`` 激发,接下来外层 ``deferred`` 链使用内层 ``deferred`` 的返回结果(或错误)激发后续的回调(或错误回调). 118 | 119 | 所以设想我们的生成器生成一个 ``deferred`` 对象而不是一个普通的Python值. 这时生成器会自动"暂停";生成器总是在每个 ``yield`` 语句后暂停直到被显示的重启.因而我们可以延迟它的重启直到 ``deferred`` 被激发, 届时我们会使用 ``send`` 方法发送值(如果 ``deferred`` 成功)或者抛出异常(如果 ``deferred`` 失败).这就使我们的生成器成为一个真正的异步回调序列,这正是 `twisted.internet.defer `_ 中 `inlineCallbacks `_ 函数背后的概念. 120 | 121 | 122 | 进一步讨论内联回调 123 | ==================== 124 | 考虑以下例程, 位于 `inline-callbacks/inline-callbacks-1.py `_: 125 | :: 126 | 127 | from twisted.internet.defer import inlineCallbacks, Deferred 128 | 129 | @inlineCallbacks 130 | def my_callbacks(): 131 | from twisted.internet import reactor 132 | 133 | print 'first callback' 134 | result = yield 1 # yielded values that aren't deferred come right back 135 | 136 | print 'second callback got', result 137 | d = Deferred() 138 | reactor.callLater(5, d.callback, 2) 139 | result = yield d # yielded deferreds will pause the generator 140 | 141 | print 'third callback got', result # the result of the deferred 142 | 143 | d = Deferred() 144 | reactor.callLater(5, d.errback, Exception(3)) 145 | 146 | try: 147 | yield d 148 | except Exception, e: 149 | result = e 150 | 151 | print 'fourth callback got', repr(result) # the exception from the deferred 152 | 153 | reactor.stop() 154 | 155 | from twisted.internet import reactor 156 | reactor.callWhenRunning(my_callbacks) 157 | reactor.run() 158 | 159 | 运行这个例子可以看到生成器运行到最后并终止了 ``reactor``, 这个例子展示了 ``inlineCallbacks`` 函数的很多方面.首先, ``inlineCallbacks`` 是一个修饰符,它总是修饰生成器函数,如那些使用 ``yield`` 语句的函数. ``inlineCallbacks`` 的全部目的是将一个生成器按照上述策略转化为一系列异步回调. 160 | 161 | 第二,当我们调用一个用 ``inlineCallbacks`` 修饰的函数时,不需要自己调用 ``send`` 或 ``throw`` 方法.修饰符会帮助我们处理细节,并确保生成器运行到结束(假设它不抛出异常). 162 | 163 | 第三,如果我们从生成器生成一个非延迟值,它将以 ``yield`` 生成的值立即重启. 164 | 165 | 最后,如果我们从生成器生成一个 ``deferred``,它不会重启除非此 ``deferred`` 被激发.如果 ``deferred`` 成功返回,则 ``yield`` 的结果就是 ``deferred`` 的结果.如果 ``deferred`` 失败了,则 ``yield`` 会抛出异常. 注这个异常仅仅是一个普通的 ``Exception`` 对象,而不是 ``Failure``,我们可以在 ``yield`` 外面用 ``try/except`` 块捕获它们. 166 | 167 | 在上面的例子中,我们仅用 ``callLater`` 在一小段时间之后去激发 ``deferred``.虽然这是一种将非阻塞延迟放入回调链的实用方法,但通常我们会生成一个 ``deferred``,它是被生成器中其他的异步操作(如 `get_poetry`)返回的. 168 | 169 | OK,现在我们知道了 ``inlineCallbacks`` 修饰的函数是如何运行的,但当你实际调用时会返回什么值呢?正如你认为的,将得到 ``deferred``.由于不能确切地知道生成器何时停止(它可能生成一个或多个 ``deferred``),装饰函数本身是异步的,所以 ``deferred`` 是一个合适的返回值.注:这个返回的 ``deferred`` 不是生成器中 ``yield`` 生成的 ``deferred``.相反,它在生成器完全结束(或抛出异常)后才被激发. 170 | 171 | 如果生成器抛出一个异常,那么返回的 ``deferred`` 将激发它的错误回调链,把异常包含在一个 ``Failure`` 中. 但是如果我们希望生成器返回一个正常值,必须使用 ``defer.returnValue`` 函数. 就像普通 ``return`` 语句一样,它也会终止生成器(实际会抛出一个特殊异常).例子 `inline-callbacks/inline-callbacks-2.py `_ 说明了这两种可能. 172 | 173 | 客户端7.0 174 | -------------- 175 | 让我们在新版本的诗歌客户端中加入 ``inlineCallbacks``,你可以在 `twisted-client-7/get-poetry.py `_ 中查看源代码.也许你需要与客户端6.0—— `twisted-client-6/get-poetry.py `_ 进行对比,它们的相对变化位于 `poetry_main `_: 176 | :: 177 | 178 | def poetry_main(): 179 | addresses = parse_args() 180 | 181 | xform_addr = addresses.pop(0) 182 | 183 | proxy = TransformProxy(*xform_addr) 184 | 185 | from twisted.internet import reactor 186 | 187 | results = [] 188 | 189 | @defer.inlineCallbacks 190 | def get_transformed_poem(host, port): 191 | try: 192 | poem = yield get_poetry(host, port) 193 | except Exception, e: 194 | print >>sys.stderr, 'The poem download failed:', e 195 | raise 196 | 197 | try: 198 | poem = yield proxy.xform('cummingsify', poem) 199 | except Exception: 200 | print >>sys.stderr, 'Cummingsify failed!' 201 | 202 | defer.returnValue(poem) 203 | 204 | def got_poem(poem): 205 | print poem 206 | 207 | def poem_done(_): 208 | results.append(_) 209 | if len(results) == len(addresses): 210 | reactor.stop() 211 | 212 | for address in addresses: 213 | host, port = address 214 | d = get_transformed_poem(host, port) 215 | d.addCallbacks(got_poem) 216 | d.addBoth(poem_done) 217 | 218 | reactor.run() 219 | 220 | 在这个新版本里, ``inlineCallbacks`` 生成函数 `get_transformed_poem` 负责取回诗歌并且应用变换(通过变换服务).由于这两个操作都是异步的,我们每次生成一个 ``deferred`` 并且隐式地等待结果.与客户端6.0一样,如果变换失败则返回原始诗歌.我们可以使用 ``try/except`` 语句捕获生成器中的异步错误. 221 | 222 | 我们以先前的方式测试新版客户端. 首先启动一个变换服务: 223 | :: 224 | 225 | python twisted-server-1/tranformedpoetry.py --port 10001 226 | 227 | 然后启动两个诗歌服务器: 228 | :: 229 | 230 | python twisted-server-1/fastpoetry.py --port 10002 poetry/fascination.txt 231 | python twisted-server-1/fastpoetry.py --port 10003 poetry/science.txt 232 | 233 | 现在可以运行新的客户端: 234 | :: 235 | 236 | python twisted-client-7/get-poetry.py 10001 10002 10003 237 | 238 | 试试关闭一个或多个服务器,看一看客户端如何捕获错误. 239 | 240 | 讨论 241 | ----------- 242 | 就像 ``Deferred`` 对象, ``inlineCallbacks`` 函数给我们一种组织异步回调的新方式.同时,如同使用 ``deferred``, ``inllineCallbacks`` 没有改变游戏规则.特别地,我们的回调仍然一次调用一个回调,它们仍然被 ``reactor`` 调用.我们可以通过打印内联回调的回溯跟踪信息来证实这一点,参见脚本 `inline-callbacks/inline-callbacks-tb.py `_.运行此代码你将首先获得一个关于 `reactor.run()` 的回溯,然后是许多帮助函数信息,最后是我们的回调. 243 | 244 | 图29解释了当 ``deferred`` 中一个回调返回另一个 ``deferred`` 时会发生什么,我们调整它来展示当一个 ``inlineCallbacks`` 生成器生成一个 ``deferred`` 时会发生什么,参考图36: 245 | 246 | .. _figure36: 247 | 248 | .. figure:: _static/p17_inline-callbacks1.png 249 | 250 | | 图36: ``inlineCallbacks`` 函数中的控制流 251 | 252 | 同样的图对两种情况都适用,因为它们表示的想法都是一样的 —— 一个异步操作正在等待另一个. 253 | 254 | 由于 ``inlineCallbacks`` 和 ``deferred`` 解决许多相同的问题,在它们之间如何选择呢?下面列出一些 ``inlineCallbacks`` 的潜在优势. 255 | 256 | * 由于回调分享相同的名空间,因此没有必要传递额外状态. 257 | * 回调的顺序很容易看到,因为它总是从上到下执行. 258 | * 节省了每个回调函数的声明和隐式控制流,通常减少输入. 259 | * 可以使用熟悉的 `try/except` 语句处理错误. 260 | 261 | 当然也存在一些缺陷: 262 | 263 | * 生成器中的回调不能被单独调用,这使代码重用比较困难.而构造 ``deferred`` 的代码则能够以任意顺序自由地添加任何回调. 264 | * 生成器的紧致性可能混淆一个事实,其实异步回调非常晦涩.尽管生成器看起来像一个普通的函数序列,但是它的行为却非常不一样. ``inlineCallbacks`` 函数不是一种避免学习异步编程模型的方式. 265 | 266 | 就像任何技术,实践将积累出必要的经验,帮你做出明智选择. 267 | 268 | 总结 269 | ------------ 270 | 在这个部分,我们学习了 ``inlineCallbacks`` 装饰符以及它怎样使我们能够以Python生成器的形式表达一系列异步回调. 271 | 272 | 在 :doc:`p18` 中,我们将学习一种管理 **一组** "并行"异步操作的技术. 273 | 274 | 参考练习 275 | ------------ 276 | 1. 为什么 ``inlineCallbacks`` 函数是复数(形式)? 277 | 2. 研究 `inlineCallbacks `_ 的实现以及它们帮助函数 `_inlineCallbacks `_. 并思考短语"魔鬼在细节处". 278 | 3. 有N个 ``yield`` 语句的生成器中包含多少个回调,假设其中没有循环或者 ``if`` 语句? 279 | 4. 诗歌客户端7.0可能同时运行三个生成器.概念上,它们之间有多少种不同的交错方式?考虑诗歌客户端和 ``inlineCallbacks`` 的实现,你认为实际有多少种可能? 280 | 5. 把客户端7.0中的 `got_poem` 放入到生成器中. 281 | 6. 把 `poem_done` 回调放入生成器.小心!确保处理所有失败情况以便无论怎样 ``reactor`` 都会关闭.与使用 ``deferred`` 关闭 ``reactor`` 对比代码有何不同? 282 | 7. 一个在 ``while`` 循环中使用 ``yield`` 语句的生成器代表一个概念上的无限序列.那么同样的装饰有 ``inlineCallbacks`` 的生成器又代表什么呢? 283 | -------------------------------------------------------------------------------- /p18.rst: -------------------------------------------------------------------------------- 1 | ================================== 2 | 第十八部分 Deferreds 全貌 3 | ================================== 4 | 你可以从 ":doc:`p01`" 开始阅读;也可以浏览 ":doc:`index`" 的索引 5 | 6 | 简介 7 | ========= 8 | 在上一个部分,我们学习了使用生成器构造顺序异步回调的新方法.这样,包括 ``deferreds``,我们现在有两种将异步操作链接在一起的方法. 9 | 10 | 有时,然而,我们需要"并行"的运行一组异步操作.由于Twisted是单线程的,它实际并不会并发运行,但我们希望使用异步I/O在一组任务上尽可能快的工作.以我们的诗歌客户端为例,它从多个服务器同时下载诗歌,而不是一个接一个的方式.这就是使用Twisted下载诗歌的全部特点. 11 | 12 | 作为一个结果,所有诗歌客户端需要解决一个问题:你怎样得知你启动的所有异步操作已经完成?目前我们通过将结果集总到一个列表(如客户端 7.0中的 `结果 `_ 列表)并检查这个列表的长度来解决这个问题.除了收集成功的结果,我们还必须小心地对待失败,否则一个失败将使程序进入死循环,以为还有工作需要做. 13 | 14 | 正如你所料,Twisted包含一个抽象层可以用来解决这个问题,我们来看一看. 15 | 16 | DeferredList 17 | =============== 18 | ``DeferredList`` 类使我们可以将一个 ``defered`` 对象列表视为一个 ``defered`` 对象.通过这种方法我们启动一族异步操作并且在它们全部完成后获得通知(无论它们成功或者失败).让我们看一些例子. 19 | 20 | 在 `deferred-list/deferred-list-1.py `_ 中,可以找到如下代码: 21 | :: 22 | 23 | from twisted.internet import defer 24 | 25 | def got_results(res): 26 | print 'We got:', res 27 | 28 | print 'Empty List.' 29 | d = defer.DeferredList([]) 30 | print 'Adding Callback.' 31 | d.addCallback(got_results) 32 | 33 | 如果运行它,将得到如下输出: 34 | :: 35 | 36 | Empty List. 37 | Adding Callback. 38 | We got: [] 39 | 40 | 注意以下几点: 41 | 42 | * ``DeferredList`` 由Python列表创建.在这种情况下,列表是空的,但我们很快将看到列表元素必须是 ``Deferred`` 对象. 43 | * ``DeferredList`` 本身是一个 ``deferred`` (它继承 ``Deferred``).这意味着你可以像对待普通 ``deferred`` 一样向其添加回调和错误回调. 44 | * 在以上例子中,回调被添加时立即激发,所以 ``DeferredList`` 也必须立即激发.我们一会儿将讨论. 45 | * ``deferred`` 列表的结果本身也是一个列表(空). 46 | 47 | 下面看一下 `deferred-list/deferred-list-2.py `_: 48 | :: 49 | 50 | from twisted.internet import defer 51 | 52 | def got_results(res): 53 | print 'We got:', res 54 | 55 | print 'One Deferred.' 56 | d1 = defer.Deferred() 57 | d = defer.DeferredList([d1]) 58 | print 'Adding Callback.' 59 | d.addCallback(got_results) 60 | print 'Firing d1.' 61 | d1.callback('d1 result') 62 | 63 | 现在我们创建了包含一个 ``deferred`` 元素的 ``DeferredList`` 列表,得到如下输出: 64 | :: 65 | 66 | One Deferred. 67 | Adding Callback. 68 | Firing d1. 69 | We got: [(True, 'd1 result')] 70 | 71 | 注意以下几点: 72 | 73 | * 这次 ``DeferredList`` 没有激发它的回调,直到我们激发列表中的 ``deferred``. 74 | * 结果同样是一个列表,但这次包含一个元素. 75 | * 这个元素是一个元组,它的第二个值是列表中 ``deferred`` 的结果. 76 | 77 | 让我们向列表添加两个 ``deferreds`` (`deferred-list/deferred-list-3.py `_): 78 | :: 79 | 80 | from twisted.internet import defer 81 | 82 | def got_results(res): 83 | print 'We got:', res 84 | 85 | print 'Two Deferreds.' 86 | d1 = defer.Deferred() 87 | d2 = defer.Deferred() 88 | d = defer.DeferredList([d1, d2]) 89 | print 'Adding Callback.' 90 | d.addCallback(got_results) 91 | print 'Firing d1.' 92 | d1.callback('d1 result') 93 | print 'Firing d2.' 94 | d2.callback('d2 result') 95 | 96 | 得到如下输出: 97 | :: 98 | 99 | Two Deferreds. 100 | Adding Callback. 101 | Firing d1. 102 | Firing d2. 103 | We got: [(True, 'd1 result'), (True, 'd2 result')] 104 | 105 | 现在 ``DeferredList`` 的结果非常清晰,至少以我们的使用方式,它是一个列表,元素个数与传入构造器的 ``deferred`` 列表元素个数相同. 而且结果列表的元素包含原始的 ``deferreds`` 结果信息,至少当这些 ``deferred`` 成功返回.这意味着 ``DeferredList`` 本身并不激发直到所有的原始列表中的 ``deferreds`` 都被激发. 而且以一个空列表创建的 ``DeferredList`` 会立即激发,因为它不需要等待任何 ``deferreds``. 106 | 107 | 那么最终结果列表中的元素顺序如何? 考虑以下代码( `deferred-list/deferred-list-4.py `_): 108 | :: 109 | 110 | from twisted.internet import defer 111 | 112 | def got_results(res): 113 | print 'We got:', res 114 | 115 | print 'Two Deferreds.' 116 | d1 = defer.Deferred() 117 | d2 = defer.Deferred() 118 | d = defer.DeferredList([d1, d2]) 119 | print 'Adding Callback.' 120 | d.addCallback(got_results) 121 | print 'Firing d2.' 122 | d2.callback('d2 result') 123 | print 'Firing d1.' 124 | d1.callback('d1 result') 125 | 126 | 这里我们先激发 `d2` 然后再激发 `d1`,注意构造参数中的 ``deferred`` 列表里 `d1`, `d2` 仍是原先的顺序.输出结果如下: 127 | :: 128 | 129 | Two Deferreds. 130 | Adding Callback. 131 | Firing d2. 132 | Firing d1. 133 | We got: [(True, 'd1 result'), (True, 'd2 result')] 134 | 135 | 输出列表中结果的顺序与原始 ``deferred`` 列表顺序相对应,而不是 ``deferred`` 碰巧被激发的顺序.这一点非常好,因为我们可以很容易地将每个结果与生成它的相应的操作联系在一起(如哪首诗来自哪个服务器). 136 | 137 | 好了,那如果列表中一个或多个 ``deferreds`` 失败了怎么办呢? 上面结果中的 `True` 有什么用? 再看一个例子(`deferred-list/deferred-list-5.py `_): 138 | :: 139 | 140 | from twisted.internet import defer 141 | 142 | def got_results(res): 143 | print 'We got:', res 144 | 145 | d1 = defer.Deferred() 146 | d2 = defer.Deferred() 147 | d = defer.DeferredList([d1, d2], consumeErrors=True) 148 | d.addCallback(got_results) 149 | print 'Firing d1.' 150 | d1.callback('d1 result') 151 | print 'Firing d2 with errback.' 152 | d2.errback(Exception('d2 failure')) 153 | 154 | 现在我们以正常结果激发 `d1`,以错误激发 `d2`.先暂时忽略 ``consumerErrors`` 选项,稍候介绍.这里是输出结果: 155 | :: 156 | 157 | Firing d1. 158 | Firing d2 with errback. 159 | We got: [(True, 'd1 result'), (False, >)] 160 | 161 | 这次对应 `d2` 的元组在第二个位置出现了一个 ``Failure``,并且第一个位置是 ``False``.至此 ``DeferredList`` 的工作原理非常清晰(但继续浏览以下讨论): 162 | 163 | * ``DeferredList`` 是以一个 ``deferred`` 对象列表创建的. 164 | * ``DeferredList`` 本身是一个 ``deferred``,它返回的结果是一个列表,长度与 ``deferred`` 列表相同. 165 | * 当原始列表中所有 ``deferred`` 被激发后, ``DeferredList`` 将会被激发. 166 | * 结果列表中的每个元素以相同顺序对应原始列表中相应的 ``deferred``.如果那个 ``deferred`` 成功返回,相应元素是(`True`,result),如果失败则为(`False`,failure). 167 | * ``DeferredList`` 不会失败,因为无论每个 ``deferred`` 的返回结果是什么都会被集总到结果列表中(同样,请看下面讨论). 168 | 169 | 现在让我们讨论一下被传入 ``DeferredList`` 的 ``consumeErrors`` 选项,如果我们运行以上相同代码而不传入此选项(`deferred-list/deferred-list-6.py `_),则得到以下输出: 170 | :: 171 | 172 | Firing d1. 173 | Firing d2 with errback. 174 | We got: [(True, 'd1 result'), (False, >twisted.python.failure.Failure >type 'exceptions.Exception'<<)] 175 | Unhandled error in Deferred: 176 | Traceback (most recent call last): 177 | Failure: exceptions.Exception: d2 failure 178 | 179 | 如果你还记得,"Unhandled error in Deferred"消息是在 ``deferred`` 垃圾回收时被生成的,而且它表示最后一个回调失败了.这个消息告诉我们并没有完全捕获潜在的异步错误.在我们例子中,它是从哪里来的呢? 很明显不是来自 ``DeferredList``,因为它已经成功返回了.所以它一定是来自 `d2`. 180 | 181 | ``DeferredList`` 需要知道它所监视的 ``deferred`` 何时激发. ``DeferredList`` 以通常的方式向每个 ``deferred`` 添加一个回调和错误回调. 默认地,这个回调(或错误)返回原始结果(或错误)在将它们放入最终结果列表之后.由于错误回调返回原始 ``failure`` 后将触发下一个错误回调, `d2` 在它被激发后仍然保持失败状态. 182 | 183 | 但是如果我们将 `consumeErrors=True` 传递给 ``DeferredList``, 它将向每个 ``deferred`` 添加返回 `None` 的错误回调, 即"消耗"掉这个错误并且取消警告信息. 我们同样可以向 `d2` 添加自己的错误回调来处理错误,如 `deferred-list/deferred-list-7.py `_. 184 | 185 | 客户端 8.0 186 | ================ 187 | 获取诗歌客户端8.0发布啦!客户端使用 ``DeferredList`` 去发现所有诗歌何时完成(或失败).新版客户端位于 `twisted-client-8/get-poetry.py `_. 同样,唯一的变化在于 `poetry_main `_, 我们来看一下重要的变化: 188 | :: 189 | 190 | ... 191 | ds = [] 192 | 193 | for (host, port) in addresses: 194 | d = get_transformed_poem(host, port) 195 | d.addCallbacks(got_poem) 196 | ds.append(d) 197 | 198 | dlist = defer.DeferredList(ds, consumeErrors=True) 199 | dlist.addCallback(lambda res : reactor.stop()) 200 | 201 | 你可以与 `客户端 7.0 `_ 中的相应部分比较. 202 | 203 | 在客户端 8.0中,我们不需要 `poem_done` 回调和 `results` 列表.相反,我们把每个从 `get_transformed_poem` 返回的 ``deferred`` 放入 `ds` 列表,之后创建一个 ``DeferredList``.由于 ``DeferredList`` 不会在所有诗歌完成或失败之前激发,我们仅仅向 ``DeferredList`` 添加一个回调以便关闭 ``reactor``. 在我们这个情况中,没有使用 ``DeferredList`` 返回的结果,我们仅仅需要知道所有事情何时结束.仅此而已! 204 | 205 | 讨论 206 | ========= 207 | 可视化 ``DeferredList`` 的工作方式: 208 | 209 | .. _figure37: 210 | 211 | .. figure:: _static/p18_deferred-list.png 212 | 213 | | 图37: ``DeferredList`` 的结果 214 | 215 | 非常简单,真的. 还有一些关于 ``DeferredList`` 的选项我们没有涉及,以及那些改变我们以上所描述行为的选项.我们在参考练习中把这些留给读者自己探索. 216 | 217 | 在 :doc:`p19` 中我们将进一步介绍 ``Deferred`` 类, 包括 Twisted 10.1.0 提出的最新特性. 218 | 219 | 参考练习 220 | ============ 221 | 1. 阅读 ``DeferredList`` 的源代码. 222 | 2. 修改 `deferred-list` 中的例子去实现可选的构造器参数 ``fireOnOneCallback`` 和 ``fireOnOneErrback``. 实现你将用其中一个(或两个都使用)的情景. 223 | 3. 你可以使用 ``DeferredLists`` 列表创建一个 ``DeferredList`` 吗? 如果是这样,结果将是什么? 224 | 4. 修改客户端8.0在所有诗歌完成下载前不打印任意信息. 这次你将使用 ``DeferredList`` 的结果. 225 | 5. 定义 ``DeferredDict`` 的句法并且实现它. 226 | -------------------------------------------------------------------------------- /p19.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | 第十九部分 改变之前的想法 3 | =========================== 4 | 你可以从 ":doc:`p01`" 开始阅读;也可以从 ":doc:`index`" 浏览索引. 5 | 6 | 简介 7 | ==== 8 | | Twisted是一个正在进展的项目,它的开发者会定期添加新的特性并且扩展旧的特性. 9 | | 随着Twisted 10.1.0发布,开发者向 ``Deferred`` 类添加了一个新的特性—— ``cancellation`` ——这正是我们今天要研究的. 10 | 11 | 异步编程将请求和响应解耦了,如此又带来一个新的可能性:在请求结果和返回结果之间,你可能决定不再需要这个结果了.考虑一下 :doc:`p14` 中的诗歌代理服务器.下面是这个如何工作的,至少对于诗歌的第一次请求: 12 | 13 | 1. 一个对诗歌的请求来了. 14 | 2. 这个代理联系实际服务器以得到这首诗 15 | 3. 一旦这首诗完成,将其发送给原发出请求的代理 16 | 17 | 看起来非常完美,但是如果客户端在获得诗歌之前挂了怎么办?也许它们先前请求 `Paradise Lost `_ 的全部内容,随后它们决定实际想要的是 `Kojo `_ 的俳句.我们的代理将陷入下载前者,并且那个慢服务器会等好一会.最好的策略便是关闭连接,让慢服务器回去顺觉. 18 | 19 | 回忆一下 :ref:`figure15`,展示了同步程序控制流的概念.在那张图中我们可以看到函数调用自上而下,异常是自下而上.如果我们希望取消一个同步调用(这仅是假设),控制流的传递方向与函数调用的方向一致,都是从高层传向底层,如图38所示: 20 | 21 | .. _figure38: 22 | 23 | .. figure:: _static/p19_sync-cancel.png 24 | 25 | | 图38:同步程序流,含假想取消操作 26 | 27 | 当然,在同步程序中这是不可能的,因为高层的代码在底层操作结束前没有恢复运行,自然也就没有什么可取消的.但是在异步程序中,高层代码在底层代码完成前具有控制权,至少具有在底层代码完成之前取消它的请求的可能性. 28 | 29 | 在Twisted程序中,底层请求被包含在一个 ``Deferred`` 对象中,你可以将其想象为一个外部异步操作的"句柄". ``deferred`` 中正常的信息流是向下的,从底层代码到高层代码,与同步程序中返回的信息流方向一致.从Twisted 10.1.0开始,高层代码可以反向发送信息 —— 它可以告诉底层代码它不再需要其结果了.如图39: 30 | 31 | .. _figure39: 32 | 33 | .. figure:: _static/p19_deferred-cancel.png 34 | 35 | | 图39: ``deferred`` 中的信息流,包含取消 36 | 37 | 38 | 取消 ``Deferreds`` 39 | ================== 40 | 让我们看一些例程,来了解下取消 ``deferreds`` 的实际工作原理.注:为了运行这些列子以及本部分中的其他代码,你需要安装Twisted 10.1.0或更高 `版本 `_. 考虑 `deferred-cancel/defer-cancel-1.py `_: 41 | :: 42 | 43 | from twisted.internet import defer 44 | 45 | def callback(res): 46 | print 'callback got:', res 47 | 48 | d = defer.Deferred() 49 | d.addCallback(callback) 50 | d.cancel() 51 | print 'done' 52 | 53 | 伴随着新的取消特性, ``Deferred`` 类获得一个名为 ``cancel`` 的新方法.上面代码创建了一个新的 ``deferred``,添加了一个回调,这后取消了这个 ``deferred`` 而没有激发它.输出如下: 54 | :: 55 | 56 | done 57 | Unhandled error in Deferred: 58 | Traceback (most recent call last): 59 | Failure: twisted.internet.defer.CancelledError: 60 | 61 | OK,取消一个 ``deferred`` 看起来像使错误回调链运行,常规的回调根本没有被调用.同样注意到这个错误是: `twisted.internet.defer.CancelledError`,一个意味着 ``deferred`` 被取消的个性化异常(但请继续阅读).让我们添加一个错误回调,如 `deferred-cancel/defer-cancel-2.py `_ 62 | :: 63 | 64 | from twisted.internet import defer 65 | 66 | def callback(res): 67 | print 'callback got:', res 68 | 69 | def errback(err): 70 | print 'errback got:', err 71 | 72 | d = defer.Deferred() 73 | d.addCallbacks(callback, errback) 74 | d.cancel() 75 | print 'done' 76 | 77 | 得到以下输出: 78 | :: 79 | 80 | errback got: [Failure instance: Traceback (failure with no frames): : 81 | ] 82 | done 83 | 84 | 所以我们可以'捕获'从 ``cancel`` 产生的错误回调,就像其他 ``deferred`` 错误一样. 85 | 86 | OK,让我们试试激发 ``deferred`` 然后取消它,如 `deferred-cancel/defer-cancel-3.py `_: 87 | :: 88 | 89 | from twisted.internet import defer 90 | 91 | def callback(res): 92 | print 'callback got:', res 93 | 94 | def errback(err): 95 | print 'errback got:', err 96 | 97 | d = defer.Deferred() 98 | d.addCallbacks(callback, errback) 99 | d.callback('result') 100 | d.cancel() 101 | print 'done' 102 | 103 | 这里我们用常规 ``callback`` 方法激发 ``deferred``,之后取消它.输出结果如下: 104 | :: 105 | 106 | callback got: result 107 | done 108 | 109 | 我们的回调被调用(正如我们所预期的)之后程序正常结束,就像 ``cancel`` 根本没有被调用.所以取消一个 ``deferred`` 好像根本没有效果如果它已经被激发(但请继续阅读!). 110 | 111 | 如果我们在取消 ``deferred`` 之后激发它会怎样?参看 `deferred-cancel/defer-cancel-4.py `_: 112 | :: 113 | 114 | from twisted.internet import defer 115 | 116 | def callback(res): 117 | print 'callback got:', res 118 | 119 | def errback(err): 120 | print 'errback got:', err 121 | 122 | d = defer.Deferred() 123 | d.addCallbacks(callback, errback) 124 | d.cancel() 125 | d.callback('result') 126 | print 'done' 127 | 128 | 这种情况的输出如下: 129 | :: 130 | 131 | errback got: [Failure instance: Traceback (failure with no frames): : 132 | ] 133 | done 134 | 135 | 有意思!与第二个例子的输出一样,当时没有激发 ``deferred``.所以如果 ``deferred`` 被取消了,再激发它没有效果.但是为什么 `d.callback('result')` 没有产生错误,考虑到不能激发 ``deferred`` 大于一次,错误回调链为何没有运行? 136 | 137 | 再次考虑 `figure39`_.用结果或失败激发一个 ``deferred`` 是底层代码的工作,然而取消 ``deferred`` 是高层代码的行为.激发 ``deferred`` 意味着"这是你的结果",然而取消 ``deferred`` 意味着"我不再想要这个结果了".同时记住 ``cancel`` 是一个新特性,所以大部分现有的Twisted代码并没有处理取消的操作.但是Twisted的开发者使我们取消 ``deferred`` 的想法变得有可能,甚至包括那些在Twisted 10.1.0之前写的代码. 138 | 139 | 为了实现以上想法, ``cancel`` 方法实际上做两件事: 140 | 141 | 1. 告诉 ``Deferred`` 对象本身你不想要那个结果,如果它还没有返回(如, ``deferred`` 没有被激发),这样忽略任何回调或错误回调的后续调用. 142 | 2. 同时,可选地,告诉正在产生结果的底层代码需要采取何种步骤来取消操作. 143 | 144 | 由于旧版本的Twisted代码会上前去激发任何已经被取消的 ``deferred``, step#1确保我们的程序不会垮掉如果我们取消一个旧有库中的 ``deferred``. 145 | 146 | 这意味着我们可以随心所欲地取消一个 ``deferred``,同时可以确定不会得到结果如果它还没有到来(甚至那些 **将要** 到来的).但是取消 ``deferred`` 可能并没有取消异步操作.终止一个异步操作需要一个上下文的具体行动.你可能需要关闭网络连接,回滚数据库事务,结束子进程,等等.由于 ``deferred`` 仅仅是一般目的的回调组织者,它怎么知道具体要做什么当你取消它时?或者,换种说法,它怎样将 ``cancel`` 请求传递给首先已经创建和返回了 ``deferred`` 的底层代码? 和我一起说: 147 | :: 148 | 149 | I know, with a callback! 150 | 151 | 152 | 本质上取消 ``Deferreds`` 153 | ======================== 154 | 好吧,首先看一下 `deferred-cancel/defer-cancel-5.py `_: 155 | :: 156 | 157 | from twisted.internet import defer 158 | 159 | def canceller(d): 160 | print "I need to cancel this deferred:", d 161 | 162 | def callback(res): 163 | print 'callback got:', res 164 | 165 | def errback(err): 166 | print 'errback got:', err 167 | 168 | d = defer.Deferred(canceller) # created by lower-level code 169 | d.addCallbacks(callback, errback) # added by higher-level code 170 | d.cancel() 171 | print 'done' 172 | 173 | 这个例子基本上跟第二个例子相同,除了有第三个回调(``canceller``).这个回调是我们在创建 ``Deferred`` 的时候传递给它的,不是之后添加的.这个回调负责执行终止异步操作时所需的上下文相关的具体操作(当然,仅当 ``deferred`` 被实际取消). ``canceller`` 回调是返回 ``deferred`` 的底层代码的必要部分,不是接收 ``deferred`` 的高层代码为其自己添加的回调和错误回调. 174 | 175 | 运行这个例子将产生如下输出: 176 | :: 177 | 178 | I need to cancel this deferred: 179 | errback got: [Failure instance: Traceback (failure with no frames): : 180 | ] 181 | done 182 | 183 | 正如你所看到, 不需要返回结果的 ``deferred`` 被传递给 ``canceller`` 回调.在这里我们可以做任何需要做的事情以便彻底终止异步操作.注意 ``canceller`` 在错误回调链激发前被调用.其实我们可以在取消回调中选择使用任何结果或错误自己激发 ``deferred`` (这样就会优先于 ``CancelledError`` 失败).这两种情况在 `deferred-cancel/defer-cancel-6.py `_ 和 `deferred-cancel/defer-cancel-7.py `_ 中进行了说明. 184 | 185 | 在激发 ``reactor`` 之前先做一个简单的测试.我们将使用 ``canceller`` 回调创建一个 ``deferred``,正常的激发它,之后取消它.你可以在 `deferred-cancel/defer-cancel-8.py `_ 中看到代码.通过检查那个脚本的输出,你将看到取消一个被激发的 ``deferred`` 不会调用 ``canceller`` 回调.这正是我们所要的,因为没什么可取消的. 186 | 187 | | 我们目前看到的例子都没有实际的异步操作. 让我们构造一个调用异步操作的简单程序,之后我们将指出如何使那个操作可取消. 188 | | 参见代码 `deferred-cancel/defer-cancel-9.py `_: 189 | :: 190 | 191 | from twisted.internet.defer import Deferred 192 | 193 | def send_poem(d): 194 | print 'Sending poem' 195 | d.callback('Once upon a midnight dreary') 196 | 197 | def get_poem(): 198 | """Return a poem 5 seconds later.""" 199 | from twisted.internet import reactor 200 | d = Deferred() 201 | reactor.callLater(5, send_poem, d) 202 | return d 203 | 204 | def got_poem(poem): 205 | print 'I got a poem:', poem 206 | 207 | def poem_error(err): 208 | print 'get_poem failed:', err 209 | 210 | def main(): 211 | from twisted.internet import reactor 212 | reactor.callLater(10, reactor.stop) # stop the reactor in 10 seconds 213 | 214 | d = get_poem() 215 | d.addCallbacks(got_poem, poem_error) 216 | 217 | reactor.run() 218 | 219 | main() 220 | 221 | 这个例子中包含了一个 `get_poem` 函数,它使用 ``reactor`` 的 ``callLater`` 方法在被调用5秒钟后异步地返回一首诗.主函数调用 `get_poem`,添加一个回调/错误回调对,之后启动 ``reactor``.我们(同样使用 ``callLater``)安排 ``reactor`` 在10秒钟之后停止.通常我们向 ``deferred`` 添加一个回调来实现,但你很快就会知道我们为何这样做. 222 | 223 | 运行程序(适当延迟后)产生如下输出: 224 | :: 225 | 226 | Sending poem 227 | I got a poem: Once upon a midnight dreary 228 | 229 | 10秒钟后程序终止.现在来试试在诗歌被发送前取消 ``deferred``.只需加入以下代码在2秒钟后取消(在5秒钟延迟发送诗歌之前): 230 | :: 231 | 232 | reactor.callLater(2, d.cancel) # cancel after 2 seconds 233 | 234 | 完整的例子参见 `deferred-cancel/defer-cancel-10.py `_,这将产生如下输出: 235 | :: 236 | get_poem failed: [Failure instance: Traceback (failure with no frames): : 237 | ] 238 | Sending poem 239 | 240 | 这个例子清晰地展示了取消一个 ``deferred`` 并没有取消它背后的异步请求.2秒钟后我们看到了错误回调输出,打印出如我们所料的 ``CancelledError`` 错误.但是5秒钟后我们看到了 `send_poem` 的输出(但是这个 ``deferred`` 上的回调并没有激发). 241 | 242 | 这时我们与 `deferred-cancel/defer-cancel-4.py `_ 的情况一样."取消" ``deferred`` 仅仅是使最终结果被忽略,但实际上并没有终止这个操作.正如我们上面所学,为了得到一个真正可取消的 ``deferred``,必须在它被创建时添加一个 ``cancel`` 回调. 243 | 244 | 那么这个新的回调需要做什么呢? 参考一下关于 ``callLater`` 方法的 `文档 `_. 它的返回值是另一个实现了 ``IDelayedCall`` 的对象,用 ``cancel`` 方法我们可以阻止延迟的调用被执行. 245 | 246 | 这非常简单,更新后的代码参见 `deferred-cancel/defer-cancel-11.py `_.所有相关变化都在 `get_poem` 函数中: 247 | :: 248 | 249 | def get_poem(): 250 | """Return a poem 5 seconds later.""" 251 | 252 | def canceler(d): 253 | # They don't want the poem anymore, so cancel the delayed call 254 | delayed_call.cancel() 255 | 256 | # At this point we have three choices: 257 | # 1. Do nothing, and the deferred will fire the errback 258 | # chain with CancelledError. 259 | # 2. Fire the errback chain with a different error. 260 | # 3. Fire the callback chain with an alternative result. 261 | 262 | d = Deferred(canceler) 263 | 264 | from twisted.internet import reactor 265 | delayed_call = reactor.callLater(5, send_poem, d) 266 | 267 | return d 268 | 269 | 在这个新版本中,我们保存 ``callLater`` 的返回值以便能够在 ``cancel`` 回调中使用. ``cancel`` 回调的唯一工作是调用 `delayed_call.cancel()`. 但是正如之前讨论的,我们可以选择激发自定义的 ``deferred``. 最新版本的程序产生如下输出: 270 | :: 271 | 272 | get_poem failed: [Failure instance: Traceback (failure with no frames): : 273 | ] 274 | 275 | 正如你看到的, ``deferred`` 被取消了并且异步操作被真正地终止了(我们看不到 `send_poem` 的输出了). 276 | 277 | 诗歌代理 3.0 278 | =============== 279 | 正如在简介中所讨论,诗歌代理服务器是实现取消的很好的候选者,因为这可以让我们取消诗歌下载如果事实证明没有人想要它(如客户端已经在我们发送诗歌前关闭了连接).版本 3.0的代理位于 `twisted-server-4/poetry-proxy.py `_,实现了 ``deferred`` 取消. 变化首先位于 `PoetryProxyProtocol `_: 280 | :: 281 | 282 | class PoetryProxyProtocol(Protocol): 283 | 284 | def connectionMade(self): 285 | self.deferred = self.factory.service.get_poem() 286 | self.deferred.addCallback(self.transport.write) 287 | self.deferred.addBoth(lambda r: self.transport.loseConnection()) 288 | 289 | def connectionLost(self, reason): 290 | if self.deferred is not None: 291 | deferred, self.deferred = self.deferred, None 292 | deferred.cancel() # cancel the deferred if it hasn't fired 293 | 294 | 你可以与 `旧版本 `_ 对比一下.两个主要的变化是: 295 | 296 | 1. 保存我们从 `get_poem` 得到的 ``deferred``,以便之后在需要时取消它. 297 | 2. 当连接关闭时取消 ``deferred``.注这个操作同样会取消 ``deferred`` 当我们实际得到诗歌之后,但正如前例所发现的,取消一个被激发的 ``deferred`` 不会有任何效果. 298 | 299 | 现在我们需要确保取消 ``deferred`` 将实际终止诗歌的下载. 所以我们需要改变 `ProxyService `_: 300 | :: 301 | 302 | class ProxyService(object): 303 | 304 | poem = None # the cached poem 305 | 306 | def __init__(self, host, port): 307 | self.host = host 308 | self.port = port 309 | 310 | def get_poem(self): 311 | if self.poem is not None: 312 | print 'Using cached poem.' 313 | # return an already-fired deferred 314 | return succeed(self.poem) 315 | 316 | def canceler(d): 317 | print 'Canceling poem download.' 318 | factory.deferred = None 319 | connector.disconnect() 320 | 321 | print 'Fetching poem from server.' 322 | deferred = Deferred(canceler) 323 | deferred.addCallback(self.set_poem) 324 | factory = PoetryClientFactory(deferred) 325 | from twisted.internet import reactor 326 | connector = reactor.connectTCP(self.host, self.port, factory) 327 | return factory.deferred 328 | 329 | def set_poem(self, poem): 330 | self.poem = poem 331 | return poem 332 | 333 | 同样,可以与 `旧版本 `_ 对比一下. 这个类具有一些新的变化: 334 | 335 | 1. 我们保存 `reactor.connetTCP` 的返回值,一个 `IConnector `_ 对象.我们可以使用这个对象上的 ``disconnect`` 方法关闭连接. 336 | 2. 我们创建带 ``canceler`` 回调的 ``deferred``.那个回调是一个闭包,它使用 ``connector`` 关闭连接. 但首先须设置 `factory.deferred` 属性为 `None`. 否则,工厂会以 "连接关闭"错误回调激发 ``deferred`` 而不是以 ``CancelledError`` 激发. 由于 ``deferred`` 已经被取消了, 以 ``CancelledError`` 激发更加合适. 337 | 338 | 你同样会注意到我们是在 ``ProxyService`` 中创建 ``deferred`` 而不是 ``PoetryClientFactory``. 由于 ``canceler`` 回调需要获取 ``IConnector`` 对象, ``ProxyService`` 成为最方便创建 ``deferred`` 的地方. 339 | 340 | 同时,就像我们之前的例子, ``canceler`` 回调作为一个闭包实现.闭包看起来在取消回调的实现上非常有用. 341 | 342 | 让我们试试新的代理.首先启动一个慢服务器.它需要很慢以便我们有时间取消: 343 | :: 344 | 345 | python blocking-server/slowpoetry.py --port 10001 poetry/fascination.txt 346 | 347 | 现在可以启动代理(记住你需要Twisted 10.1.0): 348 | :: 349 | 350 | python twisted-server-4/poetry-proxy.py --port 10000 10001 351 | 现在我们可以用任何客户端从代理下载一首诗,或者仅使用 `curl`: 352 | :: 353 | 354 | curl localhost:10000 355 | 356 | 几秒钟后,按 ``Ctrl-C`` 停止客户端或者 `curl` 进程. 在终端运行代理你将看到如下输出: 357 | :: 358 | 359 | Fetching poem from server. 360 | Canceling poem download. 361 | 362 | | 你应该看到慢服务器已经停止了向输出打印它所发送诗歌的片段,因为我们的代理挂了. 363 | | 你可以多次启动和停止客户端来证实每个下载每次都被取消了.但是如果你让整首诗运行完,那么代理将缓存它并且在此之后立即发送它. 364 | 365 | 另一个难点 366 | =========== 367 | 以上我们曾不止一次说取消一个已经激发的 ``deferred`` 是没有效果的.然而,这不是十分正确.在 :doc:`p13` 中,我们学习了附加给一个 ``deferred`` 的回调和错误回调也可能返回另一个 ``deferred``.在那种情况下,原始的(外层) ``deferred`` 暂停执行它的回调链并且等待内层 ``deferred`` 激发(参见 `figure28`_). 368 | 369 | 如此, 即使一个 ``deferred`` 激发了发出异步请求的高层代码,它也不能接收到结果,因为在等待内层 ``deferred`` 完成之前回调链暂停了. 所以当高层代码取消这个外部 ``deferred`` 时会发生什么情况呢? 在这种情况下,外部 ``deferred`` 不仅仅是取消它自己(它已经激发了);相反地,这个 ``deferred`` 取消内部的 ``deferred``. 370 | 371 | 所以当你取消一个 ``deferred`` 时,你可能不是在取消主异步操作,而是一些其他的作为前者结果所触发的异步操作.呼! 372 | 373 | 我们可以用一个例子来说明.考虑代码 `deferred-cancel/defer-cancel-12.py `_: 374 | :: 375 | 376 | from twisted.internet import defer 377 | 378 | def cancel_outer(d): 379 | print "outer cancel callback." 380 | 381 | def cancel_inner(d): 382 | print "inner cancel callback." 383 | 384 | def first_outer_callback(res): 385 | print 'first outer callback, returning inner deferred' 386 | return inner_d 387 | 388 | def second_outer_callback(res): 389 | print 'second outer callback got:', res 390 | 391 | def outer_errback(err): 392 | print 'outer errback got:', err 393 | 394 | outer_d = defer.Deferred(cancel_outer) 395 | inner_d = defer.Deferred(cancel_inner) 396 | 397 | outer_d.addCallback(first_outer_callback) 398 | outer_d.addCallbacks(second_outer_callback, outer_errback) 399 | 400 | outer_d.callback('result') 401 | 402 | # at this point the outer deferred has fired, but is paused 403 | # on the inner deferred. 404 | 405 | print 'canceling outer deferred.' 406 | outer_d.cancel() 407 | 408 | print 'done' 409 | 410 | 在这个例子中,我们创建了两个 ``deferred``, `outer` 和 `inner`,并且有一个外部回调返回内部 ``deferred``. 首先,我们激发外部 ``deferred``,然后取消它. 输出结果如下: 411 | :: 412 | 413 | first outer callback, returning inner deferred 414 | canceling outer deferred. 415 | inner cancel callback. 416 | outer errback got: [Failure instance: Traceback (failure with no frames): : 417 | ] 418 | done 419 | 420 | 正如你看到的,取消外部 ``deferred`` 并没有使外部 ``cancel`` 回调被激发. 相反,它取消了内部 ``deferred``,所以内部 ``cancel`` 回调被激发了,之后外部错误回调收到 ``CancelledError`` (来自内部 ``deferred``). 421 | 422 | 你可能需要仔细看一看那些代码,并且做些变化看看如何影响结果. 423 | 424 | 讨论 425 | ====== 426 | 取消 ``deferred`` 是非常有用的操作,使我们的程序避免去做不需要的工作. 然而正如我们看到的,它可能有一点点棘手. 427 | 428 | 需要明白的一个重要事实是取消一个 ``deferred`` 并不意味着取消了它后面的异步操作.事实上,当写这篇文章时,很多 ``deferreds`` 并不会被真的"取消",因为大部分Twisted代码写于Twisted 10.1.0之前并且还没有被升级.这包括很多Twisted本身的APIs!检查文档或源代码去发现"取消 ``deferred``"是否真的取消了背后的请求,还是仅仅忽略它. 429 | 430 | 第二个重要事实是从你的异步APIs返回的 ``deferred`` 并不一定在完整意义上可取消. 如果你希望在自己的程序中实现取消,你应该先研究一下Twisted源代码中的许多例子. ``Cancellation`` 是一个暂新的特性,所以它的模式和最好实践还在制定当中. 431 | 432 | 展望未来 433 | ========= 434 | 现在我们已经学习了关于 ``Deferreds`` 的方方面面以及Twisted背后的核心概念. 这意味着我们没什么需要介绍的了,因为Twisted的其余部分主要包括一些特定的应用,如网络编程或异步数据库处理.故而,在 `接下来 `_ 的部分中,我们想走点弯路,看看其他两个使用异步I/O的系统跟Twisted有何理念相似之处.之后,在尾声中,我们会打个包并且建议一些帮助你继续学习Twisted的方法. 435 | 436 | 参考练习 437 | ========= 438 | 1. 你知道你可以用多种方式拼写"`cancelled`"吗? `真的 `_. 这取决于你的心情. 439 | 2. 细读 `Deferred `_ 类的源代码,关注 ``cancellation`` 的实现. 440 | 3. 在Twisted 10.1.0的 `源码 `_ 中找具有取消回调的 ``deferred`` 的例子.研究它们的实现. 441 | 4. 修改我们诗歌客户端中 `get_poetry` 方法返回的 ``deferred``, 使其可取消. 442 | 5. 做一个基于 `reactor` 的例子展示取消外部 ``deferred``,它被内层 ``deferred`` 暂停了.如果使用 ``callLater`` 你需要小心选择延迟时间,以确保外层 ``deferred`` 在正确的时刻被取消. 443 | 6. 找一个 Twisted 中还不支持"本质上取消操作"的异步API,为它实现本质取消. 并向 Twisted项目 提交一个 `补丁 `_.不要忘记单元测试! 444 | -------------------------------------------------------------------------------- /p20.rst: -------------------------------------------------------------------------------- 1 | ============================================= 2 | 第二十部分 轮子中的轮子: Twisted和Erlang 3 | ============================================= 4 | 你可以从 ":doc:`p01`" 开始阅读;也可以从 ":doc:`index`" 浏览索引. 5 | 6 | 简介 7 | ====== 8 | 在这个系列中,有一个事实我们还没有介绍,即混合同步的"普通Python"代码与异步Twisted代码不是一个简单的任务,因为在Twisted程序中阻滞不定时间将使异步模型的优势丧失殆尽. 9 | 10 | 如果你是初次接触异步编程,那么你得到的知识看起来有一些局限.你可以在Twisted框架内使用这些新技术,而不是在更广阔的一般Python代码世界中.同时,当用Twisted工作时,你仅仅局限于那些专门为作为Twisted程序一部分所写的库,至少如果你想直接从 ``reactor`` 线程调用它们. 11 | 12 | 但是异步编程技术已经存在了很多年并且几乎不局限于Twisted.其实仅在Python中就有令人吃惊数目的异步编程模型. `搜索 `_ 一下就会看到很多. 它们在细节方面不同于Twisted,但是基本的思想(如异步I/O,将大规模数据流分割为小块处理)是一样的.所以如果你需要,或者选择,使用一个不同的框架,你将由于学习了Twisted而具备一个很好的开端. 13 | 14 | 当我们移步Python之外,同样会发现很多语言和系统要么基于要么使用了异步编程模型.你在Twisted学习到的知识将继续为你在异步编程方面开拓更广阔的领域而服务. 15 | 16 | 在这个部分,我们将简单地看一看 `Erlang `_,一种编程语言和运行时系统,它广泛使用异步编程概念,但是以一种独特的方式.请注意我们不是要开始写 `Erlang入门`.而是稍稍探索一下Erlang中包含的一些思想,看看这些与Twisted思想的联系.基本主题就是你通过学习Twisted得到的知识可以应用到学习其他技术. 17 | 18 | 回顾回调 19 | ============ 20 | 考虑 `图6`_ ,回调的图形表示. 是 :doc:`p06` 中介绍的 `诗歌代理3.0 `_ 的回调和 `dataReceived `_ 方法中的顺序诗歌客户端的原理. 每次从一个相连的诗歌服务器下载一小部分诗歌时将激发回调. 21 | 22 | 假设我们的客户端从3个不同的服务器下载3首诗.以 ``reactor`` 的角度看问题(这是在这个系列中一直主张的),我们得到一个单一的大循环,当每次轮到时激发一个或多个回调,如图40: 23 | 24 | .. _figure40: 25 | 26 | .. figure:: _static/p20_reactor-2.png 27 | 28 | | 图40: 以 ``reactor`` 角度的回调 29 | 30 | 此图显示了 ``reactor`` 欢快地运转,每次诗歌到来时它调用 ``dataReceived``. 每次 ``dataReceived`` 调用应用于一个特定的 ``PoetryProtocal`` 类实例. 我们知道一共有3个实例因为我们正在下载3首诗(所以必须有3个连接). 31 | 32 | 以一个Protocol实例的角度考虑这张图.记住每个Protocol只有一个连接(一首诗). 那个实例可“看到”一个方法调用流,每个方法承载着诗歌的下一部分,如下: 33 | :: 34 | 35 | dataReceived(self, "When I have fears") 36 | dataReceived(self, " that I may cease to be") 37 | dataReceived(self, "Before my pen has glea") 38 | dataReceived(self, "n'd my teeming brain") 39 | ... 40 | 41 | 然而这不是严格意义上的Python循环,我们可以将其概念化为一个循环: 42 | :: 43 | 44 | for data in poetry_stream(): # pseudo-code 45 | dataReceived(data) 46 | 47 | 我们可以设想"回调循环",如图41: 48 | 49 | .. _figure41: 50 | 51 | .. figure:: _static/p20_callback-loop.png 52 | 53 | | 图41:一个虚拟回调循环 54 | 55 | 同样,这不是一个 ``for`` 循环或 ``while`` 循环. 在我们诗歌客户端中唯一重要的Python循环是 ``reactor``. 但是我们可以把每个Protocol视作一个虚拟循环,当有诗歌到来时它会启动循环. 根据这种想法, 我们可以用图42重构整个客户端: 56 | 57 | .. _figure42: 58 | 59 | .. figure:: _static/p20_reactor-3.png 60 | 61 | | 图42: ``reactor`` 转动虚拟循环 62 | 63 | 在这张图中,有一个大循环 —— ``reactor`` 和三个虚拟循环 —— 诗歌协议实例个体.大循环转起来,如此,使得虚拟循环也转起来了,就像一组环环相扣的齿轮. 64 | 65 | 进入Erlang 66 | ============ 67 | `Erlang `_,与Python一样,源自一种八十年代创建的一般目的动态类型的编程语言.不像Python的是,Erlang是功能型的而不是面向对象的,并且在句法上类似怀旧的 `Prolog `_, Erlang最初就是由其实现的. Erlang被设计为建立高度可靠的分布式电话系统,这样Erlang包含广泛的网络支持. 68 | 69 | Erlang的一个最独特的特性是一个涉及轻量级进程的并发模型. 一个Erlang进程既不是一个操作系统进程也不是线程.而它是在Erlang运行环境中一个独立运行的函数,它有自己的堆栈.Erlang进程不是轻量级的线程,因为Erlang进程不能共享状态(许多数据类型也是不可变的,Erlang是一种功能性编程语言).一个Erlang进程可以与其他Erlang进程交互,但仅仅是通过发送消息,消息总是,至少概念上,被复制的而不是共享. 70 | 71 | 所以一个Erlang程序看起来如图43: 72 | 73 | .. _figure43: 74 | 75 | .. figure:: _static/p20_erlang-11.png 76 | 77 | | 图43:有3个进程的Erlang程序 78 | 79 | 在此图中,个体进程变成了"真实的".因为进程在Erlang中是第一构造,就像Python中的对象.但运行时变成了"虚拟的",不是由于它不存在,而是由于它不是一个简单的循环.Erlang运行时可能是多线程的,因为它必须去实现一个全面的编程语言,还要负责很多除异步I/O之外的东西.进一步,一个语言运行时也就是允许Erlang进程和代码执行的媒介,而不是像Twisted中的 ``reactor`` 那样的额外构造. 80 | 81 | 所以一个Erlang程序的更好表示如下图44: 82 | 83 | .. _figure44: 84 | 85 | .. figure:: _static/p20_erlang-2.png 86 | 87 | | 图44: 有若干进程的Erlang程序 88 | 89 | 当然, Erlang运行时确实需要使用异步I/O以及一个或多个选择循环,因为Erlang允许你创建 **大量** 进程. 大规模Erlang程序可以启动成千上万的Erlang进程,所以为每个进程分配一个实际地OS线程是问题所在.如果Erlang允许多进程执行I/O,同时允许其他进程运行即便那个I/O阻塞了,那么异步I/O就必须被包含进来了. 90 | 91 | 注我们关于Erlang程序的图说明了每个进程是"靠它自己的力量"运行,而不是被回调旋转着. 随着 ``reactor`` 的工作被归纳成Erlang运行时的结构,回调不再扮演中心角色. 原来在Twisted中需要通过回调解决的问题,在Erlang中将通过从一个进程向另一个进程发送异步消息来解决. 92 | 93 | 一个Erlang诗歌代理 94 | ================== 95 | 让我们看一下Erlang诗歌客户端. 这次我们直接跳入工作版本而不是像在Twisted中慢慢地搭建它.同样,这不是意味着完整版本的Erlang介绍. 但如果这激起了你的兴趣,我们在本部分最后建议了一些深度阅读资料. 96 | 97 | | Erlang客户端位于 `erlang-client-1/get-poetry `_. 为了运行它,你当然需要安装 `Erlang `_. 98 | | 下面代码是 ``main`` 函数代码,与Python客户端中的 ``main`` 函数具有相同的目的: 99 | :: 100 | 101 | main([]) -> 102 | usage(); 103 | 104 | main(Args) -> 105 | Addresses = parse_args(Args), 106 | Main = self(), 107 | [erlang:spawn_monitor(fun () -> get_poetry(TaskNum, Addr, Main) end) || {TaskNum, Addr} <- enumerate(Addresses)], 108 | collect_poems(length(Addresses), []). 109 | 110 | | 如果你从来没有见过Prolog或者相似的语言,那么Erlang的句法将显得有一点奇怪.但是有一些人也这样认为Python. 111 | | ``main`` 函数被两个分离的句群定义,被分号分割. Erlang根据参数选择运行哪一个句群,所以第一个句群只在我们执行客户端时不提供任何命令行参数的情况下运行,并且它只打印出帮助信息.第二个句群是所有实际的行动. 112 | 113 | Erlang函数中的每条语句被逗号分隔,所以函数以句号结尾.让我们看一看第二个句群,第一行仅仅分析命令行参数并且将它们绑定到一个变量(Erlang中所有变量必须大写).第二行使用 ``self`` 函数来获取当下正在运行的Erlang进程(而非OS进程)的ID.由于这是主函数,你可以认为它等价于Python中的 ``__main__`` 模块. 第三行是最有趣的: 114 | :: 115 | 116 | [erlang:spawn_monitor(fun () -> get_poetry(TaskNum, Addr, Main) end) 117 | || {TaskNum, Addr} <- enumerate(Addresses)], 118 | 119 | 这个语句是对Erlang列表的理解,与Python有相似的句法.它产生新的Erlang进程,对应每个需要连接的服务器. 同时每个进程将运行相同的 `get_poetry` 函数, 但是根据特定的服务器用不同的参数.我们同时传递主进程的PID以便新的进程可以把诗歌发送回来(你通常需要一个进程的PID来向它发送消息) 120 | 121 | ``main`` 函数中的最后一条语句调用 ``collect_poems`` 函数,它等待诗歌传回来和 `get_poetry` 进程结束.我们可以看一下其他函数,但首先你可能会对比一下Erlang的 `main `_ 函数与等价地Twisted客户端中的 `main `_ 函数. 122 | 123 | 现在让我们看一下Erlang中的 `get_poetry` 函数.事实上在我们的脚本中有两个函数叫 `get_poetry`.在Erlang中,一个函数被名字和元数同时确定,所以我们的脚本包含两个不同的函数, `get_poetry/3` 和 `get_poetry/4`,它们分别接收3个或4个参数.这里是 `get_poetry/3 `_,它是被 ``main`` 生成的: 124 | :: 125 | 126 | get_poetry(Tasknum, Addr, Main) -> 127 | {Host, Port} = Addr, 128 | {ok, Socket} = gen_tcp:connect(Host, Port, 129 | [binary, {active, false}, {packet, 0}]), 130 | get_poetry(Tasknum, Socket, Main, []). 131 | 132 | 这个函数首先做一个TCP连接,就像Twisted客户端中的 `get_poetry`.但之后,不是返回,而是继续使用那个TCP连接,通过调用 `get_poetry/4 `_,如下: 133 | :: 134 | 135 | get_poetry(Tasknum, Socket, Main, Packets) -> 136 | case gen_tcp:recv(Socket, 0) of 137 | {ok, Packet} -> 138 | io:format("Task ~w: got ~w bytes of poetry from ~s\n", 139 | [Tasknum, size(Packet), peername(Socket)]), 140 | get_poetry(Tasknum, Socket, Main, [Packet|Packets]); 141 | {error, _} -> 142 | Main ! {poem, list_to_binary(lists:reverse(Packets))} 143 | end. 144 | 145 | 这个Erlang函数正在做Twisted客户端中 ``PoetryProtocol`` 的工作,不同的是它使用阻塞函数调用. `gen_tcp:recv` 函数等待在套接字上一些数据的到来(或者套接字关闭),无论要等多长时间.但Erlang中的"阻塞"函数仅阻塞正在运行函数的进程,而不是整个Erlang运行时.那个TCP套接字并不是一个真正的阻塞套接字(你不能在纯Erlang代码中创建一个真正的阻塞套接字).对于Erlang中的每个套接字,在运行时的某处,一个"真正的"TCP套接字被设置为非阻塞模型并且用作选择循环的一部分. 146 | 147 | 但是Erlang进程并不知道这些.它仅仅等待一些数据的到来,如果阻塞了,其他Erlang进程会代替运行.甚至一个进程从不阻塞,Erlang运行时可以在任何时刻自由地在进程间切换.换句话说,Erlang具有一个非协同并发机制. 148 | 149 | 注意 `get_poetry/4`,在收到一小部分诗歌后,继续递归地调用它自己.对于一个急迫的语言程序员这看起来像耗尽内存的良方,但Erlang编译器却可以优化"尾"调用(函数调用一个函数中的最后一条语句)为循环.这照亮了又一个有趣的Erlang客户端和Twisted客户端之间的平行对比.在Twisted客户端中,"虚拟"循环是被 ``reaactor`` 创建的,它一次又一次地调用相同的函数(``dataReceived``).同时在Erlang客户端中,"真正"的运行进程(`get_poetry/4`)形成通过"`尾调优化 `_"一次又一次调用它们自己的循环.感觉怎么样. 150 | 151 | 如果连接关闭了, `get_poetry` 做的最后一件事情是把诗歌发送到主进程.同时结束 `get_poetry` 正在运行的进程,因为剩下没什么可做的了. 152 | 153 | 我们Erlang客户端中剩下的关键函数是 `collect_poems `_: 154 | :: 155 | 156 | collect_poems(0, Poems) -> 157 | [io:format("~s\n", [P]) || P <- Poems]; 158 | collect_poems(N, Poems) -> 159 | receive 160 | {'DOWN', _, _, _, _} -> 161 | collect_poems(N-1, Poems); 162 | {poem, Poem} -> 163 | collect_poems(N, [Poem|Poems]) 164 | end. 165 | 166 | 这个函数被主进程运行,就像 `get_poetry`,它对自身递归循环.它同样阻塞. ``receive`` 告诉进程等待符合给定模式的消息到来,并且从"信箱"中提取消息. 167 | 168 | `collect_poems` 函数等待两种消息: 诗歌和"DOWN"通知.后者是发送给主进程的, 当 `get_poetry` 进程之一由于某种原因死了的情况发送(这是 `spawn_monitor` 的监控部分).通过数 DOWN 消息,我们知道何时所有的诗歌都结束了. 前者是来自 `get_poetry` 进程的包含完整诗歌的消息. 169 | 170 | OK,让我们运行一下Erlang客户端.首先启动3个慢服务器: 171 | :: 172 | 173 | python blocking-server/slowpoetry.py --port 10001 poetry/fascination.txt 174 | python blocking-server/slowpoetry.py --port 10002 poetry/science.txt 175 | python blocking-server/slowpoetry.py --port 10003 poetry/ecstasy.txt --num-bytes 30 176 | 177 | 现在我们可以运行Erlang客户端了,与Python客户端有相似的命令行句法.如果你在Linux或其他UNIX-样的系统,你应该可以直接运行客户端(假设你安装了Erlang并使得它在你的PATH上).在Windows中,你可能需要运行 `escript` 程序,将指向Erlang客户端的路径作为第一个参数(其他参数留给Erlang客户端自身的参数). 178 | :: 179 | 180 | ./erlang-client-1/get-poetry 10001 10002 10003 181 | 182 | 之后,你可以看到如下输出: 183 | :: 184 | 185 | Task 3: got 30 bytes of poetry from 127:0:0:1:10003 186 | Task 2: got 10 bytes of poetry from 127:0:0:1:10002 187 | Task 1: got 10 bytes of poetry from 127:0:0:1:10001 188 | ... 189 | 190 | 这就像之前的Python客户端之一,打印我们得到的每一小部分诗歌的信息.当所有诗歌都结束后,客户端应该打印每首诗的完整内容.注意客户端在所有服务器之间切换,这取决于哪个服务器可以发送诗歌. 191 | 192 | 图45展示了Erlang客户端的进程结构: 193 | 194 | .. _figure45: 195 | 196 | .. figure:: _static/p20_erlang-3.png 197 | 198 | | 图45: Erlang诗歌客户端 199 | 200 | 这张图显示了3个 `get_poetry` 进程(每个服务器一个)和一个主进程.你可以看到消息从诗歌进程流向主进程. 201 | 202 | 那么当一个服务器失败了会发生什么呢? 让我们试试: 203 | :: 204 | 205 | ./erlang-client-1/get-poetry 10001 10005 206 | 207 | 上面命令包含一个活动的端口(假设你没有终止之前的诗歌服务器)和一个未激活的端口(假设你没有在10005端口运行任一服务器). 我们得到如下输出: 208 | :: 209 | 210 | Task 1: got 10 bytes of poetry from 127:0:0:1:10001 211 | 212 | =ERROR REPORT==== 25-Sep-2010::21:02:10 === 213 | Error in process <0.33.0> with exit value: {{badmatch,{error,econnrefused}},[{erl_eval,expr,3}]} 214 | 215 | Task 1: got 10 bytes of poetry from 127:0:0:1:10001 216 | Task 1: got 10 bytes of poetry from 127:0:0:1:10001 217 | ... 218 | 219 | 最终客户端从活动的服务器完成诗歌下载,打印出诗歌并退出.那么 ``main`` 函数是怎样得知那两个进程完成工作了? 那个错误消息就是线索. 这个错误源自当 `get_poetry` 尝试连接到服务器时没有得到期望的值({ok, Socket}),而是得到一个连接被拒绝的错误. 220 | 221 | Erlang进程中一个未处理的异常将使其"崩溃",这意味着进程停止运行并且它们所有资源被回收了.但主进程,它监视所有 `get_poetry` 进程,当任何进程无论因为何种原因停止运行时将收到一个DOWN消息.这样,我们的客户端就退出了而不是一直运行下去. 222 | 223 | 讨论 224 | ====== 225 | 让我们总结一下Twisted和Erlang客户端关于并行化的特点: 226 | 227 | 1. 它们都是同时连接到所有诗歌服务器(或尝试连接). 228 | 2. 它们都是从服务器即刻接收诗歌,无论是哪个服务器发送的. 229 | 3. 它们都是以小段方式处理诗歌,因此必须保存迄今为止收到的诗歌的一部分. 230 | 4. 它们都创建一个"对象"(或者Python对象或者Erlang进程)来为一个特定服务器处理所有工作. 231 | 5. 它们都需要小心地确定诗歌何时结束,无论一个特定的下载成功与否. 232 | 233 | 在最后, 两个客户端中的 ``main`` 函数异步地接收诗歌和"任务完成"通知.在Twisted客户端中这个信息是通过 ``Deferred`` 发送的,而在Erlang中客户端接收来自内部进程消息. 234 | 235 | 注意到两个客户端非常像,无论它们的整体策略还是代码架构.但机理有一点点不同,一个是使用对象, ``deferreds`` 和回调,另一个是使用进程和消息.然而在高层的思想模型方面,两个客户端是十分相似的,如果你熟悉两种语言可以很方便地把一种转化为另一种. 236 | 237 | 甚至 ``reactor`` 模式在Erlang客户端中以小型化形式重现.我们诗歌客户端中的每个Erlang进程终究转变为一个递归循环: 238 | 239 | 1. 等待一些事情发生(一小部分诗歌到来,一首诗传递完毕,另一个进程结束),以及 240 | 2. 采取相应的行动. 241 | 242 | 你可以把 Erlang 程序视作一系列小 ``reactor`` 的大集合,每个都自己旋转着并且偶尔向另一个小 ``reactor`` 发送一个信息(它将以另一个事件来处理这个信息). 243 | 244 | 另外如果你更加深入Erlang,你将发现回调露面了. Erlang的 `gen_server `_ 进程是一个通用的 ``reactor`` 循环,你可以用一系列回调函数来"实例化"它,这是一种在Erlang系统中重复出现的模式. 245 | 246 | 进一步阅读 247 | ============ 248 | 在这个部分我们关注Twisted与Erlang的相似性,但它们毕竟有很多不同.Erlang的一个独特特性之一是它处理错误的方式.一个大的Erlang程序被结构化为一个树形结构的进程组,在高一层有"监管者",在叶子上有"工作者".如果一个工作进程崩溃了,监管进程会注意到并采取相应行动(通常重启失败的进程). 249 | 250 | 如果你有兴趣学习Erlang,那么很幸运.许多关于Erlang的书已经出版或将要出版: 251 | 252 | * `Programming Erlang `_ —— 作者是Erlang的发明者之一.这个语言的精彩入门. 253 | * `Erlang Programming `_ —— 此书补充了 `Armstrong` 的书,并且在许多关键部分深入更多细节. 254 | * `Erlang and OTP in Action `_ —— 此书尚未出版,但我正在等待.前两本书都没有介绍OTP,构造大型应用的Erlang框架.完全披露:这本书的两个作者是我的朋友. 255 | 256 | 关于Erlang先就这么多.在 `下一部分 `_ 我们会看一看Haskell,另一种功能性语言,但与Python和Erlang的感觉都不同.然而,我们将努力去发现一些共同点. 257 | 258 | 建议练习(为高度热情的人) 259 | ========================= 260 | 1. 浏览Erlang和Python客户端,并且确定它们在哪里相同哪里不同.它们是怎样处理错误的(比如连接到诗歌服务器的一个错误)? 261 | 2. 简化Erlang客户端以便它不再打印到来的诗歌片段(故而你也不需要跟踪任务号). 262 | 3. 修改Erlang客户端来计量下载每个诗歌所用的时间. 263 | 4. 修改Erlang客户端打印诗歌,并使诗歌的顺序与它们在命令行给定的相同. 264 | 5. 修改Erlang客户端来打印一个更加可读的错误信息当我们不能连接到诗歌服务器时. 265 | 6. 写一个Erlang版本的诗歌服务器正如我们在Twisted中写的. 266 | 267 | .. _图6: _static/p03_reactor-callback.png 268 | -------------------------------------------------------------------------------- /p21.rst: -------------------------------------------------------------------------------- 1 | ============================================== 2 | 第二十一部分 惰性不是迟缓: Twisted和Haskell 3 | ============================================== 4 | 你可以从 ":doc:`p01`" 开始阅读;也可以从 ":doc:`index`" 浏览索引. 5 | 6 | 简介 7 | ====== 8 | 在上一个部分我们对比了Twisted与 `Erlang `_,并将注意力集中在它们共有的一些思想上.结果表明使用Erlang也是非常简便的,因为异步I/O和反应式编程是Erlang运行时和进程模型的关键元素. 9 | 10 | 今天我们想走得更远一点,去看一看 `Haskell `_ —— 另一种功能性语言,然而与Erlang有很大不同(当然与Python也不同).这里面没有太多的平行概念,但我们仍然会发现藏在下面的异步I/O概念. 11 | 12 | F —— 功能性 13 | ================= 14 | 虽然Erlang是功能性语言,它主要关注可靠的并发模型.Haskell,另一方面,是彻头彻尾功能性的,它无耻地利用了范畴论的概念,如 `函子 `_ 和 `单子 `_. 15 | 16 | 不要慌.我们这里不会涉及那些复杂的东西(虽然我们可以).相反,我们将关注一个Haskell的更加传统的功能性特性:惰性. 像许多功能性语言一样(除了Erlang), Haskell支持 `惰性计算 `_. 在懒惰计算语言中,程序的文字并不过多的描述怎样计算需要计算的东西.具体实施计算的细节一般留给了编译器和运行时系统. 17 | 18 | 同时,需要进一步指出,作为惰性计算推进的运行时可能一次只计算表达式的一部分(惰性的)而不是全部.一般地,运行时只提供维持当前计算继续所需的最小计算量. 19 | 20 | 这里有一个使用Haskell ``head`` 语句的简单例子,这是一个提取列表第一个元素的函数,对于列表[1,2,3](Haskell与Python分享一些列表句法): 21 | :: 22 | 23 | head [1,2,3] 24 | 25 | 如果你安装了GHC Haskell运行时,你可以自己试一试: 26 | :: 27 | 28 | [~] ghci 29 | GHCi, version 6.12.1: http://www.haskell.org/ghc/ : ? for help 30 | Loading package ghc-prim ... linking ... done. 31 | Loading package integer-gmp ... linking ... done. 32 | Loading package base ... linking ... done. 33 | Prelude> head [1,2,3] 34 | 1 35 | Prelude> 36 | 37 | 结果是 `1`, 正如所料. 38 | 39 | Haskell列表的句法包含从前几个元素定义列表的使用功能.例如,列表[2,4,..]是从2开始的偶数序列.到哪结束呢?实际并不结束.Haskell列表[2,4,..]和其他如此表述的都是(概念上)无限列表.你可以在交互式Haskell提示符下计算它,这将试图打印这个表达式的结果如下: 40 | :: 41 | 42 | Prelude> [2,4 ..] 43 | [2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,146, 44 | ... 45 | 46 | 你不得不按 ``Ctrl-C`` 终止计算因为它自己不会停下来.但由于是惰性计算,在Haskell中应用无限列表是没有问题的: 47 | :: 48 | 49 | Prelude> head [2,4 ..] 50 | 2 51 | Prelude> head (tail [2,4 ..]) 52 | 4 53 | Prelude> head (tail (tail [2,4 ..])) 54 | 6 55 | 56 | 这里我们分别获取无限列表的第一、二、三个元素,没看到任何无限循环.这就是惰性计算的本质.Haskell运行时只构造完成 ``head`` 函数所需的列表,而不是先构造整个列表(这将导致无限循环),再将整个列表传递给 ``head``.这个列表的其余部分跟本没有被构造,因为它们对继续推进计算毫无意义. 57 | 58 | 当我们引入 ``tail`` 函数时,Haskell被迫进一步构造列表,但是又一次仅仅构造了满足下一次计算所需的列表.同时,一旦计算结束,列表(未完成的)被丢弃了. 59 | 60 | 这里是一些部分计算无限列表的Haskell代码: 61 | :: 62 | 63 | Prelude> let x = [1..] 64 | Prelude> let y = [2,4 ..] 65 | Prelude> let z = [3,6 ..] 66 | Prelude> head (tail (tail (zip3 x y z))) 67 | (3,6,9) 68 | 69 | ``zip`` 函数将所有列表压缩在一起,之后抓取尾部的尾部的头部.又一次,Haskell没有发生任何问题,仅仅构造了计算所需的列表.我们可以将Haskell运行时"消耗"这些无限列表的过程可视化: 70 | 71 | .. _图46: 72 | 73 | .. figure:: _static/p21_haskell.png 74 | 75 | | 图46: Haskell消耗一些无限列表 76 | 77 | 虽然我们将Haskell运行时画为一个简单的循环,它可能被多线程实现(并且很可能如果你使用GHC版本的Haskell).但这幅图的关键点在于它十分像一个 ``reactor`` 循环,消耗从网络套接字传来的数据片段. 78 | 79 | 你可以把异步I/O和 ``reactor`` 模式视为一种有限形式的惰性计算.异步I/O的格言是:"仅仅推进你所拥有的数据".同时惰性计算的格言是:"仅仅推进你所需的数据".进一步,一个惰性计算语言在任何地方都使用这个格言,并不仅仅是有限范围的I/O. 80 | 81 | 但关键点在于,对于惰性计算语言,做异步I/O没什么大不了的. 编译器和运行时已经被设计为一点一点地处理数据结构,因而惰性地处理到来的I/O数据流是标准问题. 如此Haskell运行时,就像Erlang运行时,简单地集成异步I/O为套接字抽象的一部分. 我们以实现一个Haskell诗歌客户端来展示这个概念. 82 | 83 | Haskell 诗歌 84 | =============== 85 | 我们第一个Haskell诗歌客户端位于 `haskell-client-1/get-poetry.hs `_. 同Erlang一样,我们直接给出了完成版的客户端,如果你希望学习更多,我们指出进一步阅读的参考. 86 | 87 | Haskell同样支持轻量级线程或进程,尽管它们不是Haskell的核心,我们的Haskell客户端为每首需要下载的诗歌创建一个进程.关键函数是 `runTask `_,它连接到一个套接字并且以轻量级线程启动 `getPoetry `_ 函数. 88 | 89 | 在这个代码中,你将看到许多类型定义. Haskell,不像Python和Erlang,是静态类型的.我们没有为每个变量定义类型因为Haskell可以自动地推断没有显示定义的变量(或者报告错误如果不能推断).许多函数包含IO类型(技术上叫单子)因为Haskell要求我们将有副作用的代码从纯函数中干净地分离(如,执行I/O的代码). 90 | 91 | `getPoetry` 函数包含如下行: 92 | :: 93 | 94 | poem <- hGetContents h 95 | 96 | 看起来像从句柄一次读入整首诗(如TCP套接字).但是Haskell,像往常一样,是惰性的.Haskell运行时包含一个或更多实际线程,它们在一个选择循环中执行异步I/O,如此便保存了惰性处理I/O流的可能性. 97 | 98 | | 仅仅为说明异步I/O正在进行,我们引入一个"回调"函数, `gotLine `_,它为诗歌的每一行打印一些任务信息.但这不是一个真正的回调函数,无论我们用不用它程序都会使用异步I/O.甚至叫它"gotLine"反映了一个必要的语言思维,它是Haskell程序外的一部分.无论怎样,我们将一点点清扫它,但先使Haskell客户端运转起来. 99 | | 启动一些慢诗歌服务器: 100 | :: 101 | 102 | python blocking-server/slowpoetry.py --port 10001 poetry/fascination.txt 103 | python blocking-server/slowpoetry.py --port 10002 poetry/science.txt 104 | python blocking-server/slowpoetry.py --port 10003 poetry/ecstasy.txt --num-bytes 30 105 | 106 | 现在编译Haskell客户端: 107 | :: 108 | cd haskell-client-1/ 109 | ghc --make get-poetry.hs 110 | 111 | 这将创建一个二进制 `get-poetry`.最后,针对我们的服务器运行客户端: 112 | :: 113 | 114 | /get-poetry 10001 10002 1000 115 | 116 | 你将看到如下输出: 117 | :: 118 | 119 | Task 3: got 12 bytes of poetry from localhost:10003 120 | Task 3: got 1 bytes of poetry from localhost:10003 121 | Task 3: got 30 bytes of poetry from localhost:10003 122 | Task 2: got 20 bytes of poetry from localhost:10002 123 | Task 3: got 44 bytes of poetry from localhost:10003 124 | Task 2: got 1 bytes of poetry from localhost:10002 125 | Task 3: got 29 bytes of poetry from localhost:10003 126 | Task 1: got 36 bytes of poetry from localhost:10001 127 | Task 1: got 1 bytes of poetry from localhost:10001 128 | ... 129 | 130 | 输出与前一个异步客户端有点不同,因为我们只打印一行而不是任意块的数据.但,你可以清楚地看到,客户端是从所有服务器一起处理数据,而不是一个接一个.你同样可以注意到客户端立即打印第一首完成的诗,不等其他还在继续处理的诗. 131 | 132 | 好了,让我们清除还剩下的一点讨厌东西并且发布一个仅仅抓取诗歌而不介意任务序号的新版本.它位于 `haskell-client-2/get-poetry.hs `_. 注意它短多了,对于每个服务器,仅仅连接到套接字,抓取所有数据,之后将其发送回去. 133 | 134 | OK,让我们编译新的客户端: 135 | :: 136 | 137 | cd haskell-client-2/ 138 | ghc --make get-poetry.hs 139 | 140 | 针对相同的诗歌服务器组运行它: 141 | :: 142 | 143 | ./get-poetry 10001 10002 10003 144 | 145 | 最终,你将看到屏幕上出现每首诗的文字. 146 | 147 | 你将注意到每个服务器同时向客户端发送数据.更重要的,客户端以最快速度打印出第一首诗的每一行,而不去等待其余的诗,甚至当它正在处理其它两首诗.之后它快速地打印出之前积累的第二首诗. 148 | 149 | 同时这所有发生的一切都不需要我们做什么.这里没有回调,没有传来传去的消息,仅仅是一个关于我们希望程序做什么的简洁地描述,而且很少需要告诉它应该怎样做.其余的事情都是由Haskell编译器和运行时处理的.漂亮! 150 | 151 | 152 | 讨论与进一步阅读 153 | ==================== 154 | 从Twisted到Erlang之后到Haskell,我们可以看到一个平行的移动,从前景到背景逐步深入异步编程背后的思想.在Twisted中,异步编程是其存在的核心激励理念. Twisted实现作为一个与Python分离的框架(Python缺乏核心的异步抽象如轻量级线程),将异步模型置于首位与核心,当你用Twisted写程序时. 155 | 156 | 在Erlang中,异步对于程序员仍然是可见的,但细节成为语言材料的一部分和运行时系统,形成一个抽象使得异步消息在同步进程之间交换. 157 | 158 | 最后,在Haskell中,异步I/O仅仅是运行时中的另一个技术,大部分对于程序员是不可见的,因为提供惰性计算是Haskell的中心理念. 159 | 160 | 对于以上情况,我们还没有介绍任何深邃的思想.我们仅仅指出许多并且有趣的异步模型出现的地方,这种模型可以被多种方式表达. 161 | 162 | | 如果任何这些激起你对Haskell的兴趣,那么我们建议"`Real World Haskell `_"继续你的学习.这本书是介绍语言学习的典范. 163 | | 同时虽然我没有读过它,我却听说"`Learn You a Haskell `_"的饱受赞誉. 164 | 165 | 现在到了结束探索Twisted之外异步系统的时刻,并且完成了本系列的倒数第二部分. 在":doc:`p22`"中,我们将做一个总结,以及建议一些学习Twisted的方法. 166 | 167 | 建议练习(献给令人吃惊的狂热者) 168 | ================================ 169 | 1. 互相对比Twisted,Erlang和Haskell客户端. 170 | 2. 修改Haskell客户端来处理连接诗歌服务器的失败,以便它们能够下载所有的能够下载的诗歌并为那些不能下载的诗歌输出合理的错误消息. 171 | 3. 写Haskell版本的对应Twisted中的诗歌服务器. 172 | -------------------------------------------------------------------------------- /p22.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | 第二十二部分 结束 3 | ======================== 4 | 你可以从":doc:`p01`"开始阅读;也可以浏览":doc:`index`"的索引 5 | 6 | 全部完成 7 | ========== 8 | 呼呼! 感谢你一路支持. 在我开始时完全没有想到这个系列会这样长,会花这么多时间完成,但是创建这个系列的过程使我非常享受,也希望你喜欢它. 9 | 10 | 既然我已经完成了,我会进一步考虑将其转化为PDF格式.然而,不保证. 11 | 12 | 最后,我想总结一些帮助你继续学习Twisted的建议. 13 | 14 | 进一步阅读 15 | ============== 16 | 首先,我建议阅读Twisted的 `在线文档 `_. 虽然它备受指责,但我觉得这总比饱受赞誉要好. 17 | 18 | 如果你希望使用Twisted进行网络编程, 那么 **Jean-Paul Calderone** 的广受关注的系列 `Twisted网络编程60秒 `_ 是不错的选择. 虽然我觉得60秒可能读不完. 19 | 20 | 但比以上更重要的是,我认为,是阅读Twisted `源码 `_, 因为这些源码是被非常熟悉Twisted的人写的,其中的任何例子都会告诉你怎样用"Twisted的方式"做事情. 21 | 22 | 参考练习 23 | ============== 24 | 1. 将你写过的一个同步程序转化为使用Twisted. 25 | 2. 从零开始,写一个Twisted程序. 26 | 3. 从Twisted的 `错误数据库 `_ 拾起一个错误,并修改它. 给Twisted的开发者提交一个补丁, 不要忘记阅读贡献源代码的 `操作流程 `_. 27 | 28 | 真的要结束了 29 | ============== 30 | 祝你编码快乐! 31 | 32 | .. _figure47: 33 | 34 | .. figure:: _static/p22_theend1.png 35 | 36 | | 图47: 结束 37 | --------------------------------------------------------------------------------