.. _design: 设计摘要 =============== libuv 是一个跨平台支持库,原先为 NodeJS 而写。它\ 围绕着事件驱动的异步I/O模型而设计。 这个库提供不仅仅是对不同I/O轮询机制的简单抽象,还包括: ‘句柄’和‘流’对套接字和其他实体提供了高级别的抽象; 也提供了跨平台的文件I/O和线程功能,以及其他一些东西。 这是一份图表解释了组成libuv的不同组件和它们相关联的子系统: .. image:: static/architecture.png :scale: 75% :align: center 句柄和请求 ^^^^^^^^^^^^^^^^^^^^ libuv 提供给用户使用两个抽象,与事件循环相配合: 句柄 和 请求。 句柄 表示能够在活动时执行特定操作的长期存在的对象。 比方说: - 一个准备句柄当活动时每一次循环迭代调用它的回调函数。 - 一个TCP服务器句柄每次有新连接时调用它的connection回调函数。 请求代表着(通常是)短期的操作。 这些操作可以通过一个句柄执行: 写请求用于在句柄上写数据;或是独立不需要句柄的:getaddrinfo 请求 不需要句柄,它们直接在循环上运行。 I/O 循环 ^^^^^^^^^^^^ I/O(或 事件)循环是 libuv 的核心组件。 它为全部I/O操作建立内容, 并且它必须关联到单个线程。 可以运行多个事件循环 只要每个运行在不同的线程。 libuv 事件循环(或任何其他涉及循环或句柄的API,就此而言) **不是线程安全的** 除非另行说明。 事件循环遵循很常见的单线程异步I/O方法:全部(网络) I/O 在非阻塞的套接字上执行,在给定平台上使用最好的可用的机制来轮询: epoll在Linux上、kqueue在OSX和其他BSD上,event ports 在SunOS上 和IOCP在Windows上。 作为循环迭代的一部分,循环将阻塞等待套接字上已被添加到轮询器的I/O活动, 并且回调函数将被触发以指示套接字状态 (可读、可写挂起),这样句柄能够读、写或执行所需要的I/O操作。 为了更好地理解事件循环怎么运作,下面的图表显示了\ 一次循环迭代的所有阶段: .. image:: static/loop_iteration.png :scale: 75% :align: center #. 循环概念 'now' 被更新。 在开始事件循环计的时候事件循环缓存当前的时间\ 以减少时间相关的系统调用的数目。 #. 如果循环处于 *活动* 状态的话一次迭代开始,否则的话循环立刻终止。 那么, 何时一个循环确定是 *活动* 的?如果一个循环有活动的和被引用的句柄、 活动的请求或正在关闭的句柄,它被确定为 *活动* 的。 #. 运行适当的计时器。 所有在循环概念 *now* 之前到期的活动的计时器的回调函数被调用。 #. 待处理的回调函数被调用。 大多数情况下,在I/O轮询之后所有的I/O回调函数会被调用。 然而有些情况下,这些回调推延到下一次迭代中。 如果前一次的迭代推延了任何的I/O回调函数的调用,回调函数将在此刻运行。 #. 空转句柄的回调函数被调用。 虽有不恰当的名字,当其活动时空转句柄在每次循环迭代时都会运行。 #. 准备句柄的回调函数被调用。 在循环将为I/O阻塞前,准备句柄的回调函数被调用。 #. 计算轮询时限。 在为I/O阻塞前,循环计算出它应该阻塞多长时间。 这些是计算时限的规则: * 如果循环使用 ``UV_RUN_NOWAIT`` 标志运行,时限是0。 * 如果循环即将被终止(:c:func:`uv_stop` 被调用),时限是0。 * 如果没有活动的句柄或请求,时限是0。 * 如果没有任何活动的空转句柄,时限是0。 * 如果有任何待处理的句柄,时限是0。 * 如果以上均不符合,采用最近的计时器的时限,如果没有活动计时器的话,为无穷大。 #. 循环为I/O阻塞。 此刻循环将按上一步计算的时限为I/O阻塞。 对于所有监视给定文件描述符读写操作的I/O相关的句柄, 在此刻它们的回调函数被调用。 #. 检查句柄的回调函数被调用。 在循环为I/O阻塞之后,检查句柄的回调函数被调用。 检查句柄基本上与准备句柄相辅相成。 #. 关闭 回调函数被调用。 如果一个句柄通过调用 :c:func:`uv_close` 被关闭, 它的回调函数将被调用。 #. 当循环以 ``UV_RUN_ONCE`` 的特别情况下,它意味着前移。 在I/O阻塞之后也许没有触发I/O回调函数,但是已经过去了一些时间, 所以可能有到期的计时器,这些计时器的回调函数被调用。 #. 迭代结束。 如果循环以 ``UV_RUN_NOWAIT`` 或 ``UV_RUN_ONCE`` 模式运行则 迭代结束且 :c:func:`uv_run` 将返回。 如果循环以 ``UV_RUN_DEFAULT`` 运行, 它将继续从头开始如果它仍是 *活动* 的,否则的话它也会结束。 .. important:: libuv 使用了一个线程池来使得异步文件I/O操作可实现, 但是网络I/O **总是** 在单线程中执行,即每个循环的线程。 .. note:: 虽然轮询机制不同,libuv 提供了在Unix系统和Windows下一致的执行模型。 文件 I/O ^^^^^^^^ 不像是网络 I/O,libuv 没有平台特定的文件I/O原语可以依靠, 因此目前的方案是在线程池中运行阻塞的文件I/O操作。 对跨平台文件I/O规划的详尽介绍,参考 `这篇文章 `_ 。 libuv 目前使用一个全局的线程池,各类循环都能够在它上面排队执行。 目前在这个池上运作的有3种操作: * 文件系统操作 * DNS功能 (getaddrinfo 和 getnameinfo) * 用户定义的代码于 :c:func:`uv_queue_work` .. warning:: 见 :c:ref:`threadpool` 部分获取更多详细信息,但是记好了此线程池的大小\ 非常有限。