精华内容
下载资源
问答
  • how tomcat work 中文版

    2020-04-08 22:18:33
  • This book is dedicated to the generous people that made the Git community such an awesome environment to work within. You have helped create one of the most useful tools in the tech world. Thank you! ...

    This book is dedicated to the generous people that made the Git community such an awesome environment to work within. You have helped create one of the most useful tools in the tech world. Thank you!

    本书献给那些慷慨的人,是他们把Git社区变成如此一个棒的可以工作的环境。在你们的帮助下才创建了科技世界里最有用的工具之一。歇息你们!

    目录

    Part I: Version Control with Git
    Chapter 1: Version Control Systems
    What is Version Control?
    Why do you need one?
    What are the choices?
    Local Version Control Systems
    Centralized Version Control Systems
    Distributed Version Control Systems
    What is Git?
    What can Git do?
    How does Git work?
    What is the typical Git workflow?
    Summary
    Chapter 2: Installation and Setup
    Installation
    Windows
    Mac
    Linux
    Setting up Git
    Summary
    Chapter 3: Getting Started
    Repositories
    Working Directory
    Staging Area
    Commits
    Quick start with Git
    Summary
    Chapter 4: Diving into Git
    Ignoring files
    Checking logs and history
    Viewing previous versions
    Reviewing the current changes
    Summary
    Chapter 5: Commits
    The three states of Git
    Navigating between versions
    Undo a commit
    Modifying a commit
    Amending a commit
    Summary
    Chapter 6: Git Best Practices
    Commit messages
    Git commit best practices
    What to do
    What not to do
    How Git works (again)
    Summary
    Chapter 7: Remote Git
    Why work on remote
    How does it work
    The easy way
    Summary
    Part II: Project Management with GitHub
    Chapter 8: GitHub Primer
    GitHub overview
    GitHub and Open Source
    Personal use
    GitHub for businesses
    Summary
    Chapter 9: Quick Start with GitHub
    Project management
    How remote repositories work
    Linking repositories
    Pushing to remote repositories
    Summary
    Chapter 10: Beginning Project Management: Issues
    Overview on issues
    Creating an Issue
    Interacting with an issue
    Labels
    Assignees
    Linking issues with commits 1
    Working on the commit
    Referencing an issue
    Closing an issue using keywords
    Summary
    Chapter 11: Diving into Project Management: Branches
    GitHub workflow
    Branches
    Creating a branch
    Switching to another branch
    Deleting a branch
    Merging branches
    Pushing a branch to remote
    Summary
    Chapter 12: Better Project Management: Pull Requests
    Why use Pull Requests?
    Overview on Pull Requests
    Pull
    What does a PR do
    Create a Pull Request
    Code Reviews
    Give a Code Review
    Leave a review comment
    Update a Pull Request
    Summary
    Part III: Teamwork with Git
    Chapter 13: Conflicts
    How a merge works
    Pulling
    Fast-forward merge
    Merge conflicts
    Pulling commits from origin
    Resolving merge conflicts
    Summary
    Chapter 14: More About Conflicts
    Pushing after a conflict resolution
    Review changes before merge
    Check branch location
    Review branch diff
    Understand Merging
    Reducing conflicts
    Having a good workflow
    Aborting a merge
    Using a visual Git tool
    Summary
    Chapter 15: Git GUI Tools
    Default tools
    Committing: git-gui
    Browsing: gitk
    IDE tools
    Visual Studio Code
    Atom
    Specialized tools
    GitHub Desktop
    GitKraken
    Summary

    Chapter 16: Advanced Git
    Reverting
    Stashing
    Resetting
    Summary

    Part IV: Additional Resources
    Chapter 17: More with GitHub
    Wikis
    GitHub Pages
    Releases
    Project Boards
    Summary
    Chapter 18: Common Git Problems
    Repository
    Starting over
    Change origin
    Working Directory
    Git diff is empty
    Undo changes to a file
    Commits
    Error in commit
    Undo commits
    Branches
    Detached HEAD
    Worked on wrong branch
    Catch up with parent branch
    Branches have diverged
    Summary
    Chapter 19: Git and GitHub Workflow
    How to use this workflow
    GitHub workflow
    Every project starts with a project
    Every action starts with an Issue
    No direct push to master
    Any merge into master needs a PR
    Use the wiki to document your code
    Git workflow
    Always know where you are
    Pull remote changes before any action
    Take care of your commit message
    Don’t rewrite history
    Summary
    Index
     

    Chapter 1:版本控制系统

    顾名思义,版本控制是对一个项目的多个版本的管理。要管理版本,必须跟踪项目中文件的每次更改(添加、编辑或删除)。版本控制记录对一个文件(或一组文件)所做的每个更改,并提供撤消或回滚每个更改的方法。

    为了实现有效的版本控制,您必须使用称为版本控制系统的工具。它们可以帮助您在更改之间导航,并在出现问题时让您快速返回到以前的版本。

    除此之外,使用版本控制最重要的优点之一是团队合作。当一个项目中有多个人参与时,跟踪更改就变成了一场噩梦,它大大增加了覆盖另一个人更改的可能性。使用版本控制,多个人可以处理他们的项目副本(称为分支),并且只有当他们(或其他团队成员)对工作满意时才能将这些更改合并到主项目中。

    注意:这本书是从开发人员的角度写的,但其中的所有内容都适用于任何文本文件,而不仅仅是代码。版本控制系统甚至可以跟踪许多非文本文件(如图像或Photoshop文件)的更改。

    回忆一下,您是否曾经处理过一个文本项目或一个需要您回忆对每个文件所做的特定更改的代码?如果是,您是如何管理和控制每个版本的?也许你试着用后缀“review”、“fixed”或“final”来复制和重命名文件?图1-1显示了这种版本控制。

    这样做有一个问题,很容易忘记哪个文件是哪个文件以及它们之间发生了什么变化。

    另外一个想法是压缩文件并在名称上附加时间戳,以便按创建日期排列版本。图1-2显示了这种对版本的跟踪。

    图1-2所示的解决方案似乎是一个完美的系统,直到您意识到即使跟踪版本,也无法知道每个版本的内容和描述。

    为了纠正这种情况,一些开发人员使用了如图1-3所示的解决方案,即将每个版本的更改摘要放在一个单独的文件中。

    如图1-3所示,项目文件夹附带一个单独的文件,简要描述所做的更改。还要注意包含项目早期版本的许多压缩文件。

    应该可以的,对吧?不完全是这样,您仍然需要一种方法来比较每个版本和每个文件更改。在那个系统里没有办法做到这一点,你只需要记住你所做的一切。如果项目越来越大,每个版本的文件夹都会越来越大。

    当其他开发人员或编写人员加入您的团队时会发生什么?你会把你编辑的文件或版本发邮件给对方吗?或者在同一个远程文件夹上工作?在最后一个例子中,您如何知道谁在处理哪个文件以及更改了什么?

    最后,你有没有觉得有必要在不破坏过程中的一切的情况下,撤销几年前所做的改变?一个无限和全能的ctrl-z?

    所有这些问题都通过使用版本控制系统或VCS来解决。VCS跟踪您对项目的每个文件所做的每个更改,并提供一种简单的方法,它可以对比并回滚这些更改。项目的每个版本还附带所做更改的描述以及新文件或已编辑文件的列表。当更多的人加入这个项目时,风投可以准确地显示谁在特定时间编辑了特定的文件。所有这些都让你为你的项目赢得了宝贵的时间,因为你可以专注于写作,而不是花时间跟踪每一个变化。图1-4显示了一个由Git管理的版本化项目。

    如图1-4所示,一个版本化项目结合了我们在本章中尝试的所有解决方案。有变更描述、团队合作和编辑日期。

    版本控制系统有很多种,每种都有各自的优点和缺点。VCS可以是本地的、集中式的或分布式的。

    1. Local Version Control Systems(本地版本控制系统)

    这些是为管理源代码而创建的第一个VCS。他们通过跟踪本地存储的单个数据库中对文件所做的更改来工作。这意味着所有的更改都保存在一台计算机中,如果出现问题,所有的工作都将丢失。这也意味着与团队合作是不可能的。

    2. Centralized Version Control Systems(集中式版本控制系统)

    集中式VCS(centralizedvcs)的工作原理是将更改历史存储在客户机(作者)可以连接的单个服务器上。这提供了一种与团队合作的方法,也提供了一种监控项目总体进度的方法。它们仍然很受欢迎,因为概念非常简单,而且很容易设置。

    3. Distributed Version Control Systems(分布式版本控制系统)

    分布式VCS的工作原理与集中式VCS几乎相同,但有一个很大的区别:没有主服务器保存所有历史记录。每个客户机都有一个存储库副本(以及更改历史记录),而不是签出单个服务器。

     

    Git能做什么?

    首先,它非常适合跟踪更改。你可以
    •在版本之间来回切换
    •审查这些版本之间的差异
    •检查文件的更改历史
    •标记特定版本以供快速参考
    Git也是团队合作的好工具。你可以
    •在存储库之间交换“变更集”
    •审查他人所做的更改

    Git的一个主要特性是它的分支系统。分支是一个项目的副本,您可以在不影响存储库的情况下处理它。这个概念已经存在了一段时间,但是使用Git,它会更快、更高效。分支还伴随着合并,合并是复制分支中完成的变更集到源数据集的行为。

    通常,您创建一个分支来创建或测试一个新特征,并在对工作满意时将该分支合并回来。

    还有一个简单的概念,你可能会用到很多:藏匿。隐藏是指安全地将当前编辑内容放在一边,这样您就有了一个干净的环境来处理完全不同的内容。当您在玩或测试一个特性时,您可能希望使用隐藏,但需要优先处理一个新特性。因此,您将更改隐藏起来并开始编写该特性。完成后,您可以将更改恢复并应用到当前的工作环境中。

    作为小菜一碟,以下是您将在本书中学习的一些Git命令:

    $ git init # Initialize a new git database
    $ git clone # Copy an existing database
    $ git status # Check the status of the local project
    $ git diff # Review the changes done to the project
    $ git add # Tell Git to track a changed file
    $ git commit # Save the current state of the project to database
    $ git push # Copy the local database to a remote server
    $ git pull # Copy a remote database to a local machine
    $ git log # Check the history of the project
    $ git branch # List, create or delete branches
    $ git merge # Merge the history of two branches together
    $ git stash # Keep the current changes stashed away to be used later

    正如您所看到的,这些命令是非常不言自明的。不要担心把它们都背下来,当我们开始学习的时候,你会一个一个的记住它们。而且您不会一直使用它们,您将主要使用git add和git commit。您将了解每个命令,但我们将重点介绍您可能在专业环境中使用的命令。但在此之前,让我们看看Git的内部工作

    与许多版本控制系统不同,Git使用的是快照,而不是差异。这意味着它不会跟踪文件的两个版本之间的差异,而是拍摄项目的当前状态。

    Git的主要特点是它的“三态”系统。状态为工作目录、临时区域和git目录:

    •工作目录只是您正在处理的当前快照。

    •暂存区是修改后的文件在其当前版本中标记的地方,还没有存储在数据库中。

    •git目录是存储历史的数据库

    因此,基本上Git的工作原理如下:修改文件,将要包含在快照中的每个文件添加到临时区域(Git add),然后获取快照并将其添加到数据库(Git commit)。对于术语,我们将添加到暂存区域的修改文件称为“staged”,将添加到数据库的文件称为“committed”。因此,文件从“modified”到“staged”再到“committed”

    什么是典型的Git工作流?

    为了帮助您形象化我们在本节中讨论的所有内容,下面是一个使用Git的典型工作流的小演示。如果你现在什么都不懂,别担心,下一章会帮你准备好的。

    这是你上班的第一天。您的任务是将您的姓名添加到现有的项目描述文件中。由于这是您的第一天,一位高级开发人员将在那里检查您的代码。

    你应该做的第一件事就是获取项目的源代码。向经理询问存储代码的服务器。对于这个演示,服务器是GitHub,这意味着Git数据库存储在GitHub托管的远程服务器上,您可以通过URL或直接在GitHub网站上访问它。在这里,我们将使用clone命令来获取数据库,但是您也可以从GitHub网站下载项目。您将得到一个zip文件,其中包含项目文件及其所有历史记录。

    因此,您可以使用“clone”命令克隆存储库以获取源代码。

    git clone https://github.com/mariot/thebestwebsite.git

    然后,Git下载当前目录下的存储库副本。之后,可以进入新目录并检查其内容,如Figure 1-8中所示

    如果要检查最近对项目所做的更改,可以使用“log”命令来显示历史记录。图1-9显示了一个例子。

    很好!现在你应该创建一个新的分支来工作,这样你就不会把项目搞砸了。您可以使用“branch”命令创建一个新分支,并使用“checkout”命令将其签出。

    git branch add-new-dev-name-to-readme
    git checkout add-new-dev-name-to-readme

    现在创建了新分支,您可以开始修改文件了。你可以使用任何你想要的编辑器;Git将通过校验和跟踪所有的变化。现在您已经做了必要的更改,是时候把它们放到临时区域了。作为提醒,暂存区域是您放置已修改代码的地方,这些代码已准备好进行快照。如果我们修改了“自述文件.md”文件,我们可以使用“add”命令将其添加到临时区域。

    git add README.md

    您不必将修改过的每个文件都添加到临时区域,只需添加希望在快照中记帐的文件即可。既然文件已暂存,现在是“提交”它或将其更改放入数据库的时候了。我们通过使用“commit”命令并附加一点描述来实现这一点。

    git commit -m "Add Mariot to the list of developers"

    就这样!您所做的更改现在已保存在数据库中并安全存储。但只在你的电脑上!其他人看不到您的工作,因为您在自己的存储库和其他分支上工作。要向其他人展示您的工作,您必须将提交推送到远程服务器。但是你必须先向高级开发人员展示代码,然后再进行推送。如果他们同意,您可以将您的分支与项目的主快照(称为主分支)合并。因此,首先必须使用“checkout”命令导航回主分支。

    git checkout master

    您现在位于主分支上,团队的所有工作都存储在这里。但是,当您进行修复时,项目可能已经更改,这意味着团队成员可能已经更改了一些文件。在将自己的更改提交给主服务器之前,应该检索这些更改。这将限制两个或多个贡献者更改同一文件时可能发生的“冲突”风险。要获得更改,必须从远程服务器(也称为源服务器)中提取项目。

    git pull origin master

    即使其他同事更改了与您相同的文件,冲突的风险也很低,因为您只修改了一行。只有当同一行被多人修改时,才会发生冲突。如果你和你的同事改变了文件的不同部分,一切都没问题。

    现在我们跟上了项目的当前状态,是时候将我们的版本提交给master了。您可以使用“merge”命令合并分支。

    git merge add-new-dev-name-to-readme

    既然提交已经合并回主服务器,现在是将更改推送到主服务器的时候了。我们通过使用to“push”命令来实现。

    git push

    就这么简单!再说一次,如果你还不了解所有的事情,不要担心。这只是Git通常是如何使用的一个小演示。这也是不太现实的:没有一个经理会给一个新员工一个像这样的主存储库的访问权限。

     

    Chapter 2:安装和设置

    既然您了解了什么是版本控制以及Git是如何工作的,我们将学习如何安装和设置它。与其他章节相比,本章较短,因为设置Git非常容易。

    Installation

    安装Git所需的文件位于https://git-scm.com/downloads适用于所有系统。只需按照链接选择您的操作系统。

    您还可以在图2-1中看到,Git的GUI客户端也在那里可用。在你完成这本书的第三部分,“Git的团队合作”之前,不要去那里。在使用GUI客户机之前,您需要熟悉Git命令;如果不熟悉,您将浪费大量时间来解决一个简单的问题,而使用简单的Git命令需要几秒钟的时间。

    在熟悉Git命令之后,您可以查看GUI客户机并亲自查看。在本书的最后一部分有一章是关于GUI客户机的。但在此之前,请不要使用任何GUI客户端;这将大大延长您的学习时间。

    在Windows系统上安装Git非常简单。打开链接后(https://gitscm.com/download/win),下载将自动开始,您将到达如图2-2所示的确认页面。如果没有,只需下载与您的Windows风格相对应的构建。

    执行下载exe文件以开始安装。第一个屏幕是概述条款和条件的许可声明;您应该一直读到最后(是的,没错)。单击next,您将进入一个组件选择屏幕,类似于图2-3所示的屏幕。在这里,系统会提示您选择要安装的组件。我建议保留默认选项。

    您可以在图2-3中看到,您只需检查组件即可安装它们。保持Windows资源管理器集成处于选中状态是一个好主意;这样您只需右键单击一个文件夹,就可以在默认GUI或上下文菜单中的Bash(命令窗口)中找到启动Git的选项。所有其他的组成部分都是不言自明的,所以决定权在你。

    提示:如果您没有安装Windows资源管理器集成,并且希望在文件夹中打开命令窗口,则必须使用shift+右键单击打开扩展上下文菜单。

    做出选择后单击next,您将看到默认的编辑器选择,如图2-4所示。Git需要您定义一个默认编辑器,因为您需要一个编辑器来编写提交描述和注释。

    如图2-4所示,由于历史原因,Vim是Git的默认编辑器。只需从下拉列表中选择您最喜欢的文本编辑器。前两个,Nano和Vim,在控制台或命令窗口中工作,因此您不必打开另一个程序。在列表中,您可以找到许多流行的编辑器,如Notepad++、Sublime Text、Atom和VisualStudio(VS)代码。如果你的编辑器没有列出,你可以选择最后一个选项,一个新的输入将出现(如图2-5所示),这样你就可以提供一个到编辑器主可执行文件的链接。

    一旦你选择了你喜欢的编辑器,你可以进入下一个屏幕,这是路径环境调整,如图2-6所示。PATH环境是一个变量,它包含可执行程序所在的目录列表。它是必需的,这样当您想在控制台中执行一个可执行文件时,就不必键入它的完整路径;只需键入它的名称。例如,要从控制台启动VisualStudio代码,我应该键入C:\ProgramFiles(x86)\Git\bin\Git.exe。但由于我的路径中有C:\ProgramFiles(x86)\Git\bin,所以我只需键入“Git”即可启动它。

    如果您不想这样,只想将Git与自己的独立控制台“gitbash”一起使用,请选择第一个选项。因此,要使用Git,您必须从Apps列表或文件夹的上下文菜单启动它(如果您选择安装Windows资源管理器集成)

    如果您想在任何地方都能使用Git,请保留默认选项,将其添加到您的路径环境中。这样,其他工具也可以使用Git,您可以在任何命令窗口中工作。我强烈建议这个选择。

    最后一个选择是有点侵入性的。它将向您的路径中添加许多Git命令,并将覆盖Windows的一些默认工具。只有在你有正当理由的情况下才选择这个;一般来说,你没有这样的理由。

    选择一个如图2-6所示的选项并继续下一步。您将看到一个关于HTTPS连接的屏幕,如图2-7所示。通过HTTPS发送数据时,必须选择要使用的库。在本书的后面部分,您将必须连接到远程服务器(因为Git是一个分布式VCS)才能将您的提交共享给其他人,因此必须对所有这些连接进行加密,以进一步保护您的数据并确保它们不会被窃取

    在此之后,转到下一步,这是关于行结束。同样,这是一个选择屏幕,所以您的屏幕应该如图2-8所示。不同的操作系统对文本文件的操作方式不同,特别是在处理行尾时。很可能你的团队会使用不同的操作系统。因此,Git需要在共享提交之前将行尾转换为每个行尾样式

    下一步是选择默认的终端(或控制台)仿真器。这是一个简单的选择屏幕,如图2-9所示。gitbash需要一个控制台模拟器才能工作,所以您需要选择一个。默认的模拟器是MinTTY,另一个选项是Windows的默认控制台。

    我建议保留默认选项,因为MinTTY可以做Windows控制台窗口所能做的一切,但在各个方面都更好。单击“下一步”继续执行最后一步。

    我们现在到了最后一局了!这个安装快结束了。只是一些东西在额外的选项屏幕调整。这个屏幕(如图2-10所示)允许您启用一些额外的特性,这些特性将非常适合您的Git安装。例如,Git凭证管理器将帮助您安全地连接到远程服务器,并与其他Git工具配合使用。

    只需保留默认选项,除非你有理由不这样做。之后,只需启动安装并让它完成。就这样!Git已安装在您的Windows系统上。但在使用它之前,请跳到下一节以正确设置它!

    Setting up Git

    在开始使用Git之前,您需要先进行一些设置。您可能只执行一次,因为所有设置都存储在一个外部全局文件中,这意味着您的所有项目将共享相同的配置。还有一种方法可以一个接一个地配置项目,但我们稍后将看到这一点

    由于Git是一个分布式版本控制系统,有朝一日您将需要连接到其他远程存储库。为了避免犯任何身份错误,有必要向Git介绍一下你自己。别担心,它不会问你一些奇怪的问题!

    要设置Git,请打开gitbash(对于Windows系统)或默认控制台窗口(对于修改了路径环境的Linux/MacOS或Windows系统)。在命令提示符下,只需告诉Git您的姓名和电子邮件地址:

    $ git config --global user.name "Mariot Tsitoara"
    $ git config --global user.email "mariot.tsitoara@gmail.com

    请注意“global”参数;这意味着该设置适用于所有将来的Git存储库,因此您以后不必再次设置它。

    使用config命令,还可以更改默认编辑器。如果你想改变你的编辑器,因为你发现了一个新的或卸载了你的,config命令可以帮助你。例如,要将默认编辑器更改为Nano,可以键入

    $ git config --global core.editor="nano

    您可以在主文件夹中找到记录Git配置的文件。对于Windows,您可以在C:\Users\YourName\.gitconfig中找到它。对于Linux和Mac OS,您可以在/home/yourname/.gitconfig中找到它,如图2-13所示。

    在.gitconfig文件旁边,您可能会找到另一个名为.bash\u history的文件,它记录您在控制台上键入的所有命令。如果要重新检查忘记的命令,可以检查此文档。

     

    CHAPTER 3: 入门

    你终于可以开始使用Git了!在本章中,您将学习一些Git术语和任何项目所必需的概念。然后,您的任务是设置一个项目,对其进行更改,检查更改,最后在版本之间导航。我们走!

    Repository(仓库)

    存储库是保存所有项目和对其所做的所有更改的存储库。您可以将其视为“更改数据库”,但不要担心;它并不是数据库,只是一个普通文件夹,因此很容易操作。

    对于要用Git管理的每个项目,必须为其设置一个存储库。设置存储库非常简单。只需导航到您想要跟踪的文件夹,并告诉Git在那里启动一个存储库。

    所以对于每个你想开始的项目,你应该
    •创建文件夹
    •导航到文件夹
    •初始化Git存储库

    看到了吗?这很简单。让我们把这些语句转换成命令。但是首先,让我们打开一个控制台来输入命令。对于Linux用户,您只需启动您最喜欢的终端(对于类似Debian的发行版,使用Ctrl-Alt-T)。对于MacOS,您只需使用Cmd Space打开Spotlight,在那里您可以搜索终端应用程序。Windows用户可以打开两个控制台:cmd和powershell。Powershell更为现代,并具有类似UNIX的命令。要打开其中一个,请使用Windows-R并键入名称(cmd或powershell)。注意,如果第一次安装Git时打开了这些控制台,则需要重新启动它们。gitforwindows还附带了一个名为gitbash的控制台仿真器,它提供了与Linux和Mac控制台类似的环境。如果你使用Windows,我强烈建议你使用gitbash,这样你就可以和其他使用不同操作系统的人有相同的体验。

    $ mkdir mynewproject
    $ cd mynewproject/
    $ git init

    mkdir是用于创建目录的命令;它是“make directory”的缩写。cd是用于在目录之间导航的命令;它是“change directory”的缩写。最后,git init是“git initialize”的缩写。

    初始化存储库后,Git将告诉您数据库是在哪里创建的,如图3-1所示。

    Git将创建一个名为“.Git”的目录,其中包含所有变更集和快照。如果您想签出它,您将不得不显示隐藏的文件从您的文件资源管理器的设置。存储库的目录如图3-2所示。

    如果打开.git目录,您将发现更多属于git数据库的项。如图3-3所示。

    还记得第一章说Git不跟踪版本之间的更改,而是拍摄快照吗?所有这些快照都存储在“.git”目录中。每个快照都称为“提交”,我们将在本节之后不久对此进行研究。

    此“.git”目录中的头文件指向您正在处理的项目的当前“分支”或子版本。默认的分支称为“master”,但它与任何其他分支一样;名称只是一个旧的约定。

    您还应该知道,初始化是获取存储库的唯一方法。您可以复制整个存储库及其所有历史记录和快照。它被称为“克隆”,我们将在另一章中看到这一点。

    Working Directory(工作目录)
    那么“.git”目录外的空白区域呢?它被称为工作目录,您要处理的文件将存储在那里。通常,您的最新版本将在工作目录中。

    您处理的每个文件都在工作目录中。这个地方没有什么特别之处,只是你只会直接操作这里的文件。永远不要修改“.git”目录中的文件!

    Git将检测到您将要放入工作目录中的任何新文件。使用Git命令“status”检查目录的状态

    $ git status

    例如,如果我们创建一个名为自述文件.md在工作目录中,我们将看到Git将知道项目已经更改。确保将新文件放在.git目录旁边,如图3-4所示,而不是放在其中!

    如果我们检查工作目录的状态,就会得到如图3-5所示的结果。

    如图3-5所示,我们还没有任何提交;这是因为我们仍然在工作目录中,还没有拍摄任何快照。它还表示我们在“master”分支上;它是存储库初始化时创建的唯一分支的默认名称。然后我们得到未追踪的文件。这些是我们修改过的文件(在本例中是创建的)

    基本上,这就是工作目录:您直接与项目文件交互的区域。

    Staging Area(暂存区)
    暂存区域是拍摄快照之前文件的存放位置。在获取项目当前状态的快照时,不应考虑在工作目录中修改的每个文件。只有放置在临时区域中的文件才会被快照。

    因此,在对项目进行快照之前,您可以选择要考虑哪些更改的文件。文件中的更改可以是创建、删除或编辑。

    把它想象成指定哪些文件会出现在家庭照片中。要将文件添加到临时区域,我们使用Git命令“add”

    $ git add nameofthefile

    就这么简单。如果我们想上演自述文件.md我们之前创建的,我们将使用“git add自述文件.md或者,如果您创建了多个文件,您可以将它们一个接一个地添加到一起,就像“git add file1 file2 file3”

    让我们使用以下命令暂存新文件:

    $ git add README.md

    然后让我们用git status命令检查状态:

    $ git status

    将文件添加到临时区域不会产生任何可见的结果,但是检查状态会得到类似于图3-6的结果。

    如果您查看图3-6,您将注意到在暂存文件之后,工作目录再次是干净的。这是因为“git status”只跟踪“unstage”文件(未标记为快照的已编辑文件)。

    如图3-6所示,您可以使用Git命令“Git rm”和选项“--cached”来取消文件的暂存。

    $ git rm --cached README.md

    小心不要忘记在取消暂存文件时使用“--cached”选项。如果你忘了它,你可能会丢失你的文件!

    在你准备好所有的文件之后,你就可以开始你的第一个快照了!

    Commits(提交)
    正如我们在本节之前所讨论的,提交只是整个项目在某个特定时间的快照。Git不会记录对文件所做的单个更改;它会拍摄整个项目的照片。

    除了快照之外,提交还包含有关内容的“作者”和“提交者”的信息,或者是谁将变更集放入存储库的信息。

    由于提交是来自项目状态的快照,因此项目的前一个状态是另一个提交,称为“父级”,第一个提交是在创建存储库时由Git创建的,并且是没有父级的提交。所有未来的提交都通过亲子关系相互链接。这些相互为父的提交的集合称为“分支”

    注意:如果提交有两个父级,这意味着它是通过合并两个分支创建的。

    提交由其名称标识,这是一个40个字符的字符串,通过对提交进行哈希处理获得。它是一个简单的SHA1散列,因此具有相同信息的多个提交将具有相同的名称。

    对特定提交的引用称为“head”,它还有一个名称。您当前正在处理的头部称为“头部”(请参阅上一节)。

    我们现在可以提交我们之前提交的文件。在每次提交之前,您应该检查工作目录和临时区域的状态。如果要提交的所有文件都在暂存区域(在短语“要提交的更改”下),则可以提交。如果没有,你就得用“git add”将它们放在舞台上。

    为了提交我们所做的所有更改,我们使用“git commit”。这将获取项目当前状态的快照。

    $ git commit

    如果我们执行这个命令,它将打开我们的默认编辑器(如果您想修改您的编辑器,请查看第2章),并向我们请求提交消息。提交消息是对提交中与前一个消息相比发生了哪些变化的简短描述。

    我的默认编辑器是Vim,因此如果执行commit命令,我将看到如图3-7所示的屏幕。

    您可以在图3-7中看到文件的第一行是空的;这就是您必须编写提交消息的地方。提交消息应该写在一行上,但是您可以添加更多行的注释。注释以“#”开头,被Git忽略;它们只用于完成提交消息,以使其更清晰。还要注意,Git会自动将更改文件的列表放在commit注释中(与您看到的“Git status”文件相同)。

    在后面的章节中,您将学习以正确的方式编写提交消息的正确方法。但现在,只需输入一个简单的消息,如“Add自述文件.md如图3-8所示的第一个空行上的“项目”。

    在编写了如图3-8所示的提交消息之后,可以关闭编辑器(保存后!)。然后您将得到提交的摘要,如图3-9所示。

    提交的摘要将包含大量信息:
    •当前分支:主
    •上一次提交的名称:根提交,因为这是我们的第一次提交
    •提交名称:提交哈希的前七个字母
    •提交消息
    •更改的文件数:一个文件
    •对每个文件执行的操作:创建

    我们拍了第一张快照!如果您检查存储库的状态,您可以看到它再次是干净的,除非您留下一些文件未暂存。

    Quick start with Git(Git快速入门)

    所以,现在您已经熟悉了Git的基本概念,我们将在一个实际的项目中应用它们。假设您希望创建一个文件夹来保存TODO列表,并希望对其进行版本控制,以便检查每个项目何时完成。

    为了让您更熟悉Git,您将在没有任何帮助的情况下进行下一个练习。如果你被卡住了,只需检查前面的部分的方向。

    只要记住Git的基本原则:
    •修改工作目录上的文件。
    •将要记录当前状态的文件放在暂存区域。
    •您可以通过提交为项目拍摄快照。

    在提交之前,不要忘记将您修改的文件放在临时区域,否则它们将不会是快照的一部分。你没有放在暂存区的修改过的文件将一直保留在工作目录中,直到你决定放弃它们或者在将来的提交中包含它们。

    让我们开始练习吧!请完成它直到结束,不要进入下一章,直到你清楚地了解Git是如何工作的。

    •创建新的存储库。
    •创建一个名为tOdO.txt文件在目录中输入一些文本。
    •阶段tOdO.txt文件.
    •提交项目并发出简短的提交消息。
    •创建两个名为完成.txt以及工作.txt.
    •暂存并提交这些文件。
    •重命名工作.txt加入进度.txt.
    •添加一些文本到完成.txt.
    •检查目录状态。
    •分期付款进度.txt以及完成.txt.
    •未分级完成.txt.
    •承诺项目。
    •检查目录状态

    完成这个练习后,合上书,试着用自己的话向自己解释这些事情:

    • Working Directory工作目录
    • Staging Area暂存区
    • Commit提交


    CHAPTER 4:深入Git

    现在您已经熟悉了Git的基本命令,我们将深入了解它的其他特性。您将在本章中发现我在第1章中向您承诺的功能

    Ignoring files
    不是工作目录中的所有内容都应该被Git跟踪。有些文件(配置、密码、错误代码)通常不会被作者或开发人员跟踪。

    这些文件(或目录)列在一个名为“.gitignore”的简单文件中。注意“gitignore”前面的句点;这很重要。要忽略文件,请创建一个名为.gitignore的文件,并列出其中要忽略的文件或文件夹

    让我们回到上一章的存储库,TODO列表。假设您想要包含一个名为专用.txt. 首先必须使用您最喜欢的文本编辑器创建.gitinore文件,然后编写PRIVATE。如图4-1所示。

     

    展开全文
  • GX-Work2操作手册(公共编)中文版
  • 2019独角兽企业重金招聘Python工程师标准>>> ...

    序言

    这是一篇全面介绍 Webkit 和 Gecko 内部操作的入门文章,是以色列开发人员塔利·加希尔大量研究的成果。在过去的几年中,她查阅了所有公开发布的关于浏览器内部机制的数据(请参见资源),并花了很多时间来研读网络浏览器的源代码。她写道:

    在 IE 占据 90% 市场份额的年代,我们除了把浏览器当成一个“黑箱”,什么也做不了。但是现在,开放源代码的浏览器拥有了过半的市场份额,因此,是时候来揭开神秘的面纱,一探网络浏览器的内幕了。呃,里面只有数以百万行计的 C++ 代码...

    塔利在她的网站上公布了自己的研究成果,但是我们觉得它值得让更多的人来了解,所以我们在此重新整理并公布。

    作为一名网络开发人员,学习浏览器的内部工作原理将有助于您作出更明智的决策,并理解那些最佳开发实践的个中缘由。尽管这是一篇相当长的文档,但是我们建议您花些时间来仔细阅读;读完之后,您肯定会觉得所费不虚——保罗·爱丽诗 (Paul Irish),Chrome 浏览器开发人员事务部


    简介

    网络浏览器很可能是使用最广的软件。在这篇入门文章中,我将会介绍它们的幕后工作原理。我们会了解到,从您在地址栏输入 google.com 直到您在浏览器屏幕上看到 Google 首页的整个过程中都发生了些什么。

    我们要讨论的浏览器

    目前使用的主流浏览器有五个:Internet Explorer、Firefox、Safari、Chrome 浏览器和 Opera。本文中以开放源代码浏览器为例,即 Firefox、Chrome 浏览器和 Safari(部分开源)。根据 StatCounter 浏览器统计数据,目前(2011 年 8 月)Firefox、Safari 和 Chrome 浏览器的总市场占有率将近 60%。由此可见,如今开放源代码浏览器在浏览器市场中占据了非常坚实的部分。

    浏览器的主要功能

    浏览器的主要功能就是向服务器发出请求,在浏览器窗口中展示您选择的网络资源。这里所说的资源一般是指 HTML 文档,也可以是 PDF、图片或其他的类型。资源的位置由用户使用 URI(统一资源标示符)指定。

    浏览器解释并显示 HTML 文件的方式是在 HTML 和 CSS 规范中指定的。这些规范由网络标准化组织 W3C(万维网联盟)进行维护。 多年以来,各浏览器都没有完全遵从这些规范,同时还在开发自己独有的扩展程序,这给网络开发人员带来了严重的兼容性问题。如今,大多数的浏览器都是或多或少地遵从规范。

    浏览器的用户界面有很多彼此相同的元素,其中包括:

    • 用来输入 URI 的地址栏

    • 前进和后退按钮

    • 书签设置选项

    • 用于刷新和停止加载当前文档的刷新和停止按钮

    • 用于返回主页的主页按钮

    奇怪的是,浏览器的用户界面并没有任何正式的规范,这是多年来的最佳实践自然发展以及彼此之间相互模仿的结果。HTML5 也没有定义浏览器必须具有的用户界面元素,但列出了一些通用的元素,例如地址栏、状态栏和工具栏等。当然,各浏览器也可以有自己独特的功能,比如 Firefox 的下载管理器。

    浏览器的高层结构

    浏览器的主要组件为 (1.1):

    1. 用户界面 - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。

    2. 浏览器引擎 - 在用户界面和呈现引擎之间传送指令。

    3. 呈现引擎 - 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。

    4. 网络 - 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。

    5. 用户界面后端 - 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。

    6. JavaScript 解释器。用于解析和执行 JavaScript 代码。

    7. 数据存储。这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。

      图:浏览器的主要组件。

    (图:浏览器的主要组件。)

    值得注意的是,和大多数浏览器不同,Chrome 浏览器的每个标签页都分别对应一个呈现引擎实例。每个标签页都是一个独立的进程。

    呈现引擎

    呈现引擎的作用嘛...当然就是“呈现”了,也就是在浏览器的屏幕上显示请求的内容。

    默认情况下,呈现引擎可显示 HTML 和 XML 文档与图片。通过插件(或浏览器扩展程序),还可以显示其它类型的内容;例如,使用 PDF 查看器插件就能显示 PDF 文档。但是在本章中,我们将集中介绍其主要用途:显示使用 CSS 格式化的 HTML 内容和图片。

    呈现引擎

    本文所讨论的浏览器(Firefox、Chrome 浏览器和 Safari)是基于两种呈现引擎构建的。Firefox 使用的是 Gecko,这是 Mozilla 公司“自制”的呈现引擎。而 Safari 和 Chrome 浏览器使用的都是 Webkit。

    Webkit 是一种开放源代码呈现引擎,起初用于 Linux 平台,随后由 Apple 公司进行修改,从而支持苹果机和 Windows。有关详情,请参阅 webkit.org。

    主流程

    呈现引擎一开始会从网络层获取请求文档的内容,内容的大小一般限制在 8000 个块以内。

    然后进行如下所示的基本流程:

    enter image description here

    (图:呈现引擎的基本流程。)

    呈现引擎将开始解析 HTML 文档,并将各标记逐个转化成“内容树”上的 DOM 节点。同时也会解析外部 CSS 文件以及样式元素中的样式数据。HTML 中这些带有视觉指令的样式信息将用于创建另一个树结构:呈现树。

    呈现树包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。

    呈现树构建完毕之后,进入“布局”处理阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标。下一个阶段是绘制 - 呈现引擎会遍历呈现树,由用户界面后端层将每个节点绘制出来。

    需要着重指出的是,这是一个渐进的过程。为达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。

    主流程示例

    enter image description here

    (图:Webkit 主流程)

    enter image description here

    (图:Mozilla 的 Gecko 呈现引擎主流程 (3.6))

    从图 3 和图 4 可以看出,虽然 Webkit 和 Gecko 使用的术语略有不同,但整体流程是基本相同的。

    Gecko 将视觉格式化元素组成的树称为“框架树”。每个元素都是一个框架。Webkit 使用的术语是“呈现树”,它由“呈现对象”组成。对于元素的放置,Webkit 使用的术语是“布局”,而 Gecko 称之为“重排”。对于连接 DOM 节点和可视化信息从而创建呈现树的过程,Webkit 使用的术语是“附加”。有一个细微的非语义差别,就是 Gecko 在 HTML 与 DOM 树之间还有一个称为“内容槽”的层,用于生成 DOM 元素。我们会逐一论述流程中的每一部分:

    解析和 DOM 树构建

    解析 - 综述

    解析是呈现引擎中非常重要的一个环节,因此我们要更深入地讲解。首先,来介绍一下解析。

    解析文档是指将文档转化成为有意义的结构,也就是可让代码理解和使用的结构。解析得到的结果通常是代表了文档结构的节点树,它称作解析树或者语法树。

    示例 - 解析 2 + 3 - 1 这个表达式,会返回下面的树:

    enter image description here

    (图:数学表达式树节点)

    语法

    解析是以文档所遵循的语法规则(编写文档所用的语言或格式)为基础的。所有可以解析的格式都必须对应确定的语法(由词汇和语法规则构成)。这称为与上下文无关的语法。人类语言并不属于这样的语言,因此无法用常规的解析技术进行解析。

    解析器和词法分析器的组合

    解析的过程可以分成两个子过程:词法分析和语法分析。

    词法分析是将输入内容分割成大量标记的过程。标记是语言中的词汇,即构成内容的单位。在人类语言中,它相当于语言字典中的单词。

    语法分析是应用语言的语法规则的过程。

    解析器通常将解析工作分给以下两个组件来处理:词法分析器(有时也称为标记生成器),负责将输入内容分解成一个个有效标记;而解析器负责根据语言的语法规则分析文档的结构,从而构建解析树。词法分析器知道如何将无关的字符(比如空格和换行符)分离出来。

    enter image description here

    (图:从源文档到解析树)

    解析是一个迭代的过程。通常,解析器会向词法分析器请求一个新标记,并尝试将其与某条语法规则进行匹配。如果发现了匹配规则,解析器会将一个对应于该标记的节点添加到解析树中,然后继续请求下一个标记。

    如果没有规则可以匹配,解析器就会将标记存储到内部,并继续请求标记,直至找到可与所有内部存储的标记匹配的规则。如果找不到任何匹配规则,解析器就会引发一个异常。这意味着文档无效,包含语法错误。

    翻译

    很多时候,解析树还不是最终产品。解析通常是在翻译过程中使用的,而翻译是指将输入文档转换成另一种格式。编译就是这样一个例子。编译器可将源代码编译成机器代码,具体过程是首先将源代码解析成解析树,然后将解析树翻译成机器代码文档。

    enter image description here

    (图:编译流程)

    解析示例

    在图 5 中,我们通过一个数学表达式建立了解析树。现在,让我们试着定义一个简单的数学语言,用来演示解析的过程。

    词汇:我们用的语言可包含整数、加号和减号。

    语法:

    1. 构成语言的语法单位是表达式、项和运算符。

    2. 我们用的语言可以包含任意数量的表达式。

    3. 表达式的定义是:一个“项”接一个“运算符”,然后再接一个“项”。

    4. 运算符是加号或减号。

    5. 项是一个整数或一个表达式。

    让我们分析一下 2 + 3 - 1。

    匹配语法规则的第一个子串是 2,而根据第 5 条语法规则,这是一个项。匹配语法规则的第二个子串是 2 + 3,而根据第 3 条规则(一个项接一个运算符,然后再接一个项),这是一个表达式。下一个匹配项已经到了输入的结束。2 + 3 - 1 是一个表达式,因为我们已经知道 2 + 3 是一个项,这样就符合“一个项接一个运算符,然后再接一个项”的规则。2 + + 不与任何规则匹配,因此是无效的输入。

    词汇和语法的正式定义

    词汇通常用正则表达式表示。

    例如,我们的示例语言可以定义如下:

    INTEGER :0|[1-9][0-9]*
    PLUS : +
    MINUS: -

    正如您所看到的,这里用正则表达式给出了整数的定义。

    语法通常使用一种称为 BNF 的格式来定义。我们的示例语言可以定义如下:

    expression :=  term  operation  term
    operation :=  PLUS | MINUS
    term := INTEGER | expression

    之前我们说过,如果语言的语法是与上下文无关的语法,就可以由常规解析器进行解析。与上下文无关的语法的直观定义就是可以完全用 BNF 格式表达的语法。有关正式定义,请参阅关于与上下文无关的语法的维基百科文章。

    解析器类型

    有两种基本类型的解析器:自上而下解析器和自下而上解析器。直观地来说,自上而下的解析器从语法的高层结构出发,尝试从中找到匹配的结构。而自下而上的解析器从低层规则出发,将输入内容逐步转化为语法规则,直至满足高层规则。

    让我们来看看这两种解析器会如何解析我们的示例:

    自上而下的解析器会从高层的规则开始:首先将 2 + 3 标识为一个表达式,然后将 2 + 3 - 1 标识为一个表达式(标识表达式的过程涉及到匹配其他规则,但是起点是最高级别的规则)。

    自下而上的解析器将扫描输入内容,找到匹配的规则后,将匹配的输入内容替换成规则。如此继续替换,直到输入内容的结尾。部分匹配的表达式保存在解析器的堆栈中。

    堆栈                           输入
    -----------------------------------------
                               2 + 3 - 1
    项                              + 3 - 1
    项运算                         3 - 1
    表达式                            - 1
    表达式运算符                      1
    表达式

    这种自下而上的解析器称为移位归约解析器,因为输入在向右移位(设想有一个指针从输入内容的开头移动到结尾),并且逐渐归约到语法规则上。

    自动生成解析器

    有一些工具可以帮助您生成解析器,它们称为解析器生成器。您只要向其提供您所用语言的语法(词汇和语法规则),它就会生成相应的解析器。创建解析器需要对解析有深刻理解,而人工创建优化的解析器并不是一件容易的事情,所以解析器生成器是非常实用的。

    Webkit 使用了两种非常有名的解析器生成器:用于创建词法分析器的 Flex 以及用于创建解析器的 Bison(您也可能遇到 Lex 和 Yacc 这样的别名)。Flex 的输入是包含标记的正则表达式定义的文件。Bison 的输入是采用 BNF 格式的语言语法规则。

    HTML 解析器

    HTML 解析器的任务是将 HTML 标记解析成解析树。

    HTML 语法定义

    HTML 的词汇和语法在 W3C 组织创建的规范中进行了定义。当前的版本是 HTML4,HTML5 正在处理过程中。

    非与上下文无关的语法

    正如我们在解析过程的简介中已经了解到的,语法可以用 BNF 等格式进行正式定义。

    很遗憾,所有的常规解析器都不适用于 HTML(我并不是开玩笑,它们可以用于解析 CSS 和 JavaScript)。HTML 并不能很容易地用解析器所需的与上下文无关的语法来定义。

    有一种可以定义 HTML 的正规格式:DTD(Document Type Definition,文档类型定义),但它不是与上下文无关的语法。

    这初看起来很奇怪:HTML 和 XML 非常相似。有很多 XML 解析器可以使用。HTML 存在一个 XML 变体 (XHTML),那么有什么大的区别呢?

    区别在于 HTML 的处理更为“宽容”,它允许您省略某些隐式添加的标记,有时还能省略一些起始或者结束标记等等。和 XML 严格的语法不同,HTML 整体来看是一种“软性”的语法。

    显然,这种看上去细微的差别实际上却带来了巨大的影响。一方面,这是 HTML 如此流行的原因:它能包容您的错误,简化网络开发。另一方面,这使得它很难编写正式的语法。概括地说,HTML 无法很容易地通过常规解析器解析(因为它的语法不是与上下文无关的语法),也无法通过 XML 解析器来解析。

    HTML DTD

    HTML 的定义采用了 DTD 格式。此格式可用于定义 SGML 族的语言。它包括所有允许使用的元素及其属性和层次结构的定义。如上文所述,HTML DTD 无法构成与上下文无关的语法。

    DTD 存在一些变体。严格模式完全遵守 HTML 规范,而其他模式可支持以前的浏览器所使用的标记。这样做的目的是确保向下兼容一些早期版本的内容。最新的严格模式 DTD 可以在这里找到:www.w3.org/TR/html4/strict.dtd

    DOM

    解析器的输出“解析树”是由 DOM 元素和属性节点构成的树结构。DOM 是文档对象模型 (Document Object Model) 的缩写。它是 HTML 文档的对象表示,同时也是外部内容(例如 JavaScript)与 HTML 元素之间的接口。 解析树的根节点是“Document”对象。

    DOM 与标记之间几乎是一一对应的关系。比如下面这段标记:

    <html>
     <body>
       <p>
         Hello World
       </p>
       <div> <img src="example.png"/></div>
     </body>
    </html>

    可翻译成如下的 DOM 树:

    enter image description here

    (图:示例标记的 DOM 树)

    和 HTML 一样,DOM 也是由 W3C 组织指定的。请参见 www.w3.org/DOM/DOMTR。这是关于文档操作的通用规范。其中一个特定模块描述针对 HTML 的元素。HTML 的定义可以在这里找到:www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html。

    我所说的树包含 DOM 节点,指的是树是由实现了某个 DOM 接口的元素构成的。浏览器所用的具体实现也会具有一些其他属性,供浏览器在内部使用。

    解析算法

    我们在之前章节已经说过,HTML 无法用常规的自上而下或自下而上的解析器进行解析。

    原因在于:

    1. 语言的宽容本质。

    2. 浏览器历来对一些常见的无效 HTML 用法采取包容态度。

    3. 解析过程需要不断地反复。源内容在解析过程中通常不会改变,但是在 HTML 中,脚本标记如果包含 document.write,就会添加额外的标记,这样解析过程实际上就更改了输入内容。

    由于不能使用常规的解析技术,浏览器就创建了自定义的解析器来解析 HTML。

    HTML5 规范详细地描述了解析算法。此算法由两个阶段组成:标记化和树构建。

    标记化是词法分析过程,将输入内容解析成多个标记。HTML 标记包括起始标记、结束标记、属性名称和属性值。

    标记生成器识别标记,传递给树构造器,然后接受下一个字符以识别下一个标记;如此反复直到输入的结束。

    enter image description here

    (图:HTML 解析流程(摘自 HTML5 规范))

    标记化算法

    该算法的输出结果是 HTML 标记。该算法使用状态机来表示。每一个状态接收来自输入信息流的一个或多个字符,并根据这些字符更新下一个状态。当前的标记化状态和树结构状态会影响进入下一状态的决定。这意味着,即使接收的字符相同,对于下一个正确的状态也会产生不同的结果,具体取决于当前的状态。该算法相当复杂,无法在此详述,所以我们通过一个简单的示例来帮助大家理解其原理。

    基本示例 - 将下面的 HTML 代码标记化:

    <html>
     <body>
       Hello world
     </body>
    </html>

    初始状态是数据状态。遇到字符 < 时,状态更改为“标记打开状态”。接收一个 a-z 字符会创建“起始标记”,状态更改为“标记名称状态”。这个状态会一直保持到接收 > 字符。在此期间接收的每个字符都会附加到新的标记名称上。在本例中,我们创建的标记是 html 标记。

    遇到 > 标记时,会发送当前的标记,状态改回“数据状态”。<body> 标记也会进行同样的处理。目前 html 和 body 标记均已发出。现在我们回到“数据状态”。接收到 Hello world 中的 H 字符时,将创建并发送字符标记,直到接收 </body> 中的 <。我们将为 Hello world 中的每个字符都发送一个字符标记。

    现在我们回到“标记打开状态”。接收下一个输入字符 / 时,会创建 end tag token 并改为“标记名称状态”。我们会再次保持这个状态,直到接收 >。然后将发送新的标记,并回到“数据状态”。</html> 输入也会进行同样的处理。

    enter image description here

    (图:对示例输入进行标记化)

    树构建算法

    在创建解析器的同时,也会创建 Document 对象。在树构建阶段,以 Document 为根节点的 DOM 树也会不断进行修改,向其中添加各种元素。标记生成器发送的每个节点都会由树构建器进行处理。规范中定义了每个标记所对应的 DOM 元素,这些元素会在接收到相应的标记时创建。这些元素不仅会添加到 DOM 树中,还会添加到开放元素的堆栈中。此堆栈用于纠正嵌套错误和处理未关闭的标记。其算法也可以用状态机来描述。这些状态称为“插入模式”。

    让我们来看看示例输入的树构建过程:

    <html>
     <body>
       Hello world
     </body>
    </html>

    树构建阶段的输入是一个来自标记化阶段的标记序列。第一个模式是“initial mode”。接收 HTML 标记后转为“before html”模式,并在这个模式下重新处理此标记。这样会创建一个 HTMLHtmlElement 元素,并将其附加到 Document 根对象上。

    然后状态将改为“before head”。此时我们接收“body”标记。即使我们的示例中没有“head”标记,系统也会隐式创建一个 HTMLHeadElement,并将其添加到树中。

    现在我们进入了“in head”模式,然后转入“after head”模式。系统对 body 标记进行重新处理,创建并插入 HTMLBodyElement,同时模式转变为“body”。

    现在,接收由“Hello world”字符串生成的一系列字符标记。接收第一个字符时会创建并插入“Text”节点,而其他字符也将附加到该节点。

    接收 body 结束标记会触发“after body”模式。现在我们将接收 HTML 结束标记,然后进入“after after body”模式。接收到文件结束标记后,解析过程就此结束。

    enter image description here

    (图:示例 HTML 的树构建)

    解析结束后的操作

    在此阶段,浏览器会将文档标注为交互状态,并开始解析那些处于“deferred”模式的脚本,也就是那些应在文档解析完成后才执行的脚本。然后,文档状态将设置为“完成”,一个“加载”事件将随之触发。

    您可以在 HTML5 规范中查看标记化和树构建的完整算法

    浏览器的容错机制

    您在浏览 HTML 网页时从来不会看到“语法无效”的错误。这是因为浏览器会纠正任何无效内容,然后继续工作。

    以下面的 HTML 代码为例:

    <html>
     <mytag>
     </mytag>
     <div>
     <p>
     </div>
       Really lousy HTML
     </p>
    </html>

    在这里,我已经违反了很多语法规则(“mytag”不是标准的标记,“p”和“div”元素之间的嵌套有误等等),但是浏览器仍然会正确地显示这些内容,并且毫无怨言。因为有大量的解析器代码会纠正 HTML 网页作者的错误。

    不同浏览器的错误处理机制相当一致,但令人称奇的是,这种机制并不是 HTML 当前规范的一部分。和书签管理以及前进/后退按钮一样,它也是浏览器在多年发展中的产物。很多网站都普遍存在着一些已知的无效 HTML 结构,每一种浏览器都会尝试通过和其他浏览器一样的方式来修复这些无效结构。

    HTML5 规范定义了一部分这样的要求。Webkit 在 HTML 解析器类的开头注释中对此做了很好的概括。

    解析器对标记化输入内容进行解析,以构建文档树。如果文档的格式正确,就直接进行解析。

    遗憾的是,我们不得不处理很多格式错误的 HTML 文档,所以解析器必须具备一定的容错性。

    我们至少要能够处理以下错误情况:

    1. 明显不能在某些外部标记中添加的元素。在此情况下,我们应该关闭所有标记,直到出现禁止添加的元素,然后再加入该元素。

    2. 我们不能直接添加的元素。这很可能是网页作者忘记添加了其中的一些标记(或者其中的标记是可选的)。这些标签可能包括:HTML HEAD BODY TBODY TR TD LI(还有遗漏的吗?)。

    3. 向 inline 元素内添加 block 元素。关闭所有 inline 元素,直到出现下一个较高级的 block 元素。

    4. 如果这样仍然无效,可关闭所有元素,直到可以添加元素为止,或者忽略该标记。

    让我们看一些 Webkit 容错的示例:

    使用了 </br> 而不是 <br>

    有些网站使用了 </br> 而不是 <br>。为了与 IE 和 Firefox 兼容,Webkit 将其与 
    做同样的处理。 代码如下:

    if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
        reportError(MalformedBRError);
        t->beginTag = true;
    }

    请注意,错误处理是在内部进行的,用户并不会看到这个过程。

    离散表格

    离散表格是指位于其他表格内容中,但又不在任何一个单元格内的表格。

    比如以下的示例:

    <table>
       <table>
           <tr><td>inner table</td></tr>
       </table>
       <tr><td>outer table</td></tr>
    </table>

    Webkit 会将其层次结构更改为两个同级表格:

    <table>
       <tr><td>outer table</td></tr>
    </table>
    <table>
       <tr><td>inner table</td></tr>
    </table>

    代码如下:

    if (m_inStrayTableContent && localName == tableTag)
           popBlock(tableTag);

    Webkit 使用一个堆栈来保存当前的元素内容,它会从外部表格的堆栈中弹出内部表格。现在,这两个表格就变成了同级关系。

    嵌套的表单元素

    如果用户在一个表单元素中又放入了另一个表单,那么第二个表单将被忽略。

    代码如下:

    if (!m_currentFormElement) {
           m_currentFormElement = new HTMLFormElement(formTag,    m_document);
    }

    过于复杂的标记层次结构

    代码的注释已经说得很清楚了。

    示例网站 www.liceo.edu.mx 嵌套了约 1500 个标记,全都来自一堆 <b> 标记。我们只允许最多 20 层同类型标记的嵌套,如果再嵌套更多,就会全部忽略。

    bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName)
    {

    unsigned i = 0;
    for (HTMLStackElem* curr = m_blockStack;
            i < cMaxRedundantTagDepth && curr && curr->tagName == tagName;
        curr = curr->next, i++) { }
    return i != cMaxRedundantTagDepth;
    }

    放错位置的 html 或者 body 结束标记

    同样,代码的注释已经说得很清楚了。

    支持格式非常糟糕的 HTML 代码。我们从不关闭 body 标记,因为一些愚蠢的网页会在实际文档结束之前就关闭。我们通过调用 end() 来执行关闭操作。

    if (t->tagName == htmlTag || t->tagName == bodyTag )
           return;

    所以网页作者需要注意,除非您想作为反面教材出现在 Webkit 容错代码段的示例中,否则还请编写格式正确的 HTML 代码。

    CSS 解析

    还记得简介中解析的概念吗?和 HTML 不同,CSS 是上下文无关的语法,可以使用简介中描述的各种解析器进行解析。事实上,CSS 规范定义了 CSS 的词法和语法。

    让我们来看一些示例:

    词法语法(词汇)是针对各个标记用正则表达式定义的:

    comment   \/\*[^*]*\*+([^/*][^*]*\*+)*\/
    num   [0-9]+|[0-9]*"."[0-9]+
    nonascii  [\200-\377]
    nmstart   [_a-z]|{nonascii}|{escape}
    nmchar    [_a-z0-9-]|{nonascii}|{escape}
    name    {nmchar}+
    ident   {nmstart}{nmchar}*

    “ident”是标识符 (identifier) 的缩写,比如类名。“name”是元素的 ID(通过“#”来引用)。

    语法是采用 BNF 格式描述的。

    ruleset
     : selector [ ',' S* selector ]*
       '{' S* declaration [ ';' S* declaration ]* '}' S*
     ;
    selector
     : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
     ;
    simple_selector
     : element_name [ HASH | class | attrib | pseudo ]*
     | [ HASH | class | attrib | pseudo ]+
     ;
    class
     : '.' IDENT
     ;
    element_name
     : IDENT | '*'
     ;
    attrib
     : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
       [ IDENT | STRING ] S* ] ']'
     ;
    pseudo
     : ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ]
     ;

    解释:这是一个规则集的结构:

    div.error , a.error {
     color:red;
     font-weight:bold;
    }

    div.error 和 a.error 是选择器。大括号内的部分包含了由此规则集应用的规则。此结构的正式定义是这样的:

    ruleset
     : selector [ ',' S* selector ]*
       '{' S* declaration [ ';' S* declaration ]* '}' S*
     ;

    这表示一个规则集就是一个选择器,或者由逗号和空格(S 表示空格)分隔的多个(数量可选)选择器。规则集包含了大括号,以及其中的一个或多个(数量可选)由分号分隔的声明。“声明”和“选择器”将由下面的 BNF 格式定义。

    Webkit CSS 解析器

    Webkit 使用 Flex 和 Bison 解析器生成器,通过 CSS 语法文件自动创建解析器。正如我们之前在解析器简介中所说,Bison 会创建自下而上的移位归约解析器。Firefox 使用的是人工编写的自上而下的解析器。这两种解析器都会将 CSS 文件解析成 StyleSheet 对象,且每个对象都包含 CSS 规则。CSS 规则对象则包含选择器和声明对象,以及其他与 CSS 语法对应的对象。

    enter image description here

    (图:解析 CSS)

    处理脚本和样式表的顺序

    脚本

    网络的模型是同步的。网页作者希望解析器遇到 <script> 标记时立即解析并执行脚本。文档的解析将停止,直到脚本执行完毕。如果脚本是外部的,那么解析过程会停止,直到从网络同步抓取资源完成后再继续。此模型已经使用了多年,也在 HTML4 和 HTML5 规范中进行了指定。作者也可以将脚本标注为“defer”,这样它就不会停止文档解析,而是等到解析结束才执行。HTML5 增加了一个选项,可将脚本标记为异步,以便由其他线程解析和执行。

    预解析

    Webkit 和 Firefox 都进行了这项优化。在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,从而提高总体速度。请注意,预解析器不会修改 DOM 树,而是将这项工作交由主解析器处理;预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。

    样式表

    另一方面,样式表有着不同的模型。理论上来说,应用样式表不会更改 DOM 树,因此似乎没有必要等待样式表并停止文档解析。但这涉及到一个问题,就是脚本在文档解析阶段会请求样式信息。如果当时还没有加载和解析样式,脚本就会获得错误的回复,这样显然会产生很多问题。这看上去是一个非典型案例,但事实上非常普遍。Firefox 在样式表加载和解析的过程中,会禁止所有脚本。而对于 Webkit 而言,仅当脚本尝试访问的样式属性可能受尚未加载的样式表影响时,它才会禁止该脚本。

    呈现树构建

    在 DOM 树构建的同时,浏览器还会构建另一个树结构:呈现树。这是由可视化元素按照其显示顺序而组成的树,也是文档的可视化表示。它的作用是让您按照正确的顺序绘制内容。

    Firefox 将呈现树中的元素称为“框架”。Webkit 使用的术语是呈现器或呈现对象。 呈现器知道如何布局并将自身及其子元素绘制出来。 Webkits RenderObject 类是所有呈现器的基类,其定义如下:

    class RenderObject{
     virtual void layout();
     virtual void paint(PaintInfo);
     virtual void rect repaintRect();
     Node* node;  //the DOM node
     RenderStyle* style;  // the computed style
     RenderLayer* containgLayer; //the containing z-index layer
    }

    每一个呈现器都代表了一个矩形的区域,通常对应于相关节点的 CSS 框,这一点在 CSS2 规范中有所描述。它包含诸如宽度、高度和位置等几何信息。

    框的类型会受到与节点相关的“display”样式属性的影响(请参阅样式计算章节)。下面这段 Webkit 代码描述了根据 display 属性的不同,针对同一个 DOM 节点应创建什么类型的呈现器。

    RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
    {
       Document* doc = node->document();
       RenderArena* arena = doc->renderArena();
       ...
       RenderObject* o = 0;

       switch (style->display()) {
       case NONE:
               break;
           case INLINE:
               o = new (arena) RenderInline(node);
               break;
           case BLOCK:
               o = new (arena) RenderBlock(node);
               break;
           case INLINE_BLOCK:
               o = new (arena) RenderBlock(node);
               break;
           case LIST_ITEM:
               o = new (arena) RenderListItem(node);
               break;
          ...
       }

       return o;
    }

    元素类型也是考虑因素之一,例如表单控件和表格都对应特殊的框架。

    在 Webkit 中,如果一个元素需要创建特殊的呈现器,就会替换 createRenderer 方法。呈现器所指向的样式对象中包含了一些和几何无关的信息。

    呈现树和 DOM 树的关系

    呈现器是和 DOM 元素相对应的,但并非一一对应。非可视化的 DOM 元素不会插入呈现树中,例如“head”元素。如果元素的 display 属性值为“none”,那么也不会显示在呈现树中(但是 visibility 属性值为“hidden”的元素仍会显示)。 有一些 DOM 元素对应多个可视化对象。它们往往是具有复杂结构的元素,无法用单一的矩形来描述。例如,“select”元素有 3 个呈现器:一个用于显示区域,一个用于下拉列表框,还有一个用于按钮。如果由于宽度不够,文本无法在一行中显示而分为多行,那么新的行也会作为新的呈现器而添加。 另一个关于多呈现器的例子是格式无效的 HTML。根据 CSS 规范,inline 元素只能包含 block 元素或 inline 元素中的一种。如果出现了混合内容,则应创建匿名的 block 呈现器,以包裹 inline 元素。

    有一些呈现对象对应于 DOM 节点,但在树中所在的位置与 DOM 节点不同。浮动定位和绝对定位的元素就是这样,它们处于正常的流程之外,放置在树中的其他地方,并映射到真正的框架,而放在原位的是占位框架。

    enter image description here

    (图:呈现树及其对应的 DOM 树 (3.1)。初始容器 block 为“viewport”,而在 Webkit 中则为“RenderView”对象。)

    构建呈现树的流程

    在 Firefox 中,系统会针对 DOM 更新注册展示层,作为侦听器。展示层将框架创建工作委托给 FrameConstructor,由该构造器解析样式(请参阅样式计算)并创建框架。

    在 Webkit 中,解析样式和创建呈现器的过程称为“附加”。每个 DOM 节点都有一个“attach”方法。附加是同步进行的,将节点插入 DOM 树需要调用新的节点“attach”方法。

    处理 html 和 body 标记就会构建呈现树根节点。这个根节点呈现对象对应于 CSS 规范中所说的容器 block,这是最上层的 block,包含了其他所有 block。它的尺寸就是视口,即浏览器窗口显示区域的尺寸。Firefox 称之为 ViewPortFrame,而 Webkit 称之为 RenderView。这就是文档所指向的呈现对象。呈现树的其余部分以 DOM 树节点插入的形式来构建。

    请参阅关于处理模型的 CSS2 规范。

    样式计算

    构建呈现树时,需要计算每一个呈现对象的可视化属性。这是通过计算每个元素的样式属性来完成的。

    样式包括来自各种来源的样式表、inline 样式元素和 HTML 中的可视化属性(例如“bgcolor”属性)。其中后者将经过转化以匹配 CSS 样式属性。

    样式表的来源包括浏览器的默认样式表、由网页作者提供的样式表以及由浏览器用户提供的用户样式表(浏览器允许您定义自己喜欢的样式。以 Firefox 为例,用户可以将自己喜欢的样式表放在“Firefox Profile”文件夹下)。

    样式计算存在以下难点:

    1. 样式数据是一个超大的结构,存储了无数的样式属性,这可能造成内存问题。

    2. 如果不进行优化,为每一个元素查找匹配的规则会造成性能问题。要为每一个元素遍历整个规则列表来寻找匹配规则,这是一项浩大的工程。选择器会具有很复杂的结构,这就会导致某个匹配过程一开始看起来很可能是正确的,但最终发现其实是徒劳的,必须尝试其他匹配路径。

    例如下面这个组合选择器:

    div div div div{
     ...
    }

    这意味着规则适用于作为 3 个 div 元素的子代的 <div>。如果您要检查规则是否适用于某个指定的 <div> 元素,应选择树上的一条向上路径进行检查。您可能需要向上遍历节点树,结果发现只有两个div,而且规则并不适用。然后,您必须尝试树中的其他路径。

    1. 应用规则涉及到相当复杂的层叠规则(用于定义这些规则的层次)。

    让我们来看看浏览器是如何处理这些问题的:

    共享样式数据

    Webkit 节点会引用样式对象 (RenderStyle)。这些对象在某些情况下可以由不同节点共享。这些节点是同级关系,并且:

    1. 这些元素必须处于相同的鼠标状态(例如,不允许其中一个是“:hover”状态,而另一个不是)

    2. 任何元素都没有 ID

    3. 标记名称应匹配

    4. 类属性应匹配

    5. 映射属性的集合必须是完全相同的

    6. 链接状态必须匹配

    7. 焦点状态必须匹配

    8. 任何元素都不应受属性选择器的影响,这里所说的“影响”是指在选择器中的任何位置有任何使用了属性选择器的选择器匹配

    9. 元素中不能有任何 inline 样式属性

    10. 不能使用任何同级选择器。WebCore 在遇到任何同级选择器时,只会引发一个全局开关,并停用整个文档的样式共享(如果存在)。这包括 + 选择器以及 :first-child 和 :last-child 等选择器。

    Firefox 规则树

    为了简化样式计算,Firefox 还采用了另外两种树:规则树和样式上下文树。Webkit 也有样式对象,但它们不是保存在类似样式上下文树这样的树结构中,只是由 DOM 节点指向此类对象的相关样式。

    enter image description here

    (图:Firefox 样式上下文树 (2.2))

    样式上下文包含端值。要计算出这些值,应按照正确顺序应用所有的匹配规则,并将其从逻辑值转化为具体的值。例如,如果逻辑值是屏幕大小的百分比,则需要换算成绝对的单位。规则树的点子真的很巧妙,它使得节点之间可以共享这些值,以避免重复计算,还可以节约空间。

    所有匹配的规则都存储在树中。路径中的底层节点拥有较高的优先级。规则树包含了所有已知规则匹配的路径。规则的存储是延迟进行的。规则树不会在开始的时候就为所有的节点进行计算,而是只有当某个节点样式需要进行计算时,才会向规则树添加计算的路径。

    这个想法相当于将规则树路径视为词典中的单词。如果我们已经计算出如下的规则树:

    enter image description here

    假设我们需要为内容树中的另一个元素匹配规则,并且找到匹配路径是 B - E - I(按照此顺序)。由于我们在树中已经计算出了路径 A - B - E - I - L,因此就已经有了此路径,这就减少了现在所需的工作量。

    让我们看看规则树如何帮助我们减少工作。

    结构划分

    样式上下文可分割成多个结构。这些结构体包含了特定类别(如 border 或 color)的样式信息。结构中的属性都是继承的或非继承的。继承属性如果未由元素定义,则继承自其父代。非继承属性(也称为“重置”属性)如果未进行定义,则使用默认值。

    规则树通过缓存整个结构(包含计算出的端值)为我们提供帮助。这一想法假定底层节点没有提供结构的定义,则可使用上层节点中的缓存结构。

    使用规则树计算样式上下文

    在计算某个特定元素的样式上下文时,我们首先计算规则树中的对应路径,或者使用现有的路径。然后我们沿此路径应用规则,在新的样式上下文中填充结构。我们从路径中拥有最高优先级的底层节点(通常也是最特殊的选择器)开始,并向上遍历规则树,直到结构填充完毕。如果该规则节点对于此结构没有任何规范,那么我们可以实现更好的优化:寻找路径更上层的节点,找到后指定完整的规范并指向相关节点即可。这是最好的优化方法,因为整个结构都能共享。这可以减少端值的计算量并节约内存。

    如果我们找到了部分定义,就会向上遍历规则树,直到结构填充完毕。

    如果我们找不到结构的任何定义,那么假如该结构是“继承”类型,我们会在上下文树中指向父代的结构,这样也可以共享结构。如果是 reset 类型的结构,则会使用默认值。

    如果最特殊的节点确实添加了值,那么我们需要另外进行一些计算,以便将这些值转化成实际值。然后我们将结果缓存在树节点中,供子代使用。

    如果某个元素与其同级元素都指向同一个树节点,那么它们就可以共享整个样式上下文。

    让我们来看一个例子,假设我们有如下 HTML 代码:

    <html>
     <body>
       <div class="err" id="div1">
         <p>
           this is a <span class="big"> big error </span>
           this is also a
           <span class="big"> very  big  error</span> error
         </p>
       </div>
       <div class="err" id="div2">another error</div>
     </body>
    </html>

    还有如下规则:

    div {margin:5px;color:black}
    .err {color:red}
    .big {margin-top:3px}
    div span {margin-bottom:4px}
    #div1 {color:blue}
    #div2 {color:green}

    为了简便起见,我们只需要填充两个结构:color 结构和 margin 结构。color 结构只包含一个成员(即“color”),而 margin 结构包含四条边。 形成的规则树如下图所示(节点的标记方式为“节点名 : 指向的规则序号”):

    enter image description here

    (图:规则树)

    上下文树如下图所示(节点名 : 指向的规则节点):

    enter image description here

    (图:上下文树)

    假设我们解析 HTML 时遇到了第二个 <div> 标记,我们需要为此节点创建样式上下文,并填充其样式结构。 经过规则匹配,我们发现该 <div> 的匹配规则是第 1、2 和 6 条。这意味着规则树中已有一条路径可供我们的元素使用,我们只需要再为其添加一个节点以匹配第 6 条规则(规则树中的 F 节点)。

    我们将创建样式上下文并将其放入上下文树中。新的样式上下文将指向规则树中的 F 节点。

    现在我们需要填充样式结构。首先要填充的是 margin 结构。由于最后的规则节点 (F) 并没有添加到 margin 结构,我们需要上溯规则树,直至找到在先前节点插入中计算过的缓存结构,然后使用该结构。我们会在指定 margin 规则的最上层节点(即 B 节点)上找到该结构。

    我们已经有了 color 结构的定义,因此不能使用缓存的结构。由于 color 有一个属性,我们无需上溯规则树以填充其他属性。我们将计算端值(将字符串转化为 RGB 等)并在此节点上缓存经过计算的结构。

    第二个 <span> 元素处理起来更加简单。我们将匹配规则,最终发现它和之前的 span 一样指向规则 G。由于我们找到了指向同一节点的同级,就可以共享整个样式上下文了,只需指向之前 span 的上下文即可。

    对于包含了继承自父代的规则的结构,缓存是在上下文树中进行的(事实上 color 属性是继承的,但是 Firefox 将其视为 reset 属性,并缓存到规则树上)。

    例如,如果我们在某个段落中添加 font 规则:

    p {font-family:Verdana;font size:10px;font-weight:bold}

    那么,该段落元素作为上下文树中的 div 的子代,就会共享与其父代相同的 font 结构(前提是该段落没有指定 font 规则)。

    在 Webkit 中没有规则树,因此会对匹配的声明遍历 4 次。首先应用非重要高优先级的属性(由于作为其他属性的依据而应首先应用的属性,例如 display),接着是高优先级重要规则,然后是普通优先级非重要规则,最后是普通优先级重要规则。这意味着多次出现的属性会根据正确的层叠顺序进行解析。最后出现的最终生效。

    因此概括来说,共享样式对象(整个对象或者对象中的部分结构)可以解决问题 1 和问题 3。Firefox 规则树还有助于按照正确的顺序应用属性。

    对规则进行处理以简化匹配

    样式规则有一些来源:

    • 外部样式表或样式元素中的 CSS 规则

      p {color:blue}
    • inline 样式属性及类似内容

      <p style="color:blue" />
    • HTML 可视化属性(映射到相关的样式规则)

      <p bgcolor="blue" />

    后两种很容易和元素进行匹配,因为元素拥有样式属性,而且 HTML 属性可以使用元素作为键值进行映射。

    我们之前在第 2 个问题中提到过,CSS 规则匹配可能比较棘手。为了解决这一难题,可以对 CSS 规则进行一些处理,以便访问。

    样式表解析完毕后,系统会根据选择器将 CSS 规则添加到某个哈希表中。这些哈希表的选择器各不相同,包括 ID、类名称、标记名称等,还有一种通用哈希表,适合不属于上述类别的规则。如果选择器是 ID,规则就会添加到 ID 表中;如果选择器是类,规则就会添加到类表中,依此类推。 这种处理可以大大简化规则匹配。我们无需查看每一条声明,只要从哈希表中提取元素的相关规则即可。这种优化方法可排除掉 95% 以上规则,因此在匹配过程中根本就不用考虑这些规则了 (4.1)。

    我们以如下的样式规则为例:

    p.error {color:red}
    #messageDiv {height:50px}
    div {margin:5px}

    第一条规则将插入类表,第二条将插入 ID 表,而第三条将插入标记表。

    对于下面的 HTML 代码段:

    <p class="error">an error occurred </p>
    <div id=" messageDiv">this is a message</div>

    我们首先会为 p 元素寻找匹配的规则。类表中有一个“error”键,在下面可以找到“p.error”的规则。div 元素在 ID 表(键为 ID)和标记表中有相关的规则。剩下的工作就是找出哪些根据键提取的规则是真正匹配的了。

    例如,如果 div 的对应规则如下:

    table div {margin:5px}

    这条规则仍然会从标记表中提取出来,因为键是最右边的选择器,但这条规则并不匹配我们的 div 元素,因为 div 没有 table 祖先。

    Webkit 和 Firefox 都进行了这一处理。

    以正确的层叠顺序应用规则

    样式对象具有每个可视化属性一一对应的属性(均为 CSS 属性但更为通用)。如果某个属性未由任何匹配规则所定义,那么部分属性就可由父代元素样式对象继承。其他属性具有默认值。

    如果定义不止一个,就会出现问题,需要通过层叠顺序来解决。

    样式表层叠顺序

    某个样式属性的声明可能会出现在多个样式表中,也可能在同一个样式表中出现多次。这意味着应用规则的顺序极为重要。这称为“层叠”顺序。根据 CSS2 规范,层叠的顺序为(优先级从低到高):

    1. 浏览器声明

    2. 用户普通声明

    3. 作者普通声明

    4. 作者重要声明

    5. 用户重要声明

    浏览器声明是重要程度最低的,而用户只有将该声明标记为“重要”才可以替换网页作者的声明。同样顺序的声明会根据特异性进行排序,然后再是其指定顺序。HTML 可视化属性会转换成匹配的 CSS 声明。它们被视为低优先级的网页作者规则。

    特异性

    选择器的特异性由 CSS2 规范定义如下:

    • 如果声明来自于“style”属性,而不是带有选择器的规则,则记为 1,否则记为 0 (= a)

    • 记为选择器中 ID 属性的个数 (= b)

    • 记为选择器中其他属性和伪类的个数 (= c)

    • 记为选择器中元素名称和伪元素的个数 (= d)

    将四个数字按 a-b-c-d 这样连接起来(位于大数进制的数字系统中),构成特异性。

    您使用的进制取决于上述类别中的最高计数。

    例如,如果 a=14,您可以使用十六进制。如果 a=17,那么您需要使用十七进制;当然不太可能出现这种情况,除非是存在如下的选择器:html body div div p ...(在选择器中出现了 17 个标记,这样的可能性极低)。

    一些示例:

     *             {}  /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
    li            {}  /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
    li:first-line {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
    ul li         {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
    ul ol+li      {}  /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
    h1 + *[rel=up]{}  /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
    ul ol li.red  {}  /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
    li.red.level  {}  /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
    #x34y         {}  /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
    style=""          /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */

    规则排序

    找到匹配的规则之后,应根据级联顺序将其排序。Webkit 对于较小的列表会使用冒泡排序,而对较大的列表则使用归并排序。对于以下规则,Webkit 通过替换“>”运算符来实现排序:

    static bool operator >(CSSRuleData& r1, CSSRuleData& r2)
    {
       int spec1 = r1.selector()->specificity();
       int spec2 = r2.selector()->specificity();
       return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2;
    }

    渐进式处理

    Webkit 使用一个标记来表示是否所有的顶级样式表(包括 @imports)均已加载完毕。如果在附加过程中尚未完全加载样式,则使用占位符,并在文档中进行标注,等样式表加载完毕后再重新计算。

    布局

    呈现器在创建完成并添加到呈现树时,并不包含位置和大小信息。计算这些值的过程称为布局或重排。

    HTML 采用基于流的布局模型,这意味着大多数情况下只要一次遍历就能计算出几何信息。处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,因此布局可以按从左至右、从上至下的顺序遍历文档。但是也有例外情况,比如 HTML 表格的计算就需要不止一次的遍历 (3.5)。

    坐标系是相对于根框架而建立的,使用的是上坐标和左坐标。

    布局是一个递归的过程。它从根呈现器(对应于 HTML 文档的 <html> 元素)开始,然后递归遍历部分或所有的框架层次结构,为每一个需要计算的呈现器计算几何信息。

    根呈现器的位置左边是 0,0,其尺寸为视口(也就是浏览器窗口的可见区域)。 所有的呈现器都有一个“laybout”或者“reflow”方法,每一个呈现器都会调用其需要进行布局的子代的 layout 方法。

    Dirty 位系统

    为避免对所有细小更改都进行整体布局,浏览器采用了一种“dirty 位”系统。如果某个呈现器发生了更改,或者将自身及其子代标注为“dirty”,则需要进行布局。

    有两种标记:“dirty”和“children are dirty”。“children are dirty”表示尽管呈现器自身没有变化,但它至少有一个子代需要布局。

    全局布局和增量布局

    全局布局是指触发了整个呈现树范围的布局,触发原因可能包括:

    1. 影响所有呈现器的全局样式更改,例如字体大小更改。

    2. 屏幕大小调整。

    布局可以采用增量方式,也就是只对 dirty 呈现器进行布局(这样可能存在需要进行额外布局的弊端)。

    当呈现器为 dirty 时,会异步触发增量布局。例如,当来自网络的额外内容添加到 DOM 树之后,新的呈现器附加到了呈现树中。

    enter image description here

    (图:增量布局 - 只有 dirty 呈现器及其子代进行布局 (3.6)。)

    异步布局和同步布局

    增量布局是异步执行的。Firefox 将增量布局的“reflow 命令”加入队列,而调度程序会触发这些命令的批量执行。Webkit 也有用于执行增量布局的计时器:对呈现树进行遍历,并对 dirty 呈现器进行布局。

    请求样式信息(例如“offsetHeight”)的脚本可同步触发增量布局。

    全局布局往往是同步触发的。

    有时,当初始布局完成之后,如果一些属性(如滚动位置)发生变化,布局就会作为回调而触发。

    优化

    如果布局是由“大小调整”或呈现器的位置(而非大小)改变而触发的,那么可以从缓存中获取呈现器的大小,而无需重新计算。

    在某些情况下,只有一个子树进行了修改,因此无需从根节点开始布局。这适用于在本地进行更改而不影响周围元素的情况,例如在文本字段中插入文本(否则每次键盘输入都将触发从根节点开始的布局)。

    布局处理

    布局通常具有以下模式:

    1. 父呈现器确定自己的宽度。

    2. 父呈现器依次处理子呈现器,并且:

    • 放置子呈现器(设置 x,y 坐标)。

    • 如果有必要,调用子呈现器的布局(如果子呈现器是 dirty 的,或者这是全局布局,或出于其他某些原因),这会计算子呈现器的高度。

    父呈现器根据子呈现器的累加高度以及边距和补白的高度来设置自身高度,此值也可供父呈现器的父呈现器使用。

    将其 dirty 位设置为 false。

    Firefox 使用“state”对象 (nsHTMLReflowState) 作为布局的参数(称为“reflow”),这其中包括了父呈现器的宽度。

    Firefox 布局的输出为“metrics”对象 (nsHTMLReflowMetrics),其包含计算得出的呈现器高度。

    宽度计算

    呈现器宽度是根据容器块的宽度、呈现器样式中的“width”属性以及边距和边框计算得出的。

    例如以下 div 的宽度:

    <div style="width:30%"/>

    将由 Webkit 计算如下(BenderBox 类,calcWidth 方法):

    • 容器的宽度取容器的 availableWidth 和 0 中的较大值。availableWidth 在本例中相当于 contentWidth,计算公式如下:

      clientWidth() - paddingLeft() - paddingRight()

      clientWidth 和 clientHeight 表示一个对象的内部(除去边框和滚动条)。

    • 元素的宽度是“width”样式属性。它会根据容器宽度的百分比计算得出一个绝对值。

    • 然后加上水平方向的边框和补白。

    现在计算得出的是“preferred width”。然后需要计算最小宽度和最大宽度。

    如果首选宽度大于最大宽度,那么应使用最大宽度。如果首选宽度小于最小宽度(最小的不可破开单位),那么应使用最小宽度。

    这些值会缓存起来,以用于需要布局而宽度不变的情况。

    换行

    如果呈现器在布局过程中需要换行,会立即停止布局,并告知其父代需要换行。父代会创建额外的呈现器,并对其调用布局。

    绘制

    在绘制阶段,系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上。绘制工作是使用用户界面基础组件完成的。

    全局绘制和增量绘制

    和布局一样,绘制也分为全局(绘制整个呈现树)和增量两种。在增量绘制中,部分呈现器发生了更改,但是不会影响整个树。更改后的呈现器将其在屏幕上对应的矩形区域设为无效,这导致 OS 将其视为一块“dirty 区域”,并生成“paint”事件。OS 会很巧妙地将多个区域合并成一个。在 Chrome 浏览器中,情况要更复杂一些,因为 Chrome 浏览器的呈现器不在主进程上。Chrome 浏览器会在某种程度上模拟 OS 的行为。展示层会侦听这些事件,并将消息委托给呈现根节点。然后遍历呈现树,直到找到相关的呈现器,该呈现器会重新绘制自己(通常也包括其子代)。

    绘制顺序

    CSS2 规范定义了绘制流程的顺序。绘制的顺序其实就是元素进入堆栈样式上下文的顺序。这些堆栈会从后往前绘制,因此这样的顺序会影响绘制。块呈现器的堆栈顺序如下:

    1. 背景颜色

    2. 背景图片

    3. 边框

    4. 子代

    5. 轮廓

    Firefox 显示列表

    Firefox 遍历整个呈现树,为绘制的矩形建立一个显示列表。列表中按照正确的绘制顺序(先是呈现器的背景,然后是边框等等)包含了与矩形相关的呈现器。这样等到重新绘制的时候,只需遍历一次呈现树,而不用多次遍历(绘制所有背景,然后绘制所有图片,再绘制所有边框等等)。

    Firefox 对此过程进行了优化,也就是不添加隐藏的元素,例如被不透明元素完全遮挡住的元素。

    Webkit 矩形存储

    在重新绘制之前,Webkit 会将原来的矩形另存为一张位图,然后只绘制新旧矩形之间的差异部分。

    动态变化

    在发生变化时,浏览器会尽可能做出最小的响应。因此,元素的颜色改变后,只会对该元素进行重绘。元素的位置改变后,只会对该元素及其子元素(可能还有同级元素)进行布局和重绘。添加 DOM 节点后,会对该节点进行布局和重绘。一些重大变化(例如增大“html”元素的字体)会导致缓存无效,使得整个呈现树都会进行重新布局和绘制。

    呈现引擎的线程

    呈现引擎采用了单线程。几乎所有操作(除了网络操作)都是在单线程中进行的。在 Firefox 和 Safari 中,该线程就是浏览器的主线程。而在 Chrome 浏览器中,该线程是标签进程的主线程。

    网络操作可由多个并行线程执行。并行连接数是有限的(通常为 2 至 6 个,以 Firefox 3 为例是 6 个)。

    事件循环

    浏览器的主线程是事件循环。它是一个无限循环,永远处于接受处理状态,并等待事件(如布局和绘制事件)发生,并进行处理。这是 Firefox 中关于主事件循环的代码:

    while (!mExiting)
       NS_ProcessNextEvent(thread);

    CSS2 可视化模型

    画布

    根据 CSS2 规范,“画布”这一术语是指“用来呈现格式化结构的空间”,也就是供浏览器绘制内容的区域。画布的空间尺寸大小是无限的,但是浏览器会根据视口的尺寸选择一个初始宽度。

    根据 www.w3.org/TR/CSS2/zindex.html,画布如果包含在其他画布内,就是透明的;否则会由浏览器指定一种颜色。

    CSS 框模型

    CSS 框模型描述的是针对文档树中的元素而生成,并根据可视化格式模型进行布局的矩形框。

    每个框都有一个内容区域(例如文本、图片等),还有可选的周围补白、边框和边距区域。

    enter image description here

    (图:CSS2 框模型)

    每一个节点都会生成 0..n 个这样的框。

    所有元素都有一个“display”属性,决定了它们所对应生成的框类型。示例:

    block  - generates a block box.
    inline - generates one or more inline boxes.
    none - no box is generated.

    默认值是 inline,但是浏览器样式表设置了其他默认值。例如,“div”元素的 display 属性默认值是 block

    您可以在这里找到默认样式表示例:www.w3.org/TR/CSS2/sample.html

    定位方案

    有三种定位方案:

    1. 普通:根据对象在文档中的位置进行定位,也就是说对象在呈现树中的位置和它在 DOM 树中的位置相似,并根据其框类型和尺寸进行布局。

    2. 浮动:对象先按照普通流进行布局,然后尽可能地向左或向右移动。

    3. 绝对:对象在呈现树中的位置和它在 DOM 树中的位置不同。

    定位方案是由“position”属性和“loat”属性设置的。

    • 如果值是 static 和 relative,就是普通流

    • 如果值是 absolute 和 fixed,就是绝对定位

    static 定位无需定义位置,而是使用默认定位。对于其他方案,网页作者需要指定位置:topbottomleftright

    框的布局方式是由以下因素决定的:

    • 框类型

    • 框尺寸

    • 定位方案

    • 外部信息,例如图片大小和屏幕大小

    框类型

    block 框:形成一个 block,在浏览器窗口中拥有其自己的矩形区域。

    enter image description here

    (图:block 框)

    inline 框:没有自己的 block,但是位于容器 block 内。

    enter image description here

    (图:inline 框)

    block 采用的是一个接一个的垂直格式,而 inline 采用的是水平格式。

    enter image description here

    (图:block 和 inline 格式)

    inline 框放置在行中或“行框”中。这些行至少和最高的框一样高,还可以更高,当框根据“底线”对齐时,这意味着元素的底部需要根据其他框中非底部的位置对齐。如果容器的宽度不够,inline 元素就会分为多行放置。在段落中经常发生这种情况。

    enter image description here

    (图:行)

    定位

    相对

    相对定位:先按照普通方式定位,然后根据所需偏移量进行移动。

    enter image description here

    (图:相对定位)

    浮动

    浮动框会移动到行的左边或右边。有趣的特征在于,其他框会浮动在它的周围。下面这段 HTML 代码:

    <p>
     <img style="float:right" src="images/image.gif" width="100" height="100">
     Lorem ipsum dolor sit amet, consectetuer...
    </p>

    显示效果如下:

    enter image description here

    (图:浮动)

    绝对定位和固定定位

    这种布局是准确定义的,与普通流无关。元素不参与普通流。尺寸是相对于容器而言的。在固定定位中,容器就是可视区域。

    enter image description here

    (图:固定定位)

    请注意,即使在文档滚动时,固定框也不会移动。

    分层展示

    这是由 z-index CSS 属性指定的。它代表了框的第三个维度,也就是沿“z 轴”方向的位置。

    这些框分散到多个堆栈(称为堆栈上下文)中。在每一个堆栈中,会首先绘制后面的元素,然后在顶部绘制前面的元素,以便更靠近用户。如果出现重叠,新绘制的元素就会覆盖之前的元素。

    堆栈是按照 z-index 属性进行排序的。具有“z-index”属性的框形成了本地堆栈。视口具有外部堆栈。

    示例:

    <style type="text/css">
         div {
           position: absolute;
           left: 2in;
           top: 2in;
         }
    </style>

    <p>
       <div
            style="z-index: 3;background-color:red; width: 1in; height: 1in; ">
       </div>
       <div
            style="z-index: 1;background-color:green;width: 2in; height: 2in;">
       </div>
    </p>

    结果如下:

    enter image description here

    (图:固定定位)

    虽然红色 div 在标记中的位置比绿色 div 靠前(按理应该在常规流程中优先绘制),但是 z-index 属性的优先级更高,因此它移动到了根框所保持的堆栈中更靠前的位置。


    转载于:https://my.oschina.net/anna153/blog/377259

    展开全文
  • how Linux work 精通Linux 中文第二 带书签,纯文字,非扫描 Brian Ward
  • Django-REST-framework教程中文版.pdf
  • how tomcat work

    2018-12-28 12:06:15
    how tomcat work 中文版pdf 还有源码可以看啊(中文名为 深度剖析tomcat)
  • 在这篇教程中,我们将创建一个工作队列(Work Queue),它会发送一些耗时的任务给多个工作者(Works )。 工作队列(又称:任务队列——Task Queues)是为了避免等待一些占用大量资源、时间的操作。当我们把任务...

    工作队列

    第一篇教程中,我们已经写了一个从已知队列中发送和获取消息的程序。在这篇教程中,我们将创建一个工作队列(Work Queue),它会发送一些耗时的任务给多个工作者(Works )。

    工作队列(又称:任务队列——Task Queues)是为了避免等待一些占用大量资源、时间的操作。当我们把任务(Task)当作消息发送到队列中,一个运行在后台的工作者(worker)进程就会取出任务然后处理。当你运行多个工作者(workers),任务就会在它们之间共享。

    这个概念在网络应用中是非常有用的,它可以在短暂的HTTP请求中处理一些复杂的任务。

    准备

    之前的教程中,我们发送了一个包含“Hello World!”的字符串消息。现在,我们将发送一些字符串,把这些字符串当作复杂的任务。我们没有真是的例子,例如图片缩放、pdf文件转换。所以使用 sleep()函数来模拟这种情况。我们在字符串中加上点号(.)来表示任务的复杂程度,一个点(.)将会耗时1秒钟。比 如”Hello…”就会耗时3秒钟。

    我们对之前教程的send.php做些简单的调整,以便可以发送随意的消息。这个程序会按照计划发送任务到我们的工作队列中。我们把它命名为new_task.php:

    $message = empty($argv[1]) ? 'Hello World!' : ' '.$argv[1];
    $exchange->publish($message, $routeKey);
    var_dump("[x] Sent $message");  
    

    我们的旧脚本(receive.php)同样需要做一些改动:它需要为消息体中每一个点号(.)模拟1秒钟的操作。它会从队列中获取消息并执行,我们把它命名为worker.php:

    function callback($envelope, $queue) {  
    $msg = $envelope->getBody();
    var_dump(" [x] Received:" . $msg);  
    sleep(substr_count($msg,'.'));  
    $queue->ack($envelope->getDeliveryTag());
    }
    

    轮询分发

    使用工作队列的一个好处就是它能够并行的处理队列。如果堆积了很多任务,我们只需要添加更多的工作者(workers)就可以了,扩展很简单。

    首先,我们先同时运行两个worker.php脚本,它们都会从队列中获取消息,到底是不是这样呢?我们看看。

    你需要打开三个终端,两个用来运行worker.php脚本,这两个终端就是我们的两个消费者(consumers)—— C1 和 C2。

    shell1

    $php worker.php
     [*] Waiting for messages. To exit press CTRL+C
    

    shell2

    $ php worker.php
     [*] Waiting for messages. To exit press CTRL+C
    

    第三个终端,我们用来发布新任务。你可以发送一些消息给消费者(consumers):

    shell3

    $ php new_task.php First message.
    

    shell3

    $ php new_task.php Second message..
    

    shell3

    $ php new_task.php Third message...
    

    shell3

    $ php new_task.php Fourth message....
    

    shell3

    $ php new_task.php Fifth message.....
    

    看看到底发送了什么给我们的工作者(workers):

    shell1

    $ php worker.php
     [*] Waiting for messages. To exit press CTRL+C
     [x] Received 'First message.'
     [x] Received 'Third message...'
     [x] Received 'Fifth message.....'
    

    shell2

    $ php worker.php
     [*] Waiting for messages. To exit press CTRL+C
     [x] Received 'Second message..'
     [x] Received 'Fourth message....'
    

    默认来说,RabbitMQ会按顺序得把消息发送给每个消费者(consumer)。平均每个消费者都会收到同等数量得消息。这种发送消息得方式叫做——轮询(round-robin)。试着添加三个或更多得工作者(workers)。

    消息响应

    当处理一个比较耗时得任务的时候,你也许想知道消费者(consumers)是否运行到一半就挂掉。当前的代码中,当消息被RabbitMQ发送给 消费者(consumers)之后,马上就会在内存中移除。这种情况,你只要把一个工作者(worker)停止,正在处理的消息就会丢失。同时,所有发送 到这个工作者的还没有处理的消息都会丢失。

    我们不想丢失任何任务消息。如果一个工作者(worker)挂掉了,我们希望任务会重新发送给其他的工作者(worker)。

    为了防止消息丢失,RabbitMQ提供了消息响应(acknowledgments)。消费者会通过一个ack(响应),告诉RabbitMQ已经收到并处理了某条消息,然后RabbitMQ就会释放并删除这条消息。

    如果消费者(consumer)挂掉了,没有发送响应,RabbitMQ就会认为消息没有被完全处理,然后重新发送给其他消费者(consumer)。这样,及时工作者(workers)偶尔的挂掉,也不会丢失消息。

    消息是没有超时这个概念的;当工作者与它断开连的时候,RabbitMQ会重新发送消息。这样在处理一个耗时非常长的消息任务的时候就不会出问题了。

    消息是没有超时这个概念的;当工作者与它断开连的时候,RabbitMQ会重新发送消息。这样在处理一个耗时非常长的消息任务的时候就不会出问题了。 之前的例子中我们使用$queue->ack()。当工作者(worker)完成了任务,就发送一个响应。

    function callback($envelope, $queue) {  
        $msg = $envelope->getBody();
        var_dump(" [x] Received:" . $msg);
        sleep(substr_count($msg,'.'));
        $queue->ack($envelope->getDeliveryTag());
    }
    $queue->consume('callback');
    

    运行上面的代码,我们发现即使使用CTRL+C杀掉了一个工作者(worker)进程,消息也不会丢失。当工作者(worker)挂掉这后,所有没有响应的消息都会重新发送。

    忘了响应

    一个很容易犯的错误就是忘了basic_ack,后果很严重。消息在你的程序退出之后就会重新发送,如果它不能够释放没响应的消息,RabbitMQ就会占用越来越多的内存。

    为了排除这种错误,你可以使用rabbitmqctl命令,输出messages_unacknowledged字段:

    ``` $ sudo rabbitmqctl listqueues name messagesready messages_unacknowledged Listing queues ... hello 0 0 ...done.

    ```

    消息持久化

    如果你没有特意告诉RabbitMQ,那么在它退出或者崩溃的时候,它将会流失所有的队列和消息。为了确保信息不会丢失,有两个事情是需要注意的:我们必须把“队列”和“消息”设为持久化。

    首先,为了不让队列丢失,需要把它声明为持久化(durable)

    $queue->setFlags(AMQP_DURABLE);
    

    尽管这行代码本身是正确的,但是仍然不会正确运行。因为我们已经定义过一个叫hello的非持久化队列。RabbitMq不允许你使用不同的参数重新定义一个队列,它会返回一个错误。但我们现在使用一个快捷的解决方法——用不同的名字,例如task_queue。

    $queue->setName('task_queue');
    $queue->setFlags(AMQP_DURABLE);
    $queue->declare();
    

    这个$queue->declare();必须在生产者(producer)和消费者(consumer)对应的代码中修改。

    这时候,我们就可以确保在RabbitMq重启之后queue_declare队列不会丢失。

    注意:消息持久化

    将消息设为持久化并不能完全保证不会丢失。以上代码只是告诉了RabbitMq要把消息存到硬盘,但从RabbitMq收到消息到保存之间还是有一 个很小的间隔时间。因为RabbitMq并不是所有的消息都使用fsync(2)——它有可能只是保存到缓存中,并不一定会写到硬盘中。并不能保证真正的 持久化,但已经足够应付我们的简单工作队列。如果你一定要保证持久化,你需要改写你的代码来支持事务(transaction)。

    公平分发

    你应该已经发现,它仍旧没有按照我们期望的那样进行分发。比如有两个工作者(workers),处理奇数消息的比较繁忙,处理偶数消息的比较轻松。然而RabbitMQ并不知道这些,它仍然一如既往的派发消息。

    这时因为RabbitMQ只管分发进入队列的消息,不会关心有多少消费者(consumer)没有作出响应。它盲目的把第n-th条消息发给第n-th个消费者。

    我们可以使用$channel->qos();方法,并设置prefetch_count=1。这样是告诉RabbitMQ,再同一时刻,不要发送超过1条消息给一个工作者(worker),直到它已经处理了上一条消息并且作出了响应。这样,RabbitMQ就会把消息分发给下一个空闲的工作者(worker)。

    $channel->qos(0,1);
    

    关于队列大小

    如果所有的工作者都处理繁忙状态,你的队列就会被填满。你需要留意这个问题,要么添加更多的工作者(workers),要么使用其他策略。

    整合

    new_task.py的完整代码:

    <?php
    
    /**
     * PHP amqp(RabbitMQ) Demo-2
     * @author  yuansir &/yuansir-web.com>
     */
    
    $exchangeName = 'demo';
    $queueName = 'task_queue';
    $routeKey = 'task_queue';
    $message = empty($argv[1]) ? 'Hello World!' : ' '.$argv[1];
    
    $connection = new AMQPConnection(array('host' => '127.0.0.1', 'port' => '5672', 'vhost' => '/', 'login' => 'guest', 'password' => 'guest'));
    $connection->connect() or die("Cannot connect to the broker!\n");
    
    $channel = new AMQPChannel($connection);
    $exchange = new AMQPExchange($channel);
    $exchange->setName($exchangeName);
    $queue = new AMQPQueue($channel);
    $queue->setName($queueName);
    $queue->setFlags(AMQP_DURABLE);
    $queue->declare();
    $exchange->publish($message, $routeKey);
    var_dump("[x] Sent $message");
    
    $connection->disconnect();
    

    我们的worker:

    <?php
    
    /**
     * PHP amqp(RabbitMQ) Demo-2
     * @author  yuansir &/yuansir-web.com>
     */
    $exchangeName = 'demo';
    $queueName = 'task_queue';
    $routeKey = 'task_queue';
    
    $connection = new AMQPConnection(array('host' => '127.0.0.1', 'port' => '5672', 'vhost' => '/', 'login' => 'guest', 'password' => 'guest'));
    $connection->connect() or die("Cannot connect to the broker!\n");
    $channel = new AMQPChannel($connection);
    $exchange = new AMQPExchange($channel);
    $exchange->setName($exchangeName);
    $exchange->setType(AMQP_EX_TYPE_DIRECT);
    $exchange->declare();
    $queue = new AMQPQueue($channel);
    $queue->setName($queueName);
    $queue->setFlags(AMQP_DURABLE);
    $queue->declare();
    $queue->bind($exchangeName, $routeKey);
    
    var_dump('[*] Waiting for messages. To exit press CTRL+C');  
    while (TRUE) {  
            $queue->consume('callback');
            $channel->qos(0,1);
    }
    $connection->disconnect();
    
    function callback($envelope, $queue) {  
            $msg = $envelope->getBody();
            var_dump(" [x] Received:" . $msg);
            sleep(substr_count($msg,'.'));
            $queue->ack($envelope->getDeliveryTag());
    }
    

    使用消息响应和prefetch_count你就可以搭建起一个工作队列了。这些持久化的选项使得在RabbitMQ重启之后仍然能够恢复。

    现在我们可以移步教程3学习如何发送相同的消息给多个消费者(consumers)

     转载自Ryan是菜鸟 | LNMP技术栈笔记

    转载于:https://www.cnblogs.com/grimm/p/5728743.html

    展开全文
  • 为什么我在WINXP下不能玩魔兽学院啊 你可以在桌面上...游戏下载 http://lib.verycd.com/2005/03/24/0000043488.html前言:魔兽学园本是studio.e.go公司发行的游戏men at work2的中文版。该游戏采用了2D和3D技术的结
  • 为什么我在WINXP下不能玩魔兽学院啊 你可以在桌面上《魔兽学院》的游戏图标上点右键,选择属性栏,然后选择“兼容...前言:魔兽学园本是studio.e.go公司发行的游戏men at work2的中文版。该游戏采用了2D和3D技术的结...
  • DAZ Studio Pro中文版

    2020-07-02 16:57:09
    教程: 1、在安装DAZ Studio Pro之前,先断开网络连接; 2、双击对应操作系统安装包开始安装,这里会显示版本为4.11,但实际安装完成为4.12; 3、选择是否更改安装路径;...8、运行软件再次选择work off
  • 移动通信(第二版)中文版

    热门讨论 2011-03-29 09:00:07
    《移动通信(第二版 中文版)》 作 者: JOCHEN SCHILLER 出 版 社: 高等教育出版社 目 录: 第一章 绪论 1.1应用 1.1.1车辆 1.1.2应急通信 1.1.3商用通信 1.1.4替代有线网络 1.1.5信息娱乐及其他 1.1.6与位置相关的...
  • Xenu中文绿色-Windows

    2018-12-19 16:41:47
    Xenu是一款功能简单、但对SEO十分重要的蜘蛛爬行模拟工具。这里分享的是绿色汉化,打开即可使用。使用说明:http://a3.work/a/share/tool/128.html
  • 本名为MAN AT WORK !2,山本大妈出品。 这个游戏对我来说是有纪念意义的。因为这是我买的第一个正版游戏,第一个GALGAME,并且我从这个游戏学会了玩RPG。当时是由华义出的简体,正版,就装在个挺漂亮的纸壳里,...
  • 启点CE过NP中文December 24 2018:Cheat Engine 6.8.2 Released: Here's a new version for the hollidays. Mainly minor improvements and some small bugfixes, but also a new 'ultimap like' feature called ...
  • the case of a replacement or service work. We have checked the content of this documentation for conformity with the hardware and software described. Nevertheless, discrepancies cannot be precluded,...
  • Android Training官方培训课程中文版 — Hu Kai Google Android团队在2012年的时候开设了Android Training板块 - http://developer.android.com/training/index.html,这些课程是学习Android应用开发的绝佳资料。 ...
  • It also covers the concepts of definable and high-uncertainty work, and the correlation between lean, the Kanban Method and agile approaches. Life Cycle Selection introduces the various life cycles ...
  • sublime中文汉化插件,详细说明请查看http://a3.work/a/share/tool/6.html
  • 本名为MAN AT WORK !2,山本大妈出品。 这个游戏对我来说是有纪念意义的。因为这是我买的第一个正版游戏,第一个GALGAME,并且我从这个游戏学会了玩RPG。当时是由华义出的简体,正版,就装在个挺漂亮的纸壳里,...
  • Python APISeafile Python APIThis tutorial show you how to use seafile-api, and will accomplish a "library copy" work under Ubuntu as example.Install Seafile ServerFirst of all, make sure you have Down...
  •  本次修订反映了6年来数据库领域总体的进步和发展动态,书中重点讲述了对象关系模型;介绍了一些系统(如oracle、db2和informix等)中通用的新概念;对隔离技术作了更新介绍;对运行结果的表述更现代。相对于初始...
  • Windows7简体中文版主页开放,点击下面的连接进入 [url='http://www.microsoft.com/china/windows/windows-7/whats-new-work.mspx']http://www.microsoft.com/china/windows/windows-7/whats-new-work.mspx[/url] ...
  • 桌面上建立一个Work文件夹 从Protein Data Bank 中下载胰岛素(4ins)的PDB文件,并将其保存在“Work”文件夹中 3.将文件输入格式设置为PDB,将文件名设置未下载的PDB文件 4. 将工作文件夹中输出文件格式设置为MOL...

空空如也

空空如也

1 2 3 4 5 ... 13
收藏数 247
精华内容 98
关键字:

work中文版