精华内容
下载资源
问答
  • 很多成功的企业应用程序都是使用 Java EE 平台构建的。但是,Java EE 的设计原理并不能够有效地支持 Web 2.0 应用程序。深入了解 Java EE 和 Web 2.0 原理之间的脱节可帮助您制定明智的决策,从而使用各种方法和工具...

    很多成功的企业应用程序都是使用 Java EE 平台构建的。但是,Java EE 的设计原理并不能够有效地支持 Web 2.0 应用程序。深入了解 Java EE 和 Web 2.0 原理之间的脱节可帮助您制定明智的决策,从而使用各种方法和工具在一定程度上解决这种脱节。本文将解答 Web 2.0 和标准 Java EE 平台缘何成为失败的组合,并演示为何由事件驱动的异步架构更适合 Web 2.0 应用程序。本文还介绍了一些框架和 API,它们通过支持异步设计使得 Java 平台更加适合 Web 2.0。


    Java EE 原理和设想


    Java EE 平台的创建目的就是为企业到客户(B2C)和企业到企业(B2B)应用程序提供支持。企业发现了 Internet 的价值之后就开始使用它增强与合作伙伴和客户之间的现有业务流程。这些应用程序通常要与一个现有企业集成系统(EIS)进行交互。大多数常见基准测试(测试 Java EE 服务器的性能和可伸缩性)— ECperf 1.1、SPECjbb2005 和 SPECjAppServer2004(参阅参考资料)— 的用例都将这一点反映到了 B2C、B2B 和 EIS 中。类似地,标准的 Java PetStore 演示也是一个典型的电子商务应用程序。

    很多有关 Java EE 架构可伸缩性的明显和暗含的设想都反映在基准测试中:

    • 从客户机角度来看,请求吞吐量是影响性能的最重要特性。
    • 事务持续时间是最重要的性能因素,并且,缩减所有个体事务的持续时间将改善应用程序的总体性能。
    • 事务之间通常都是彼此独立的。
    • 除长期执行的事务以外,只有少数业务对象会受事务影响。
    • 应用服务器的性能和部署在同一管理域的 EIS 会限制事务的持续时间。
    • 通过使用连接池可以抵消一定的网络通信成本(在处理本地资源时产生)
    • 通过对网络配置、硬件和软件进行优化,可以缩短事务持续时间。
    • 应用程序所有者可以控制内容和数据。在不依赖外部服务的前提下,向用户提供内容的最重要限制因素是带宽。
    性能和可伸缩性问题

    Java EE 平台最初的设计目的是使用部署在单个管理域中的资源操作服务。其设想的前提是 EIS 事务生存期较短并且请求处理较快,从而使平台能够支持较高的事务负载。

    很多新兴架构方法和模式 — 例如对等(P2P)、面向服务架构和统称(非正式地)为 Web 2.0 的新型 Web 应用程序 — 不满足这些假设。在这些应用程序的使用场景中,请求处理将占用更长的时间。因此,当使用 Java EE 方法开发 Web 2.0 应用程序时,将出现严重的性能和可伸缩性问题。

    这些设想产生了以下 Java EE API 构建原理:

    • 同步 API。Java EE 在很多应用中都需要使用同步 API(重量级并且繁琐的 Java Message Service (JMS) API 基本上是惟一的例外)。这种需求更多地源于可用性的需要,而非性能需求。同步 API 易于使用并且开销较低。但需要处理大型多线程时,则会出现严重问题,因此 Java EE 严格限制未受控制的多线程处理。
    • 有限的线程池。人们很快发现线程是种重要的资源,并且当线程数量超过某一界限后,应用服务器的性能将显著下降。然而,根据每个操作都很短暂的设想,这些操作可以分配到一组有限的线程中,从而维持较高的请求吞吐量。
    • 有限的连接池。如果只使用一个数据库连接,则很难获得最优的数据库性能。虽然一些数据库操作可以并行执行,但是增加额外的数据库连接只能将应用程序提速到某一点。当连接数达到某一值后,数据库性能将开始下滑。通常,数据库连接的数量要小于 servlet 线程池中可用线程的数量。因此,连接池在创建时允许向服务器组件 — 例如 servlet 和 Enterprise JavaBeans (EJB) — 分配一个连接并在以后返回给连接池。如果连接不可用,组件将等待阻塞当前线程的连接。因为其他组件只对连接占用很短的时间,因此这种延迟通常较短。
    • 固定的资源连接。应用程序被假设只使用很少一些外部资源。与各个资源的连接工厂通过 Java Naming and Directory Interface (JNDI)(或 EJB 3.0 的依赖性注入)获得。实际上,支持与不同 EIS 资源进行连接的主要 Java EE API 只有企业 Web 服务 API(参见参考资料)。其他 API 多数都假设资源是固定的并且只有诸如用户凭证这样的额外数据应该提供给开放连接操作。

    在 Web 1.0 中,这些原理玩转得非常好。可以将一些独特的应用程序设计为遵守这些规则。但是,这些原理不能有效支持 Web 2.0。

    Web 2.0 带来的巨变

    Java EE 迎合 SOA

    SOA 的引入首先对 Java EE 提出了挑战。在 SOA 中,交互通常产生很高的吞吐量,并且由于要跨多个域到达服务端点,因此很可能会产生较高的延迟。一些交互可能还需要得到操作人员的允许,而这种批准流程可能会产生几小时到几周的延迟。各种中间流程通常会使延迟情况进一步恶化,SOA 的出现就是为了支持这些中间流程。

    通过利用事务消息传递 API 并引入业务流程概念,Java EE 平台已经解决了延迟带来的难题。SOAP-over-HTTP Web 服务调用模型和诸如 JMS 之类的消息传递服务之间一直不太匹配。HTTP 使用同步请求/响应模型并且没有提供任何内置的可靠特性。诸如 WS-Notification、WS-Reliability、WS-ReliableMessaging 和 WS-ASAP 这些规范试图针对部署在 B2B 环境中的 Web 服务解决这种错误匹配。但是对于 B2C 场景,通常部署的是富应用程序客户机,因为这种客户机可以使用特定于场景的交互模式(相对于 Web 应用程序)处理高延迟。

    Web 2.0 应用程序具有很多独特需求,因此,不适合将 Java EE 用于 Web 2.0 实现。其中一个需求就是,Web 2.0 应用程序更多地通过服务 API 使用另一个 Web 2.0 应用程序,而不是使用 Web 1.0 应用程序。Web 2.0 应用程序的一个更为重要的因素是,极度倾向于用户到用户(C2C)交互:应用程序所有者只生成一小部分内容;用户负责生成大部分内容。

    SOA + B2C + Web 2.0 = 高延迟

    在 Web 2.0 环境中,聚合应用程序经常使用通过 SOA 服务 API 公开的服务和提要(参见 Java EE 迎合 SOA)。这些应用程序需要在 B2C 环境中使用服务。例如,一个聚合应用程序可能从三个不同的数据源提取数据,如天气信息、交通信息和地图。检索这三种独特数据所需的时间延长了总的请求处理时间。不管数据源和服务 API 的数量是否增加,用户仍然期望得到具有高反应度的应用程序。

    诸如缓存这类技术可以缓解延迟问题,但是不适用于所有场景。比如,可以缓存地图数据来减少响应时间,但通常并不适合将搜索查询结果或者实时交通信息进行缓存。

    服务调用本来就是一种高延迟过程,在客户机和服务器上通常只分配很小一部分 CPU 资源。Web 服务调用的持续时间很大一部分用于建立连接和传输数据。因此,通常来讲,提升客户端或服务器端的性能对于减少调用持续时间效果甚微。

    更好的交互性

    Web 2.0 对用户参与的支持引发了另外一大挑战,因为应用程序要处理来自每个活动用户的更多数量的请求。下面这些理由证明了这一点:

    • 因为大多数事件是由其他用户的操作引起的,因此会引发更多相关事件,并且用户具备更强大的能力来生成事件。这些事件通常使用户能够更加积极地使用 Web 应用程序。
    • 应用程序为用户提供了更多的用例。Web 1.0 用户仅仅可以浏览类别、购买商品并跟踪他们的订单处理状态。现在,用户可以通过论坛、聊天、聚合等等方法与其他用户进行积极地交流,这将产生更高的通信负载。
    • 如今的应用程序越来越多地使用 Ajax 改善用户体验。与普通 Web 应用程序的页面相比,使用 Ajax 的 Web 页面加载要慢一些,因为页面是由一些静态内容、脚本(可能会非常大)和一些发往服务器的请求组成。加载完成后,Ajax 页面通常会向服务器生成一些短小的请求。

    高延迟和低带宽客户机

    以手机和其他限制带宽的客户机为目标的应用程序日趋流行。即使服务器可以为特定客户机提供快速服务,客户机仍然不能迅速地使用数据,这要归咎于其低带宽连接和设备本身的物理限制。虽然客户机是通过低吞吐量连接加载数据,服务器在占用 servlet 线程时仍然未被充分利用或处于等待状态。随着越来越多的移动设备使用网络服务以及无线频段的充分利用,这类客户机的吞吐量和延迟性将逐渐减少,除非开发出一种通信机制来提供更好的可伸缩性。

    与典型的 Web 1.0 应用程序相比,这些因素往往会生成更多的服务器通信量和请求数。在高负载期间,这种通信量难于控制(然而,Ajax 也提供了更多的机会对通信量进行优化;与支持相同用例的简单 Web 应用程序相比,Ajax 生成的通信量通常更少)。

    更多内容

    Web 2.0 的特征就是比上一代 Web 应用程序拥有更大量的内容和更大的规模。

    在 Web 1.0 世界中,内容通常只有经过业务实体的明确允许后才被发布到公司网站。企业需要控制所显示的文本的每个字符。因此,如果计划发布的内容超出了框架的大小限制,则要对内容进行优化或将其分成几个较小的部分。

    Web 2.0 站点的一个特性就是不会限制内容的大小或创建。大部分 Web 2.0 内容由用户和社区生成。组织和企业仅仅提供工具实现内容创建和发布。由于使用了大量图像、音频和视频,内容的大小也相应增加。

    持久连接

    建立客户机到服务器的新连接会耗费很多时间。如果某些交互在预期之中,则建立一次客户机/服务器通信,然后重复使用该连接,这样做会获得更高的效率。持久连接对于发送客户机通知也很有用。但是 Web 2.0 应用程序的客户机通常位于防火墙之后,一般很难或不能直接建立服务器到客户机的连接。Ajax 应用程序需要发送请求轮询特定事件。要减少轮询请求的数量,一些 Ajax 应用程序使用Comet 模式(参见 参考资料):该服务器被设计为在某个事件发生以前保持等待状态,然后发送应答,同时保持连接打开。

    对等消息传递协议,如 SIP、BEEP 和 XMPP,逐渐使用持久连接。流式直播视频也从持久连接中获益良多。

    更容易发生 Slashdot 效应

    Web 2.0 应用程序拥有大量的访客,这一点使某些站点更容易发生 “Slashdot 效应” — 如果某个流行的 blog、新闻站点或社会型网站提及某个站点时,该站点的通信量负载会猛增(参见参考资料)。所有 Web 站点都应该准备好处理比普通负载高几个数量级的通信量。这种情况下更重要的一点是,站点在如此高的负载下不会发生崩溃。

    延迟问题

    与操作吞吐量相比,操作延迟对 Java EE 应用程序的影响更大。即使应用程序使用的服务可以处理大量操作,延迟仍然保持不变或者进一步恶化。目前的 Java EE API 还无法很好地处理这一情况,因为这种情况违背了这些 API 设计中暗含的延迟假设。

    在使用同步 API 时,为论坛或 blog 中的大型页面提供服务将开启一个处理线程。如果每个页面需要一秒钟的服务时间(例如 LiveJournal 这类应用程序,包含很多较大的页面),并且线程池包含 100 个线程,那么一秒钟的时间内无法为超过 100 个页面提供服务 — 这种性能无法接受。增加线程池中的线程数量收效甚微,因为当线程池中的线程数量增加时,应用程序-服务器性能将开始降低。

    Java EE 架构无法利用 SIP、BEEP 和 XMPP 这样的消息传递协议,因为 Java EE 的同步 API 持续使用单个线程。由于应用服务器使用有限的线程池,持续使用一个线程将使应用服务器在使用这些协议发送和接收消息时无法处理其他请求。同样要注意,使用这些协议发送的消息可长可短(尤其在使用 BEEP 时),并且要生成这些消息,还需要使用 Web 服务或其他方法访问部署在其他组织内的资源。此外,BEEP 和 Stream Control Transmission Protocol (SCTP) 这样的传输协议在 TCP/IP 连接之上还需要建立一些同步的逻辑连接,这使线程管理问题变得更加严重。

    要实现流式场景,Web 应用程序必须摒弃标准的 Java EE 模式和 API。因此,Java EE 很少用于运行 P2P 应用程序或流视频。对于经常使用 Java Connector Architecture (JCA) 连接器实现专有异步逻辑的协议,常常开发自定义组件来处理(正如后文将介绍的,新一代 servlet 引擎也支持使用非标准接口处理 Comet 模式。然而,就 API 和使用模式而言,这种支持与标准的 servlet 接口截然不同)。

    最后,回想一下 Java EE 的一个基本原理,即对网络基础结构进行优化可以缩短事务持续时间。但是,对于现场直播的视频提要,提升网络基础结构的速度丝毫不会减少请求的持续时间,因为视频流是边生成边发送给客户机的。对网络基础结构进行优化只会增加流的数量,从而支持更多的客户机并以更高的分辨率处理流。

    异步方法

    要避免我们讨论的这些问题,一个可行的方法就是在设计应用程序时将延迟纳入到考虑事项中并使用由事件驱动的异步方法实现应用程序。如果应用程序处于空闲状态,则不会占用线程这样的有限资源。通过使用异步 API,应用程序将对外部事件进行轮询并在事件发生后执行相应的操作。通常,这种应用程序被分为若干个事件循环,每个循环都有自己独有的线程。

    事件驱动异步设计的一个明显优点就是,如果大量等待外部服务的操作之间没有数据依赖关系,则可以并行执行这些操作。即使根本没有发生并行操作,事件驱动的异步架构也提供了优于传统同步设计的强大的可伸缩性。

    异步 API 优点:概念证明模型

    可以通过一个简单的 servlet 流程模型演示异步 API 带来的可伸缩性优势(如果您已经确信异步设计能够满足 Web 2.0 应用程序的可伸缩性需求,那么可以跳过本节内容,直接了解可用来解决 Web 2.0 / Java EE 问题的解决方案讨论)。

    在我们的模型中,servlet 流程对到来的请求执行一些处理,对数据库进行查询,然后使用从数据库获取的信息调用 Web 服务。最后,根据 Web 服务的响应生成最终的响应。

    模型的 servlet 使用两种类型的资源,并伴有较高的延迟。在逐渐增加的负载之下,这些资源的特征和行为都互不相同:

    • 数据库连接。这种资源通常以 DataSource 的形式用于 Web 应用程序,它提供了数量有限的连接,通过这些连接实现同步处理。
    • 网络连接。这种资源用于编写对客户机的响应并调用 Web 服务。直到现在为止,这种资源在大多数应用服务器中都受到限制。然而,新一代应用服务器开始使用nonblocking I/O (NIO) 实现这种资源,因此我们可以根据需要使用任意数量的同步网络连接。模型 servlet 在以下几种情形中使用这种资源:
      1. 调用 Web 服务。尽管目标服务器每秒可以处理的请求的数量是有限制的,但是这个数量通常都很高。调用持续时间取决于网络通信量。

      2. 从客户机读取请求。我们的模型忽视了这一开销,因为模型假定使用了一个 HTTP GET 请求。在这种情形下,从客户机读取请求所需的时间不会添加到 servlet 请求持续时间中。

      3. 向客户机发送响应。我们的模型忽视了这一开销,因为,对于较短的 servlet 响应来说,应用服务器可以在内存中缓冲该响应,然后再使用 NIO 将它发送给客户机。并且我们假设这个响应非常短小。在这种情形下,向客户机发送响应所需的时间不会添加到 servlet 请求持续时间中。

    让我们假设 servlet 执行时间被划分为如表 1 所示的几个阶段:


    表 1. Servlet 操作时限(以抽象单位表示持续时间)
    阶段 持续时间 操作
    1 2 个单位 解析 servlet 请求信息
    2 8 个单位 处理本地数据库事务
    3 2 个单位 处理数据库请求结果并准备远程调用
    4 16 个单位 使用一个 Web 服务调用远程服务器
    5 4 个单位 创建响应
    总用时: 32 个单位  


    图 1 展示了执行期间业务逻辑、数据库和 Web 服务之间的时间分布:


    图 1. 执行步骤的时间分布
    执行步骤的时间分布

    这些选择的时限提供了一个可读的图表。在实际中,大多数 Web 服务进行处理使用的时间远远超过图表显示的时间。可以这样讲,Web 服务的处理时间要比业务逻辑 Java 代码的处理时间高出 100 到 300 倍。但是,为了演示同步调用模型,我们挑选了一些不太符合现实的参数,比如,Web 服务性能极其快,或者应用服务器速度很慢,或两者兼有。

    让我们假设连接池的容量为 2。因此,同一时间内只能处理两个数据库事务。(对于真实的应用服务器,实际的线程数和连接数要比这个数大)。

    我们还假设 Web 服务调用使用的时间相同并且全部可以并行处理。这一假设比较符合实际,因为 Web 服务交互过程包括来回发送数据。执行实际的处理只是 Web 服务调用的一小部分。

    对于这种场景,同步和异步用例在低负载下表现相同。如果数据库查询和 Web 服务调用并行进行,异步用例表现更加良好。在发生超载时,比如访问量忽然达到峰值,将看到一个有趣的结果。我们假设同一时刻有 9 个请求。对于同步用例,servlet 引擎线程池有三个线程。而对于异步用例,我们只使用一个线程。

    注意,在这两个用例中,所有 9 个连接在到达时全部被接受(大多数 servlet 引擎都会这样做)。然而,在处理前三个连接时,同步用例没有对接受的其他六个连接进行处理。

    图 2 和图 3 是使用一个简单的模拟程序创建的,它分别模拟同步和异步 API 用例:


    图 2. 同步用例
    同步用例

    点击这里查看完整图像)。

    图 2 中的每个矩形表示流程的一个步骤。矩形中的第一个数字是流程编号(1 到 9),第二个数字是流程内的阶段编号。每个流程使用惟一的颜色标记。注意,数据库和 Web 服务操作位于单独的行中,因为它们分别由数据库引擎和 Web 服务实现执行。servlet 引擎在等待结果期间不执行任何操作。浅灰色区域表示空闲(等待)状态。

    图表底部的菱形标记表示在该点完成了一个或多个请求。标记的第一个数字表示以抽象单位计算的时间;第二个使用圆括号括起的可选数字表示在该点终止的请求数。在图 2 中可以看到,前两个请求在点 32 处完成,最后一个请求在点 104 处完成。

    现在假设数据库和 Web 服务客户机运行时支持异步接口。并且假设所有异步 servlets 只使用一个线程(但是,如果提供了额外线程的话,异步接口非常适合使用额外线程)。图 3 显示了结果:


    图 3. 异步用例
    异步用例

    单击这里查看完整图像)。

    图 3 中有几处需要注意。第一个请求要比同步用例中晚结束 23%。但是,最后一个请求则快了 26%。并且所使用的线程只是同步用例的三分之一。请求执行时间的分布更加有规律,因此用户可以以更加有规律的速度接收页面。第一个请求和最后一个请求的处理时间相差了 80%。在同步接口用例中,这个值达到了 225%。

    现在假设我们对应用程序和数据库服务器进行了升级,它们的性能提升了两倍。表 2 展示了用时结果(使用与表 1 相关的单位):


    表 2. 升级后的 Servlet 操作时限
    阶段 持续时间 操作
    1 1 个单位 解析 servlet 请求信息
    2 4 个单位 执行本地数据库事务处理
    3 1 个单位 处理数据库请求结果并为远程调用做准备
    4 16 个单位 使用 Web 服务调用远程服务器
    5 2 个单位 创建响应
    总用时: 24 个单位  

    可以看到,单个请求处理时间一般为 24 个时间单位,大概是原来的请求持续时间的 3/4。

    图 4 展示了业务逻辑、数据库和 Web 服务之间的新的分布:


    图 4. 升级后的步骤时间分布
    升级后的步骤时间分布

    图 5 展示了同步处理后的结果。可以看到,总体持续时间减少了 25%。但是,步骤的分布模式没有发生很大变化,并且 servlet 线程处于等待状态的时间更长了。


    图 5. 升级后的同步用例
    升级后的同步用例

    单击这里查看完整图像)。

    图 6 展示了异步 API 的处理结果:


    图 6. 升级后的异步用例
    升级后的异步用例

    单击这里查看完整图像)。

    使用异步 API 得到的结果非常有趣。数据库和应用服务器的性能提高时,处理可以很好地进行相应扩展。结论已经得到证明,并且最差和最佳请求处理时间相差只有 57%。总的处理时间(截至最后一个请求完成)是升级之间所使用时间的 57%。与同步用例的 75% 相比,这是一个很显著的改进。最后一个请求(两种情况中的第 9 个请求)要比同步用例中早完成 40%,而第一个请求仅仅比同步用例晚 14%。此外,在异步用例中,可以执行更多数量的并行 Web 服务操作。而使用同步则无法达到这种并行程度,因为 servlet 线程池中的线程数是有限制的。即使 Web 服务能够处理更多的请求,servlet 也不会发送请求,因为它不处于活动状态。

    实际的测试结果表明,异步应用程序具有更好的可伸缩性并且可以更从容地应对超载情况。延迟问题非常棘手,并且 Moore 定律(参见 参考资料)也帮不了什么忙。大多数现代计算改进增加了所需的带宽。多数情况下,延迟可能维持不变,甚至进一步恶化。正因为如此,开发人员才尝试将异步接口引入到应用服务器中。

    目前,可以使用很多方法实现异步系统,但是还未将其中任何一种方法确立为事实标准。每种方法都各有优缺点,并且它们在不同的情形中扮演不同的角色。本文后面的内容将对这些机制进行大致介绍,包括各种机制的优缺点,使您能够使用 Java 平台构建事件驱动的异步应用程序。

    一般解决方案

    Ad-hoc 并发性和 NIO

    自 Java 1.4 起,Java 语言提供了一个非阻塞网络 I/O API(java.nio.*)。而从 Java SE 5 开始,Java 提供了更加标准的并发性工具(java.util.concurrent.*)。开发人员利用非阻塞 I/O 和并发性实现的应用程序能够通过可用的 API 和框架支持大量同步连接。

    然而,这些 API 仍然处于较低的级别,并且通常只有在无法使用其他方式解决性能问题时才得到使用。NIO 选择器机制是一种非常低级的 API。使用它很难编写任何比复制流更复杂的操作。编写使用相同 NIO 选择器的独立模块也很困难。需要开发一个框架来封装 NIO 并简化这种类型的开发。

    鉴于这些原因,很少直接使用 NIO API。应用程序通常使用一个更可用的接口将 NIO API 封装起来。和众多 API 相比,NIO API 有其独特的作用,但是不应该强制应用程序编程人员直接使用它。

    使用并发性工具编写的应用程序则很少发生由于多线程处理引发的故障,因为 Java 5 的并发性工具提供了更高级的操作。然而,很容易发生死锁并且难于调试和查找错误根源

    为了在 Java 平台上以通用的方式支持异步交互,人们作出了很多尝试。所有这些尝试都基于一个消息传递通信模型。大部分使用了 actor 模型的一个变体来定义对象。此外,这些框架在可用性、可用库和方法方面各有不同。参见参考资料 中有关这些项目的 Web 站点和相关信息的链接。

    阶段式事件驱动架构

    阶段式事件驱动架构(SEDA)是一种有趣的框架,它将异步编程和自主计算的原理结合在一起。SEDA 是 J2SE 1.4 对 Java NIO API 引入的最大一项补充。该项目本身已经被中断,但是 SEDA 为 Java 应用程序的可伸缩性和适应性设定了新的基准,并且其有关异步 API 的思想对其他项目也产生了影响。

    SEDA 试图将异步和同步 API 设计结合起来,产生有趣的结果。这个框架具有比 ad-hoc 并发性 更加良好的可用性,但它还无法达到用户认可的程度。

    使用 SEDA,应用程序被划分为若干个阶段。每个阶段表示的组件包含一定数量的线程。请求被分配到一个阶段然后进行处理。阶段可以通过以下几种方式管理自身的容量:

    • 根据负载增加和减少使用线程的数量。这允许服务器动态适应组件的实际使用情况。如果某个组件的使用急剧上升,则会分配更多线程。如果为空闲状态,则减少线程的数量。
    • 根据负载更改行为。例如,可以根据负载生成更加简单的页面。避免对页面使用图像,使用更少的脚本,禁用不必要的功能等等。用户仍然可以使用应用程序,但是生成的请求和通信量将变少。
    • 对试图纳入请求或拒绝接受请求的阶段进行阻塞。

    前两种方法非常不错,采用了智能应用程序实现自主计算的思想。然而,第三种方法揭示了为什么该框架至今无法得到广泛应用的原因。除非在设计应用程序时加倍小心,否则这样做会因为增加了死锁风险而引入一个故障点。下面介绍了致使该框架难于使用的其他一些原因:

    • 阶段是一种非常粗粒度的组件。比如网络接口和 HTTP 支持。在将网络层作为整体处理时,很难解决诸如某些客户机带宽有限这样的问题。
    • 无法使用简单的方法返回异步调用的结果。结果只是被分配给阶段,寄希望于阶段能自己找到相关的操作状态。
    • 目前,大多数可用的 Java 库都是同步的。框架并没有尝试以一种一致的方式将同步代码从异步代码中分离开来,从而使编写出的代码很容易意外阻塞整个阶段。

    贯彻 SEDA 项目思想的实现中部署最多的可能是 Apache MINA 框架(参见 参考资料)。它用于 OSFlash.org Red5 流服务器的实现、Apache Directory Project 和 Jive Software Openfire XMPP Server。

    E 编程语言

    严格来讲,E 编程语言是一种动态输入的函数性编程语言,而非一种框架。它强调提供安全的分布式计算,它还为异步编程提供了一些有趣的概念。在异步编程方面,该语言仿效了其前辈 Joule 和 Concurrent Prolog,但是其并发性支持和整体语法更加自然,而且对于拥有主流编程语言(例如 Java 语言、JavaScript 和 C#)背景的编程人员来说也十分友好。

    该语言目前通过 Java 和 Common-Lisp 实现。可以通过 Java 应用程序使用。但是,要将其应用于高负荷的服务器端应用程序,仍然存在着一些障碍。大多数问题源于其早期开发,但将来很可能会得到解决。其他一些问题则是由该语言的动态特性引起的,但是这些问题大部分与该语言提供的并发性扩展并无关系。

    E 提供了以下核心语言概念来支持异步编程:

    • vat 表示对象的容器。所有对象都保存在一些 vat 的上下文中,并且不能从其他 vat 同步访问这些对象。
    • promise 变量用来表示某些异步操作的结果。它的初始状态为未解决状态,表示该操作还未结束。完成操作后,它会获得一个值或者出现 错误。
    • 任何对象都可以接收消息并进行本地调用。本地对象可以通过即时的调用操作进行同步调用,也可以通过最终的发送操作进行异步调用。只能使用最终的发送操作对远程对象进行调用。最终的调用将生成一个 promise。. 操作符用于即时调用,而<- 操作符用于最终调用。
    • Promise 也可通过显式方式创建。此时,将提供对解析器对象的引用,并传递给其他 vat。这个对象有两个方法:resolvesmash
    • when 操作符允许您在 promise 执行 resolvesmash 时调用一些代码。when 操作符中的代码被处理为闭包,并且通过访问外围范围中的定义执行。这种方式类似于匿名的内部 Java 类访问一些方法范围内的定义。

    这几个概念构成了一个功能强大的可用系统,允许轻松地创建异步组件。即使不是在生产环境中使用该语言,仍然可以使用它原型化复杂并发性问题。它执行一种消息传递规程并提供方便的语法来处理并发性问题。其操作符也十分简单,并且可以在其他编程语言中进行模仿,虽然产生的代码很可能不及原始代码那么优雅和简单。

    E 增强了异步编程的可用性。该语言提供的并发支持与其他语言特性毫不相关,并且它可能对现有语言进行了改进。在 Squeak、Python 和 Erlang 开发环境中已经对这些语言特性进行了讨论。与更加特定于域的语言特性(如 C# 中的迭代器)相比,这种语言特性可能更为有用。

    AsyncObjects 框架

    AsyncObjects 框架项目侧重于使用纯 Java 代码创建可用的异步组件框架。该框架尝试将 SEDA 和 E 编程语言结合在一起。与 E 相同,它提供了基本的并发性机制。同样,它也仿效 SEDA 来提供机制集成同步 Java API。该框架的第一个原型版本发行于 2002 年。自此之后,该框架的开发变得消极起来,但是最近,该项目重新开始活跃。E 已经展示了异步编程的可用性,而这个框架将试图使用纯 Java 代码获得同样的可用性。

    和 SEDA 相同,应用程序被分为若干个事件循环。但是,该项目目前还没有实现任何类似 SEDA 的自管理特性。与 SEDA 不同的是,它对 I/O 使用了更加简单的负载管理机制,因为组件更加细粒度化并且 promise 可用来接收操作结果。

    框架实现了与 E 编程语言相同的异步组件概念、vat、和 promise。因为不能使用纯 Java 代码引入新的操作符,并不是任意一个对象都是异步组件。实现需要扩展某个基类,并且应该提供由框架实现的异步接口。由框架提供的异步接口实现将向组件的 vat 发送消息,而 vat 稍后将消息分配给组件。

    该框架的当前版本(0.3.2)与 Java 5 兼容并且支持泛型。如果当前平台支持的话,也将使用 Java NIO。但是,框架能够返回到普通套接字。

    该框架最大的一个问题是类库非常匮乏,因为很难实现与同步 Java API 的集成。目前,只实现了网络 I/O 库。但是,最近作出的一些改进 — 例如 Axis2 中的异步 Web 服务和前面描述的 Tomcat 6 的 Comet Servlet(参见特定于 Servlet 或特定于 IO 的 API)— 可以简化这种集成。

    Waterken 的 ref_send

    Waterken 的 ref_send 框架是使用 Java 编程语言实现 E 思想的又一尝试。它主要通过 Java 语言的一个子集(称为 Joe-E)实现。

    该库支持最终的操作调用。然而,与 AsyncObjects 中的支持相比,这种支持的自主性较低。该框架的当前版本还存在线程安全问题。

    它只发布了一些核心类和一些非常小的示例,并且没有提供重要的应用程序和类库。因此,关于如何在更大的范围内实现框架思想仍不明确。框架构建者宣称目前正在实现一个完整的 Web 服务器并且即将发布。等到发布之后在重新审视这个框架可能会更加有趣。

    Frugal Mobile Objects

    Frugal Mobile Objects 是另一种基于 actor 模型的框架。它以诸如 Java ME CLDC 1.1 这样的资源受限环境为目标,使用有趣的设计模式减少资源使用量,同时保持接口具有适当的简单性。

    该框架表明应用程序在性能和可伸缩性方面会从异步设计中获益 — 甚至在一个资源受限的环境中。

    该框架提供的 API 看似非常繁琐,但是框架的受限目标环境充分证明了这些 API 的有效性。

    Scala actor

    Scala 是另一种面向 Java 平台的编程语言。它提供了一个 Java 特性超集,但是却使用了稍有不同的语法。与普通的 Java 编程语言相比,它提供了一些可用性增强。

    其中一个有趣特性就是基于 actor 的并发性支持,这一点模拟了 Erlang 编程语言。它的设计似乎还没有最终确定,但是这种特性基本可用并且得到该语言的语法支持。然而,与 E 的并发性支持相比,Scala 的跟 Erlang 类似的并发性支持的可用性和自主性较低。

    Scala 模型还存在一些安全性问题,因为每条消息都传递对调用方的引用。这使得被调用的组件可以调用调用方组件的所有操作,而不仅仅是返回调用值。就这方面来说,E 的 promise 模型更具粒度化。这种机制用来与阻塞进行通信,目前还没有完全开发完毕。

    Scala 的优点在于它可以编译为 JVM 字节码。理论上讲,它可以用于 Java SE 和 Java EE 应用程序,并且不会带来性能损失。然而,对于商业开发的适用性则另当别论,因为 Scala 的 IDE 支持有限,并且,与 Java 语言不同的是,它尚不具备供应商支持。因此,只能用于生命周期较短的项目(如原型),但是,如果对生命周期较长的项目使用该语言,则会添加很多风险。

    特定于 Servlet 或特定于 I/O 的 API

    由于我们讨论的这些问题只要针对 servlet、Web 服务和一般的 I/O 级别,因此使用了一些项目来专门解决这些问题。这些解决方案的最大缺陷就是它们只针对有限类别的应用程序解决问题。如果不能对本地和远程资源进行异步调用,即使能够实现异步 servlet 也毫无用处。它还应该能够编写一个异步模型和业务逻辑代码。另一个常见问题是解决方案的可用性,通常要低于普通的解决方案。

    然而,作为为实现异步组件而作出的努力,这些尝试都值得关注。参见 参考资料 中有关这些项目的 Web 站点和相关信息的链接。

    JSR 203(NIO.2)

    JSR 203 是 NIO API 的改进版。在撰写本文时,它仍然处于初期的草案阶段,在开发过程中可能发生了很多重大修改。其目标是将 API 纳入到 Java 7 中。

    JSR 203 引入了异步通道(asynchronous channel)概念。目的是解决众多编程问题,但是似乎 API 仍然非常低级。它最终引入了以前版本所不具备的异步 File I/O API,并且IoFutureCompletionHandler 概念使它可以更轻松地使用其他框架中的类。一般来讲,新的异步 NIO API 要比上一代 API 中基于选择器的 API 更加易用。甚至可以将它直接用于简单的任务,而不需要编写自定义包装器。

    然而,这种 JSR 的一大缺点就是,它高度特定于文件和套接字 I/O。它没有提供构建块来创建更高级的异步组件。可能提供了高级的类,但是必须提供自己的方法来执行相同的任务。这看似是一个不错的技术理念,因为在 Java 语言中仍然没有出现标准的异步组件开发方法。

    Glassfish Grizzly NIO

    Glassfish Grizzly NIO 支持类似于 SEDA 框架,并且继承了大部分 SEDA 问题。然而,它提供了对 I/O 任务的更加具体化的支持。所提供的 API 要比普通 NIO API 更加高级,但是使用起来仍然很枯燥。

    Jetty 6 continuation

    Jetty continuation 是一种与传统方法截然不同的方法。甚至可以将之称为一种快速补丁(quick hack)。servlet 可能会请求一个 continuation 对象并调用具有指定超时的suspend() 方法。该操作将抛出一个异常。然后再对 continuation 调用一个恢复操作,或者 continuation 超过指定时间后自动重新开始执行。

    因此 Jetty 尝试实现一个具有异步语义的同步查找 API。然而,这种行为将打断客户机的预测,因为 servlet 将从头执行方法,而不是从调用 suspend() 的位置执行。

    Apache Tomcat 6 Comet API

    Tomcat Comet API 专门为支持 Comet 交互模式而设计。servlet 引擎通知 servlet 关于其状态转换以及数据是否可读的信息。与 Jetty 使用的方法相比,这种方法更加健全和简单。它使用传统的同步 API 对流执行写入和读取操作。通过使用这种方式实现,如果谨慎使用,则不会出现 API 阻塞的情况。

    JAX WS 2.0 和 Apache Axis2 Asynchronous Web Service Client API

    JAX WS 2.0 和 Axis2 为 Web 服务的非阻塞调用提供了 API 支持。当 Web 服务操作完成后,Web 服务引擎将通知提供的侦听器。这为 Web 服务的使用提供了新的机会 — 即使来自 Web 客户机。如果一个 servlet 中发生若干独立的调用,它们将并行执行,因此客户机中的总延迟将更低。

    结束语

    现在,我们已经认识到了异步 Java 组件的必要性,并且,异步应用程序目前正在积极开发之中。两种大型的开源 servlet 引擎(Tomcat 和 Jetty)都至少针对最令开发人员头痛的 servlet 提供了一些支持。尽管 Java 库已开始提供异步接口,这些缺口还缺乏通用的结构,并且,由于线程管理和其他问题,彼此之间很难兼容。因此需要容器能够托管由不同来源提供的各种异步组件。

    目前,用户面对着各种各样的选择,每种方法在不同情形下都各有优缺点。例如,Apache MINA 库为一些流行的网络协议提供了现成的支持,因此,在需要使用这些协议的情况下它将是一个不错的选择。Apache Tomcat 6 可以很好地支持 Comet 交互模式,如果要在这种模式中进行异步交互,那么则可以选择使用 Apache Tomcat 6。如果是从头构建应用程序,并且现有库明显不能提供足够支持,那么可以使用 AsyncObjects 框架,因为它提供了各种各样的可用接口。这种框架还可以用于围绕现有异步组件库创建包装器。

    现在,是时候为 Java 语言创建一个通用的异步编程框架了。然后,还需要花费很多精力将现有异步组件集成到这个框架中,并为现有同步接口创建一个异步版本。每实现一个步骤,企业 Java 应用程序的可伸缩性都会得到改善,并且我们将能够应对比这更艰难的挑战。持续发展的 Internet 以及不断增生的各种网络服务必定将为我们带来更多这样的挑战。


    展开全文
  • 使用 .NET 远程处理访问其他应用程序域中的对象 在 运行于不同进程中的对象之间建立通讯(无论是在同一台计算机上,还是在相距数千公里的计算机上)是常见的开发目标,尤其是在生成大范围分布式应用程序的时 候。...

    使用 .NET 远程处理访问其他应用程序域中的对象



    在 运行于不同进程中的对象之间建立通讯(无论是在同一台计算机上,还是在相距数千公里的计算机上)是常见的开发目标,尤其是在生成大范围分布式应用程序的时 候。传统上,这需要深入了解相关知识:不仅是关于通讯流任一端的对象的知识,而且还有关于低级别协议的主机、应用程序编程接口以及配置工具或文件的知识。 简言之,它是一项需要大量专业知识和经验的复杂任务。

    .NET 框架提供了几种可用来快速而方便地完成此任务的通讯方法,而无论您是否对协议和编码有大量的了解。因此,无论是需要快速开发 Web 应用程序,还是要花费更多时间生成关键的企业范围的应用程序(在此过程中涉及许多计算机或操作系统,并使用多种协议和序列化优化),.NET 框架都会支持您的方案。跨进程通讯仍然是一项复杂的任务,但现在它的许多工作都由 .NET 框架处理。

    选择 .NET 中的通讯选项

    .NET 框架提供了几种与不同应用程序域中的对象进行通讯的方式,每一种方式都具有特定级别的专业性和灵活性。例如,Internet 的发展已经使 XML Web services 成为一种颇具吸引力的通讯方法,这是因为 XML Web services 是建立在使用 XML 的 HTTP 协议和 SOAP 格式化的通用基础结构之上的。这些都是公共标准,并且可以直接与当前的 Web 基础结构结合使用,而无需担心其他的代理或防火墙问题。

    然而,如果只是存在与在 HTTP 连接上使用 SOAP 序列化相关的性能问题,那么并不是所有的应用程序都应使用某种形式的 XML Web services 来建立。下面的子节应有助于您确定要为应用程序采用哪种形式的对象间通讯。

    ASP.NET 还是远程处理?

    ASP.NET 和 .NET 远程处理都是进程间的通讯实现方法。ASP.NET 提供一种由 Internet 信息服务 (IIS) 承载的基础结构,该结构擅长处理基本类型,而且是 Web 应用程序开发人员所熟悉的。.NET 远程处理是一般性的且具有高度可扩展性的进程间通讯系统,可以用来创建在 IIS 中承载的 XML Web services(并具有 ASP.NET 和 IIS 的所有安全性、可伸缩性以及会话和应用程序状态)或任何其他类型的通讯。

    所需的通讯类型和您所熟悉的编程模型是您在两种编程模型之间进行选择的主要依据。请首先选择所需的进程间通讯类型,然后选择能以最容易的方式最佳地实现您的决定的编程模型。以下是选择您可能需要的进程间通讯类型的一些依据(按优先级排序):

    1.     安全需要。如果需要保证调用的安全,则必须使用在 IIS 中承载的基于 HTTP 的应用程序,无论它是 ASP.NET 应用程序还是远程处理应用程序。在 IIS 中,基于 ASP.NET 和 .NET 远程处理的应用程序都具有您可能需要的所有安全功能。使用任何其他传输协议,或是在 IIS 外使用 HttpChannel,都需要您自己完成安全工作。请注意,在使用 HTTP 连接时无需使用 SOAP 编码格式;可以使用二进制编码来提高速度。

    2.     速度。如果每个调用的效率很关键,则应当使用二进制编码,即使您不使用默认的 TcpChannel 也如此。在远程处理中,可以结合使用二进制格式化程序和 HttpChannel;在 ASP.NET 中,可以使用 POST 通讯。单是使用二进制格式化就可以显著地提高远程处理中的调用性能,即使您使用的是 HttpChannel 也如此。如果没有任何安全问题(例如,如果正在生成完全在防火墙内部运行的小应用程序),则使用二进制格式化的默认 TcpChannel 就能够达到最佳性能。

    3.     交互操作。如果必须在不同的操作系统之间进行交互操作,则无论使用 .NET 远程处理还是使用 ASP.NET,都应当使用 SOAP 格式化协议。

    4.     可缩放性。无论使用 .NET 远程处理还是使用 ASP.NET,将应用程序承载在 IIS 内部都会获得所需的可缩放性。

    5.     随机变更。有几种问题可能会与您有关,例如:

    ·         编程模型易于使用。

    ·         易于实现扩展性。

    ·         自定义需要。

    ·         使用 C++ 的托管扩展。

    ·         对于远程对象激活、生存期或用户权限的特殊需要。

    ·         是否需要完全的类型保真。

    .NET 远程处理系统为所有这些要求提供了解决方案,而 ASP.NET 系统只对其中某些要求提供支持。

    以下是使用 ASP.NET、System.Net 命名空间和 .NET 远程处理生成的 XML Web services 之间存在的一些差异的简短摘要。

    XML Web services

    如果您是生成 ASP 应用程序并希望使用 Web 应用程序模型和 ASP.NET HTTP 运行库的功能(包括 Microsoft Visual Studio .NET 中的强大支持),则使用 ASP.NET 生成的 Web 服务可以满足您的要求。通过 XML Web services 基础结构,可以使用最适合于基于 Web 的应用程序的协议、格式和数据类型来方便地创建自己的组件供其他应用程序使用,或是使用他人创建的组件。它不支持 .NET 计算机之间的完全类型保真,并且仅可以传递某些类型的参数。

    System.Net 命名空间

    可以使用 System.Net 命名空间中的类从无到有生成整个通讯结构。还可以使用 System.Net 类实现您自己的可以插入到远程处理结构中的通讯协议和序列化格式。

    .NET 远程处理

    .NET 远程处理提供用于实现任意数量的全面通讯方案(包括但不仅限于 XML Web services)的工具。使用 .NET 远程处理可以:

    • 在任意类型的应用程序域中发布或使用服务,无论该域是控制台应用程序、Windows 窗体、Internet 信息服务 (IIS)、XML Web services 还是 Windows 服务。
    • 在二进制格式的通讯中保持完整的托管代码类型系统保真度。

    注意 XML Web services 使用 SOAP 格式化,这种格式化不会保持所有的类型详细信息。

    • 通过引用传递对象并返回到特定应用程序域中的特定对象。
    • 直接控制激活特性和对象生存期。
    • 实现和使用第三方信道或协议来扩展通讯以满足特定要求。
    • 直接参与通讯进程以创建所需的功能。

    .NET 远程处理概述

    可以使用 .NET 远程处理来使不同的应用程序能够互相通讯,而无论这些应用程序是驻留在同一台计算机上、位于同一局域网中的不同计算机上还是位于相隔万里的差异巨大的网络中(即使计算机运行不同的操作系统)。

    .NET 框架提供多种服务(如激活和生存期控制),以及负责在本地和远程应用程序之间传输消息的通讯信道。格式化程序用于在沿信道发送消息之前对消息进行编码和解 码。应用程序可以在性能很关键时使用二进制编码,或是在与其他远程处理系统的互操作性至关重要时使用 XML 编码。远程处理的设计考虑到了安全性,因此您可以使用一些挂钩来访问调用消息和序列化流,以便在通过信道传输它们以前保证它们的安全。

    远程处理基础结构是进程间通讯的抽象方法。系统的大部分在运行时无需关心。例如,可以通过值传递或可以复制的对象是自动在不同应用程序域中的或不同计算机上的应用程序之间传递的。只需将自定义类标记为可序列化便可使它工作。

    然而,远程系统的真正优点在于它具有使位于不同应用程序域或者进程(它们使用不同的传输协议、序列化格式、对象生存期方案和对象创建模式)中的对象互相通讯的能力。此外,如果出于任何原因,需要干预通讯进程的几乎任何阶段,远程处理使这种干预变为可能。

    无论您已经实现了一些分布式应用程序,还是只对将组件移动到其他计算机上以增加程序的可伸缩性感兴趣,您都可以非常容易地将远程处理系统理解为一般性的进程间通讯系统,它具有一些能够轻松处理大多数方案的默认实现。下面的讨论从使用远程处理进行进程间通讯的基础知识开始。

    副本与引用

    进 程间通讯需要一个向其进程外的调用方提供功能的服务器对象、一个在服务器对象上进行调用的客户端以及一个将调用从一端运送到另一端的传输机制。服务器方法 的地址是逻辑地址,并且在一个进程中正常工作,但不能在其他客户端进程中正常工作。若要解决此问题,客户端调用服务器对象的一种方法是:创建对象的完整副 本并将该副本移动到客户端进程(在该进程中可以直接调用副本的方法)。

    然 而,许多对象无法或不应复制和移动到某个其他进程来执行。具有许多方法的非常大的对象不适合复制到或通过值传递到其他进程。通常,客户端仅需要由服务器对 象上的一个或几个方法返回的信息。复制整个服务器对象(包括可能是与客户端需求无关的大量内部信息或可执行结构)将是对带宽以及客户端内存和处理时间的浪 费。另外,许多对象公开公共功能,但是需要用于内部执行的私有数据。复制这些对象会使恶意客户端能够查看内部数据,从而产生安全问题隐患。最后,某些对象 使用的数据干脆就无法以任何可理解的方式复制。例如,FileInfo 对象包含一个对操作系统文件的引用,此文件在服务器进程的内存中具有唯一的地址。可以复制这个地址,但它在另一进程中将永远不会具有任何意义。

    在 这些情况下,服务器进程应当向客户端进程传递一个对服务器对象的引用,而不是传递该对象的副本。客户端可以使用此引用来调用服务器对象。这些调用不在客户 端进程中执行。相反,远程处理系统收集关于调用的所有信息并将其发送到服务器进程,在该进程中,将解释这些信息并查找正确的服务器对象,然后代表客户端对 象向该服务器对象发出调用。然后,调用的结果被发送回客户端进程以返回到客户端。带宽仅用于关键信息:调用、调用参数以及任何返回值或异常。

    简化的远程处理结构

    使用对象引用在服务器对象和客户端之间进行通讯是远程处理的核心。然而,远程处理结构向程序员提供了更简单的过程。如果正确配置了客户端,只需要使用 new(或 托管编程语言中的实例创建函数)创建远程对象的新实例。客户端接收对服务器对象的引用,然后就可以像该对象位于您的进程中而不是运行在另外一台计算机上一 样调用其方法。远程处理系统使用代理对象来产生服务器对象位于客户端进程中的效果。代理是将它们自身显示为某个其他对象的临时代理对象。当客户端创建远程 类型的实例时,远程处理基础结构创建对于客户端来说看起来与远程类型完全相同的代理对象。客户端调用此代理上的方法,而远程处理系统则接收调用,将其路由 到服务器进程,调用服务器对象,并将返回值返回到客户端代理,而客户端代理将结果返回到客户端。

    远程调用必须以某种方式在客户端和服务器进程之间传送。如果要自己生成远程处理系统,可以从学习网络编程、各种各样的协议和序列化格式规范开始。在 .NET 远程处理系统中,打开网络连接和使用特定协议将字节发送到接收应用程序所需的基础技术的组合被表示为传输信道。

    信道是一个承载数据流,根据特定网络协议创建包并将该包发送到另一台计算机的类型。某些信道只能接收信息,另外一些只能发送信息,还有一些(例如默认的 TcpChannel 和 HttpChannel 类)可以在两个方向上使用。

    虽然服务器进程了解有关每个唯一类型的一切信息,但是客户端仅知道它需要对其他应用程序域(可能在其他计算机上)中的某个对象的引用。从服务器应用程序域外的世界来说,该对象是通过 URL 查找的。向外部世界表示唯一类型的 URL 是激活 URL,它们确保远程调用是向正确的类型发出的。

    完整的远程处理系统设计

    假定您的应用程序在一台计算机上运行,而您想使用由存储在另一台计算机上的类型公开的功能。下图显示常规的远程处理过程。

    远程处理过程

    如 果关系两端都是正确配置的,则客户端仅创建一个服务器类的新实例。远程处理系统创建一个表示该类的代理对象,并向客户端对象返回一个对该代理的引用。当客 户端调用方法时,远程处理基础结构接应该调用,检查类型信息,并通过信道将该调用发送到服务器进程。侦听信道获得该请求并将其转发给服务器远程处理系统, 服务器远程处理系统查找(或在必要时创建)并调用被请求的对象。然后,此过程将反向进行,服务器远程处理系统将响应捆绑成消息并由服务器信道发送到客户端 信道。最后,客户端远程处理系统通过代理将调用的结果返回给客户端对象。

    为使此过程工作只需要非常少的实际代码,但应当认真考虑关系的设计和配置。即使代码完全正确,仍然可能因为 URL 或端口号不正确而失败。

    虽然远程处理进程的这种高级别概述相当简单,但低级别的详细信息可能是非常复杂的。其他主题中提供了对远程处理的主要元素的更为深入的讨论,如下面所列出的主题所示。

    可远程处理的和不可远程处理的对象

    请一定记住,在某个应用程序域中创建并因此特定于它的对象可以在此域中直接调用,但在此对象可以在它的域以外使用之前必须进行一些特殊处理。并非每种类型的对象都可以跨域的边界高效地发布或使用;因此,必须根据应用程序的需要决定要发布哪种类型的对象。

    根据分布式应用程序的用途,有两种简单的对象类别:可远程处理的对象和不可远程处理的对象。不可远程处理的对象不向系统提供复制它们或在其他应用程序中表示它们的任何方法。因此,这些对象仅可以从它们的原始应用程序域中访问。可远程处理的对象既可以使用代理在其应用程序域或上下文外部访问,也可以复制它们并且可以将这些副本传递到它们的应用程序域或上下文外;换句话说,某些可远程处理的对象通过引用传递,而另一些通过值传递。

    不可远程处理的对象

    某些对象不能离开它们的应用程序域;它们永远不会被封送处理,原因是它们不声明序列化方法。这些不可远程处理的对象专用于创建它们的同一应用程序域内部,并且总是从该同一应用程序域中直接访问。.NET 框架类库中的大多数基类是不可远程处理的对象。

    可远程处理的对象

    可远程处理的对象是能在大范围的分布环境中正常运行的对象。有两种主要的可远程处理的对象:

    • 按值封送对象,它们被复制并传出应用程序域。
    • 按引用封送对象,将为其创建代理,而该代理由客户端用于远程访问对象。

    按值封送对象

    按值封送 (MBV) 对象声明它们的序列化规则(通过实现 ISerializable 来实现其自身的序列化,或者通过用 SerializableAttribute 修饰,该属性通知系统自动序列化该对象),但是不扩展 MarshalByRefObject。远程处理系统创建这些对象的完整副本并将副本传递到进行调用的应用程序域。一旦副本到达调用方的应用程序域内,对 它的调用就是对该副本的直接调用。而且,当 MBV 对象作为参数传递时,也是通过值传递的。除声明 SerializableAttribute 或实现 ISerializable 之外,无需做其他任何事情就可以将类的实例跨应用程序或上下文边界通过值传递。

    当由于性能或处理原因,将对象的完整状态和任何可执行功能移动到目标应用程序域有意义时,应当使用 MBV 对象。在许多方案中,这减少了跨网络、进程和应用程序域边界的冗长而耗费资源的往返行程。MBV 对象还可以从对象的原始应用程序域内直接使用。在这种情况下,由于不进行任何封送处理,因此不创建任何副本而且访问非常高效。

    另一方面,如果发布的对象非常大,那么在繁忙的网络上传递整个副本对于应用程序来说可能不是最佳的选择。此外,永远不会将对复制对象的状态所做的更改传回到起始应用程序域中的原始对象。在抽象级别上,这种方案类似于客户端浏览器所请求的静态 HTML 页的方案。服务器复制文件,将其写入到流中,发送出去,然后忘掉它。所有后续的请求都只是对其他副本的其他请求。

    远程处理系统广泛使用可序列化的对象。对其他应用程序域中的对象的引用(由 ObjRef 类在远程处理系统中表示)本身是可序列化的;必须能够将它精确地复制并将副本发送给请求。同样,对于实现 IMessage 的消息对象也是如此,这是因为它们是调用信息和所有其他所需对象引用的一般容器。另外,仅传输数据的对象通常是 MBV 对象。例如,DataSet 扩展实现 ISerializable 的 MarshalByValueComponent。

    按引用封送的对象

    按引用封送 (MBR) 的对象是至少扩展 System.MarshalByRefObject 的可远程处理的对象。根据已声明的激活类型,当客户端在自己的应用程序域中创建 MBR 对象的实例时,.NET 远程处理基础结构在调用方的应用程序域中创建表示该 MBR 对象的代理对象,并向调用方返回对此代理的引用。然后客户端将在该代理上进行调用;远程处理封送这些调用,    将它们发送回起始应用程序域,并在实际对象上调用该调用。

    注意 如果客户端位于与 MBR 对象相同的应用程序域中,基础结构将向客户端返回对该 MBR 对象的直接引用,从而避免封送处理的系统开销。

    如果 MarshalByRefObject 作为参数传入,当调用到达时,它变成另一个应用程序域中的代理。MBR 返回值,并且 out 参数以相同的方式工作。

    当对象的状态和任何可执行的功能应处在创建它的应用程序域中时,应当使用 MBR 对象。例如,具有内部字段且该内部字段是操作系统句柄的对象应扩展 MarshalByRefObject,这是因为操作系统句柄在其他进程中或其他计算机上的其他应用程序域中是无意义的。有时对象可能大得难以想象;对于功能强大的服务器还行,但通过网络发送到 33.6 kbps 的调制解调器就不行了。

    上下文绑定对象

    上下文绑定对象是从 System.ContextBoundObject(它本身从 System.MarshalByRefObject 继承)继承的 MBR 对象。可以将上下文当作应用程序域的子部分,它在执行期间为驻留在其中的对象提供某种资源充足的环境(例如保证对象不会被多个线程同时访问)。每个应用程 序域都具有默认的上下文,大多数托管代码使用应用程序域的默认上下文创建对象并直接从同一应用程序域内部调用成员,而不会产生与上下文有关的问题。所有从 ContextBoundObject 继承的类型都作为代理向其他上下文公开,无论它们是否在同一应用程序域中。

    例如,假定有一个某类型上的方法,该类型是事务的一部分因而受到特定于在其中创建它的上下文的规则的约束。应当使该类型从 ContextBoundObject 继承,这样就可以从对象本身的上下文访问该对象,而且系统可以强制实施有关与对象及其方法关联的事务的规则。如果从同一应用程序域内部的另一上下文中调用 ContextBoundObject,则将为调用方创建代理,但是上下文间的通讯不通过信道系统,从而在此情况下提高了调用效率。

    考 虑要远程处理哪种类别的类型时,请决定需要通过哪些边界。特定于某个特定上下文的对象只能从该上下文中直接访问。对于特定于某个特定应用程序域的对象也是 如此。若要远程处理这两者中的任一种对象,在从服务器对象所特定于的边界内调用该服务器对象之前,远程处理系统必须成功通过上下文边界、应用程序边界或是 成功通过这两种边界。由于通过每一边界都要花费处理时间,因而为了决定服务器应当是何种类型的可远程处理对象,就需要确定对象必须通过哪些边界。如果无需 上下文检查就调用对象,则不应让远程类型扩展 ContextBoundObjectMarshalByRefObject 将执行得更好)。如果确实需要上下文检查,则应当扩展 ContextBoundObject,但是需要明白:在对象上进行调用之前,必须通过附加的边界。

    对象激活和生存期

    多 数情况下,无需注意创建对象的确切时间;只需在对象上调用方法时让它作出响应。然而,当生成远程对象时,必须知道新对象是何时以及如何创建和初始化的,即 它是如何激活的。在可以使对象对于客户端可用之前,远程处理系统必须总是知道需要哪种类型的激活;因此,理解您的选择是很重要的。

    激活

    对于按引用封送 (MBR) 的对象,有两种激活类型:服务器激活和客户端激活。

    服务器激活的对象:

    • 只在需要它们时由服务器创建——不是在通过调用 new 或 Activator.GetObject() 创建客户端代理时创建,而是在客户端调用该代理上的第一个方法时创建。
    • 可以声明为 SingletonSingleCall 对象。Singleton 对象是这样的对象:无论该对象有多少个客户端,总是只有一个实例,且该对象具有默认的生存期。将对象声明为 SingleCall 对象时,系统为每个客户端方法调用创建一个新对象。客户端可以使用生存期租约系统参与这些实例的生存期。

    客户端激活的对象:

    • 客户端调用 new 或 Activator.CreateInstance() 时在服务器上创建。使用生存期租约系统,客户端本身可以参与这些实例的生存期。

    生存期租约

    按引用封送的对象 (MBR)无论是服务器激活的 Singleton 对象还是客户端激活的对象,都不会永远驻留在内存中。相反,除非该类型重写 MarshalByRefObject.InitializeLifetimeService() 以控制自己的生存期策略,否则每个 MBR 的生存期都是由租约、租约管理器和一些主办方的组合控制的。(在这种情况下,MBR 对象的生存期是对象在内存中保持活动状态的总时间。)租约本身是 .NET 远程处理系统开始删除某个特定对象并回收内存的进程之前,该特定对象在内存中保持活动状态的时间。服务器应用程序域的租约管理器是确定何时标记远程对象以 供进行垃圾回收的对象。主办方是可以通过将其本身注册到租约管理器来为特定对象请求新租约的对象。


    只要 MBR 对象被在应用程序域外部进行远程处理,就为该对象创建生存期租约。每个应用程序域都包含一个负责管理其域中的租约的租约管理器。租约管理器定期检查所有租 约以确定过期的租约时间。如果租约已过期,租约管理器将为该对象遍历它的主办方列表并请求它们中是否有谁要续订租约。如果没有任何主办方续订该租约,租约 管理器将移除该租约,该对象被删除,其内存被垃圾回收机制回收。因此,如果对象被主办方多次续订租约或被客户端持续调用,其生存期可以比其生存期租约长得 多。


    由 于远程对象的生存独立于其客户端的生存,因此简单或轻量对象的租约(举个例子来说)可以很长并被一些客户端使用,前提是该租约被管理器或客户端定期续订。 这种方法高效地使用租约,原因是分布式垃圾回收所需的网络通信量很小。另一方面,对于使用时间不太长和使用稀有资源的远程对象来说,其租约的生存期可以很 短,以至于客户端用较短的时间频繁地续订其生存期。当所有客户端都完成对远程对象的处理时,.NET 远程处理系统将很快删除该对象。这种策略增加了网络通信量以更有效地使用服务器资源。


    使用租约管理远程对象的生存期是引用计数的一种替换方法,引用计数在不可靠的网络连接上可能是复杂而低效的。虽然可以将租约配置为延长远程对象的生存期以超过所需的精确长度,但是由于减少了专用于引用计数和进行 ping 操作的客户端的网络通信量,因而使租约在为特定方案正确配置时成为一种很有吸引力的解决方案。

    租约有五个主要属性:

    • InitialLeaseTime。它指定对象在租约管理器开始删除对象的过程之前将保留在内存中的初始时间长度。在配置文件中,它是 <lifetime> 配置元素的 leaseTime 属性。默认为 5 分钟。租约时间为零则将租约设置为具有无限长的生存期。
    • CurrentLeaseTime。它指定在租约过期之前所剩下的时间长度。当租约被续订时,其 CurrentLeaseTime 被设置为 CurrentLeaseTime 的最大值或 RenewOnCallTime
    • RenewOnCallTime。它指定对对象进行各个远程调用后将 CurrentLeaseTime 设置为的最大时间长度。默认为 2 分钟。
    • SponsorshipTimeout。它指定租约管理器被通知租约已过期时租约管理器等待主办方响应的时间。如果主办方未在指定的时间内响应,则移除该主办方并调用另一主办方。如果没有其他主办方,则租约过期,且垃圾回收器将处置该远程对象。如果值为“0”(TimeSpan.Zero),则租约将不注册主办方。默认为 2 分钟。
    • LeaseManagerPollTime。它是租约管理器在检查过期的租约之后休眠的时间量。默认的 LeaseManagerPollTime 为 10 秒钟。

    当 MBR 对象在其他应用程序域中被激活时创建租约。此时,当 ILease.CurrentState 属性为 LeaseState.Initial 时,可以设置租约的属性。一旦设置,就不能直接更改它们。只可以从 ILease.Renew 调用中或者当租约管理器在主办方上调用 ISponsor.Renewal 且该主办方用一个 TimeSpan 响应时更改 CurrentLeaseTimeMarshalByRefObject 具有生存期租约的默认实现,且除非该租约在创建时被修改,否则租约属性将始终相同。

    修改租约属性

    可以通过两种方式修改生存期租约属性。可以声明自定义生存期租约属性,方法是重写 MBR 对象中的 MarshalByRefObject.InitializeLifetimeService 以自己设置租约的属性,或者将其重写为返回 null(Visual Basic 中为 Nothing);后一种方法通知 .NET 远程处理系统该类型的实例将具有无限长的生存期。您或者管理员还可以在特定的应用程序或计算机配置文件中的 <lifetime> 元素内指定该应用程序中所有对象的生存期属性。

    创建后,可以用三种方式续订租约:

    • 客户端直接调用 ILease.Renew 方法。
    • 如果设置了 ILease.RenewOnCallTime 属性,则每个对远程对象的调用都将为租约续订指定的时间。
    • 租约调用 ISponsor.Renewal 方法以请求续订租约,而主办方则以一个 TimeSpan 响应。

    租约管理器

    租约管理器的任务之一是定期检查租约的时间是否过期。当租约的时间已过期时,该租约将接到通知并尝试通过调用其主办方来续订自己。

    租约管理器还维护一个租约正等待其答复的主办方的列表。如果主办方未在 SponsorshipTimeOut 时间长度所指定的间隔内响应,则将其从主办方列表中移除。

    租约被允许过期时,它不再接受任何其他租约消息或主办方返回的内容。租约的引用被从租约列表中移除,而且 .NET 远程处理系统将把该对象引用从其内部表中移除。然后,垃圾回收系统将移除该租约和对象。

    信道

    信道是跨远程处理边界(无论是在应用程序域、进程还是计算机之间)在应用程序之间传输消息的对象。信道可以在终结点上侦听入站消息,向另一个终结点发送出站消息,或者两者都可以。这使您能够插入各种各样的协议,即使信道的另一端上没有公共语言运行库。

    信道必须实现 IChannel 接口,该接口提供诸如 ChannelName ChannelPriority 这样的信息性属性。被设计为在特定端口上侦听特定协议的信道实现 IChannelReceiver,而被设计为发送信息的信道实现 IChannelSender。TcpChannelHttpChannel 对于这两种接口都加以实现,因此它们可用于发送或接收信息。

    您可以以两种方式将信道注册到远程处理基础结构:

    • 如果您正在发布可远程处理的对象,则在注册服务器对象之前调用 ChannelServices.RegisterChannel()。
    • 如果您正在使用可远程处理的对象的功能,则在创建服务器对象的实例之前调用 ChannelServices.RegisterChannel()

    信道还可以从远程处理配置文件加载。

    在客户端,消息经过客户端上下文链后,被传递到客户端信道接收链。第一个信道接收器通常是格式化程序接收器;它将消息序列化为流(然后该流将被沿着信道接收链传递到客户端传输接收器)。然后客户端传输接收器将此流写出到网络。

    在服务器端,服务器传输接收器从网络读取请求,并将该请求流传递到服务器信道接收链。此链末端的服务器格式化程序接收器将该请求反序列化为消息。然后将此消息传递到远程处理基础结构。

    信道选择

    当 客户端在远程对象上调用方法时,参数以及和调用有关的其他详细信息将被通过信道传输到远程对象。调用的任何结果均以相同的方式返回。客户端可以选择在服务 器上注册的任何信道与远程对象通讯,这样就使开发人员能够自由地选择最符合他们的需要的信道。也可以自定义任何现有信道或者生成使用不同通讯协议的新信 道。信道选择需要遵循以下规则:

    • 在可以调用远程对象之前,服务器上的远程处理系统必须注册了至少一个信道。必须在注册对象之前注册信道。如果客户端上没有注册信道,则远程处理系统将选择或创建一个以发送出站调用。

    注意 如果客户端需要回调函数,则必须在客户端上注册一个侦听信道,并且必须将服务器配置为使用一个兼容的信道。

    • 信道是针对每个应用程序域注册的。单个进程可以包含多个应用程序域。当进程结束时,它注册的所有信道都自动销毁。
    • 信道名称在应用程序域中必须是唯一的。例如,由于默认信道具有名称,因此,若要在一个应用程序域中注册两个 HttpChannel 对象,就必须在注册它们之前更改信道的名称。下面的 C# 代码示例对此进行了演示。

    ·                IDictionary prop = new Hashtable();·                prop["name"] = "http1";·                prop["port"] = "9001";ChannelServices.RegisterChannel(new HttpChannel(prop, null, null));

    • 不能多次注册在特定端口上侦听的信道。虽然信道是针对每个应用程序域注册的,但是同一台计算机上的不同应用程序域不能注册在同一个端口上侦听的同一个信道。
    • 如果您不能确定是否有可用的端口,那么在配置信道的端口时请使用“0”(零),远程处理系统将为您选择一个可用的端口。
    • 客户端可以使用任何已注册的信道与远程对象进行通讯。当客户端尝试连接到远程对象时,远程处理系统将确保该远程对象被连接到正确的信道。客户端负责在尝试与远程对象通讯之前调用 ChannelServices.RegisterChannel(),如果客户端需要回调函数,那么它必须注册一个信道和一个端口。

    当客户端在代理上调用方法时,调用将被截获并捆绑为消息,然后被传递到 RealProxy 类的实例。RealProxy 类将消息转发到消息接收器以进行处理。消息接收器与由远程对象注册的信道建立连接,并通过该信道将消息调度到起始应用程序域中。在那里,该消息将被取消封送,并且将对远程对象本身进行调用。

    当远程处理在客户端的域中初始化远程对象的代理时,将通过调用选定信道上的 IChannelSender.CreateMessageSink 从客户端配置的信道中检索一个能够与该远程对象通讯的消息接收器。

    远程处理系统的一个易引起混淆的方面是远程对象和信道之间的关系。例如,如果对象仅在调用到达时才激活,SingleCall 远程对象将如何侦听要连接的客户端呢?

    这 是可能的,部分原因在于远程对象共享信道;远程对象不拥有信道。承载远程对象的服务器应用程序必须注册它们所需要的信道以及它们要使用远程处理系统公开的 对象。信道注册之后,将自动在指定的端口开始侦听客户端请求。对于同步调用的情况,将在消息调用期间维护来自客户端的连接。由于每个客户端连接都是在它自 己的线程中处理的,因此单个信道可以同时服务于多个客户端。

    远程处理示例:异步远程处理

    下面的示例应用程序说明远程处理方案中的异步编程。该示例首先创建远程对象的同步委托并且调用它来阐释等待该返回的线程。然后,它使用异步委托和 ManualResetEvent 来调用远程对象方法并等待响应。

    该应用程序可在单台计算机上运行或通过网络运行。如果要在网络上运行该应用程序,必须用远程计算机的名称替换客户端配置中的“localhost”。

    若要生成此示例,请保存下面的文件并在命令提示处键入下面的内容:

    csc /t:library /out:ServiceClass.dll ServiceClass.cs

    csc /r:ServiceClass.dll Server.cs

    csc /r:ServiceClass.dll RemoteAsync.cs

    然后,打开两个指向同一目录的命令提示符。在其中一个键入

       Server

    在另一个键入:
       RemoteAsync
    RemoteAsync.cs

    using System;

    using System.Reflection;

    using System.Runtime.Remoting;

    using System.Runtime.Remoting.Messaging;

    using System.Runtime.Remoting.Channels;

    using System.Threading;

     

    public class RemotingDelegates : MarshalByRefObject{

     

        public static ManualResetEvent e;

     

        public delegate string RemoteSyncDelegate();

        public delegate string RemoteAsyncDelegate();

     

        // This is the call that the AsyncCallBack delegate will reference

        [OneWayAttribute]

       public void OurRemoteAsyncCallBack(IAsyncResult ar){

          RemoteAsyncDelegate del = (RemoteAsyncDelegate)((AsyncResult)ar).AsyncDelegate;

          Console.WriteLine("/r/n**SUCCESS**: Result of the remote AsyncCallBack: "  + del.EndInvoke(ar) );

          

            // Signal the thread.

            e.Set();

            return;

       }

     

        public static void Main(string[] Args){

     

            // IMPORTANT: Remember that .NET Remoting does not remote

            // static members. This class must be an instance before

            // the callback from the async invocation can reach this client

            RemotingDelegates HandlerInstance = new RemotingDelegates();

            HandlerInstance.Run();        

     

        }

     

        public void Run(){

            // enable this and the e.WaitOne call at the bottom if you

            // are going to make more than one async call.

            e = new ManualResetEvent(false);

     

            Console.WriteLine("Remote synchronous and asynchronous delegates.");

            Console.WriteLine(new String('-',80));

            Console.WriteLine();

     

            // this is the only thing you must do in a remoting scenario

            // for either synchronous or asynchronous programming -- configuration.

            RemotingConfiguration.Configure("SyncAsync.exe.config");

     

            // otherwise, from here on, the steps are identical to single-AppDomain

            // programming.

            ServiceClass obj = new ServiceClass();

            

            // This delegate is a remote synchronous delegate

            RemoteSyncDelegate Remotesyncdel = new RemoteSyncDelegate(obj.VoidCall);

            

            // When invoked, program execution waits until method returns.

            // This delegate could be passed to another application domain

            // to be used as a callback to the obj.VoidCall method.

            Console.WriteLine(Remotesyncdel());

     

            // This delegate is an asynchronous delegate. Two delegates must be created.

            // The first is the system-defined AsyncCallback delegate, which references

            // the method the remote type calls back when the remote method is done

     

            AsyncCallback RemoteCallback = new AsyncCallback(this.OurRemoteAsyncCallBack);

     

            // Now create the delegate to the remote method you want to use asynchronously

            RemoteAsyncDelegate RemoteDel = new RemoteAsyncDelegate(obj.TimeConsumingRemoteCall);

            

            // Now we start the method call. Note that execution on this thread continues

            // immediately without waiting for the return of the method call.

            IAsyncResult RemAr = RemoteDel.BeginInvoke(RemoteCallback, null);

     

            // If at some point you want to stop execution on this thread to wait for

            // the return from this specific call, retrieve the IAsyncResult returned from

            // the BeginIvoke call, obtain its WaitHandle, and pause the thread until,

            // such as the next line:

            // RemAr.AsyncWaitHandle.WaitOne();

     

            // To wait in general, if, for example, many asynchronous calls have been

            // made and you want notification of any of them, or, like this example,

            // because the application domain can be recycled before the callback

            // can print the result to the console.

            //e.WaitOne();

     

       // This simulates some other work going on in this Thread while the async

       // call has not returned. If you're not doing work on this thread,

       int count = 0;

       while(!RemAr.IsCompleted){

          Console.Write("/rNot completed: " + (++count).ToString());

          // make sure callback thread can invoke callback

          Thread.Sleep(1);

       }

        }

    }

    Server.cs

    using System;

    using System.Runtime.Remoting;

     

    public class Server{

     

       public static void Main(){

          RemotingConfiguration.Configure("server.exe.config");

          Console.WriteLine("Waiting...");

          Console.ReadLine();

       }

    }

    ServiceClass.cs

    using System;

    using System.Runtime.Remoting;

     

    public class ServiceClass : MarshalByRefObject{

     

       public ServiceClass() {

          Console.WriteLine("ServiceClass created.");

       }

     

       public string VoidCall(){

          Console.WriteLine("VoidCall called.");

          return "You are calling the void call on the ServiceClass.";

       }

     

       public int GetServiceCode(){

          return this.GetHashCode();

       }

     

       public string TimeConsumingRemoteCall(){

          Console.WriteLine("TimeConsumingRemoteCall called.");

       

          for(int i = 0; i < 20000; i++){

             Console.Write("Counting: " + i.ToString());

             Console.Write("/r");

          }

          return "This is a time-consuming call.";

       }

    }

    Server.exe.config

    <configuration>

       <system.runtime.remoting>

          <application>

             <service>

                <wellknown

                   type="ServiceClass, ServiceClass"

                   mode="Singleton"

                   objectUri="ServiceClass.rem"

                />

             </service>

             <channels>

                <channel

                   ref="http"

                   port="8080"

                />

             </channels>

          </application>

       </system.runtime.remoting>

    </configuration>

    SyncAsync.exe.config

    <configuration>

       <system.runtime.remoting>

          <application>

             <client>

                <wellknown

                   type="ServiceClass, ServiceClass"

                   url="http://localhost:8080/ServiceClass.rem"

                />

             </client>

             <channels>

                <channel

                   ref="http"

                   port="0"

                />

             </channels>

          </application>

       </system.runtime.remoting>

    </configuration>

    总结

        上面是VS.NET中.NET远程通信的一些概念和示例,给大家参考一下。有任何建议请MAIL我 paulni@citiz.net。

    展开全文
  • Service Broker 可以创建异步的、数据驱动的消息应用程序,它允许一个数据库发送消息到其他数据库,而不需要等待响应,即使远程数据库不能立即处理这些消息,发送数据库也可以继续其他操作。通过使用T-SQL对象和命令...

    Service Broker 可以创建异步的、数据驱动的消息应用程序,它允许一个数据库发送消息到其他数据库,而不需要等待响应,即使远程数据库不能立即处理这些消息,发送数据库也可以继续其他操作。通过使用T-SQL对象和命令,就可以完成管理Service Broker。

     

    Service Broker 为SQL Server提供消息队列,这样就可以从数据库中发送异步事务性消息到队列,在队列中这些消息将会被其他服务获取和处理,该服务可能运行在其他数据库、或服务器上。另外,对于异步程序,发送一条消息,并且应用程序不需要等待原始消息已被接收、或处理的确认信息,就可以处理其他相关的任务。一旦完成特定任务,两个Service Broker 服务之间的会话就可以显式的结束。

     

     

    Service Broker包含了一些开箱即用的特性,它处理当试图创建自己的异步消息系统时,可能经常遇到的复杂问题。

    比如:

    首先,Service Broker 消息要保证以适当的顺序,或者以他们发送的原始顺序进行接收,而且这些消息也只能被接收一次(调度程序保证不会重复的读取),并且可以作为同一个会话的一部分发送,与任务的同一个实例相关;

     

    其次,Service Broker 保证了消息的发送,当尝试发送第一个消息时,目标数据库(消息的接收者)不可用,消息将加入到发送方的数据库的队列中,当目标数据库变为可用时,发送者将会尝试发送这个消息;

     

    再次,由于Service Broker 是内建在SQL Server数据库中的,可以与数据库的其余部分仪器备份,所以这些消息在数据库发生故障的情况下也是可以恢复的;

     

    最后使用Service Broker 的事件通知功能,可以跟踪数据库和SQL Server实例的事件,这个与SQL Trace相似,但事件通知是异步的,并且对SQL Server实例整体的性能的影响最小,而SQL Trace对性能的影响较大。

    use master
    go
    
    if not exists(select 1 from sys.databases where name = 'BOOKSTORE')
    	CREATE DATABASE bookstore
    go
    
    
    if not exists(select 1 from sys.databases where name = 'bookDistribution')
    	CREATE DATABASE bookDistribution
    go
    
    
    --1.启动Service broker
    alter database bookstore set enable_broker   --disable_broker可以禁用
    
    alter database bookstore set trustworthy on  --指明SQL Server实例是否信任该数据库以及其中的内容
    
    alter database bookDistribution set enable_broker
    
    alter database bookDistribution set trustworthy on
    
    
    --2.创建数据库主密钥
    use bookstore
    go
    
    create master key
    encryption by password = '123abc'
    
    use bookDistribution
    go
    
    create master key
    encryption by password = 'abc123'
    
    
    
    --3.创建消息类型,定义了从Service Broker端点发送的消息中包含的数据类型
    use bookstore
    go
    
    create message type [//SackConsulting/SendBookOrder]
    validation = WELL_FORMED_XML
    go
    
    create message type [//SackConsulting/BookOrderReceived]
    validation = WELL_FORMED_XML
    
    
    
    use bookDistribution
    go
    
    
    create message type [//SackConsulting/SendBookOrder]
    validation = well_formed_xml
    go
    
    create message type [//SackConsulting/BookOrderReceived]
    validation = well_formed_xml
    go
    
    
    --4.创建约定,定义了在任务级别可以发送或者接收的消息类型
    use bookstore
    go
    
    create contract [//SackConsulting/BookOrderContract]
    (
    	[//SackConsulting/SendBookOrder] sent by initiator,  --可以由会话的发起方发送的消息类型
    	[//SackConsulting/BookOrderReceived] sent by target  --可以由会话的目标方发送的消息类型
    )
    
    
    use bookDistribution
    go
    
    create contract [//SackConsulting/BookOrderContract]
    (
    	[//SackConsulting/SendBookOrder] sent by initiator,
    	[//SackConsulting/BookOrderReceived] sent by target 
    )
    go
    
    
    --5.创建队列,队列用来保存数据
    --通过select语句来查询队列,或者用receive命令从队列检索一条、多条消息
    --检索程序可以是外部的.net程序,不过通过存储过程来实现更方便
    use bookstore
    go
    
    create queue bookStoreQueue
    with status = on 
    go
    
    
    --在创建queue时可以把自动处理消息的程序,绑定到队列的激活选项,
    --此处通过手动控制队列中的信息交换
    use bookDistribution
    go
    
    create queue bookStoreDistributionQueue
    with status = on 
    go
    
    
    
    --6.创建服务这样就可以把消息队列绑定到一个或者多个约定上
    --服务使用队列和约定来定义一个或一组任务
    --服务是消息的发起方和接收方强制约定的规则,并将消息路由到正确的队列
    use bookstore
    go
    
    create service [//SackConsulting/BookOrderService]
    on queue dbo.bookStoreQueue ([//SackConsulting/BookOrderContract])
    
    
    use bookDistribution
    go
    
    create service [//SackConsulting/BookStoreDistributionService]
    on queue dbo.bookStoreDistributionQueue ([//SackConsulting/BookOrderContract])
    
    
    
    
    --7.1开始会话,发送消息
    use bookstore
    go
    
    declare @conversation_handler uniqueidentifier;
    declare @order_msg xml;
    
    
    begin dialog conversation @conversation_handler
    from service [//SackConsulting/BookOrderService]
    to service '//SackConsulting/BookStoreDistributionService'
    on contract [//SackConsulting/BookOrderContract];
    
    
    set @order_msg = 
    	'<order id="1234" customer="22" orderdate="2012-10-01">
    	<LineItem ItemNumber="1" ISBN="1-12345-123-0" Quantity="1" />
    	</order>
    	';
    
    --send语句可以发送消息	
    send on conversation @conversation_handler
    message type [//SackConsulting/SendBookOrder]
    (@order_msg);
    
    
    --7.2.1检索消息
    use bookDistribution
    go
    
    select message_type_name,      --消息类型名
           CAST(message_body as xml) as message,  --消息
           queuing_order,                         --队列顺序,从0开始
           
           conversation_handle,                   --会话句柄
           conversation_group_id                  --会话组id
    from dbo.bookstoreDistributionQueue
    
    
    
    create table dbo.bookOrderReceived
    (
    	bookOrderReceivedID int identity(1,1) not null,
    	conversation_handle uniqueidentifier not null,
    	conversation_group_id uniqueidentifier not null,
    	message_body xml not null
    )
    
    
    --7.2.2receive语句会从队列中读取消息,并且把已经读取的消息删除
    declare @conversation_handler uniqueidentifier
    declare @conversation_group uniqueidentifier
    
    declare @order_msg xml
    
    declare @Text_response_msg varchar(max)
    declare @response_msg xml
    
    declare @orderID int;
    
    
    receive top(1)
    	@order_msg = message_body,
    	@conversation_handler = conversation_handle,
    	@conversation_group = conversation_group_id
    from dbo.bookStoreDistributionQueue;
    
    
    insert into dbo.bookOrderReceived
    (conversation_handle,conversation_group_id,message_body)
    values(@conversation_handler,
           @conversation_group,
           @order_msg)
    
    
    select @orderID = @order_msg.value('(/order/@id)[1]','int')
    
    select @Text_response_msg = 
    	'<orderreceived id="' + CAST(@orderID as varchar(10)) + '"/>'
    
    select @response_msg = CAST(@Text_response_msg as xml);
    
    --7.2.3发送回复消息
    send on conversation @conversation_handler
    message type [//SackConsulting/BookOrderReceived]
    (@response_msg);
    
    
    
    --7.3查看返回的消息,结束会话
    use bookstore
    go
    
    
    create table dbo.bookOrderConfirmation
    (
    	bookorderconfirmationID int identity(1,1) not null,
    	conversation_handle uniqueidentifier not null,
    	datereceived datetime not null default getdate(),
    	message_body xml not null
    )
    
    
    declare @conversation_handler uniqueidentifier
    declare @conversation_group uniqueidentifier
    
    declare @order_msg xml
    declare @text_response_msg varcahr(max);
    
    
    receive top(1)
    	@conversation_handler = conversation_handle,
    	@order_msg = message_body
    from dbo.bookstorequeue
    
    
    insert into dbo.bookOrderConfirmation
    (conversation_handle,message_body)
    values(@conversation_handler,@order_msg)
    
    
    end conversation @conversation_handler;  --结束会话
    
    
    
    --7.4取出消息,判断是否是结束会话消息类型,如果是,那么结束会话
    use bookDistribution
    go
    
    
    declare @conversation_handler uniqueidentifier
    declare @conversation_group uniqueidentifier
    
    declare @order_msg xml
    
    declare @message_type_name nvarchar(256);
    
    
    receive top(1)
    	@conversation_handler =  conversation_handle,
    	@order_msg = message_body,
    	@message_type_name = message_type_name
    from dbo.bookstoredistributionqueue
    
    /*======================================================
    结束会话会自动发送:
    http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog
    的消息类型到目标数据库,双方,包括发起方和目标,必须都结束会话
    
    ========================================================*/
    if @message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
    begin
    	end conversation @conversation_handler;
    end
    
    
    --7.5查看会话端点的状态
    select *
    from sys.conversation_endpoints
    
    
    
    
    --8.1设定Serivce Broker会话的优先级
    ALTER DATABASE BOOKSTORE
    set honor_broker_priority on
    
    alter database bookdistribution
    set honor_broker_priority on
    
    
    --8.2查看数据库的属性
    select name,
           is_honor_broker_priority_on
    from sys.databases
    where name in ('bookstore','bookdistribution')
    
    
    --8.3创建Service Broker优先级
    use bookstore
    go
    
    create broker priority conversation_priority_bookordercontract_bookorderservice
    for conversation
    set (
    		contract_name = [//SackConsulting/BookOrderContract],
    		local_service_name = [//SackConsulting/BookOrderService],
    		remote_service_name = any,
    		priority_level = 10
    	)
    
    select cp.name,
           cp.priority,             --优先级
           
           cp.service_contract_id,
           sc.name,                 --约定名称
           
           cp.local_service_id,
           s.name,                  --服务名称       cp.remote_service_name
           
    from sys.conversation_priorities cp
    
    inner join sys.service_contracts sc
            on cp.service_contract_id = sc.service_contract_id
            
    inner join sys.services s
            on s.service_id = cp.local_service_id
    
    
    use bookDistribution
    go
    
    create broker priority conversation_priority_bookordercontract_bookstoredistributionservice
    for conversation
    set
    	(
    		contract_name = [//SackConsulting/BookOrderContract],
    		local_service_name = [//SackConsulting/BookStoreDistributionService],
    		remote_service_name = any,
    		priority_level = 10
    	)
    
    select cp.name,
           cp.priority,             --优先级
           
           cp.service_contract_id,
           sc.name,                 --约定名称
           
           cp.local_service_id,
           s.name,                  --服务名称
           cp.remote_service_name       
    from sys.conversation_priorities cp
    inner join sys.service_contracts sc
            on cp.service_contract_id = sc.service_contract_id
            
    inner join sys.services s
            on s.service_id = cp.local_service_id
    	
    
    
    --8.4修改优先级
    use bookstore
    go
    
    alter broker priority conversation_priority_bookordercontract_bookorderservice
    for conversation
    set
    	(
    		remote_service_name = '//SackConsulting/BookStoreDistributionService'
    	)
    
    
    use bookDistribution
    go
    
    alter broker priority conversation_priority_bookordercontract_bookstoredistributionservice
    for conversation
    set
    	(
    		priority_level = 9
    	)
    	
    
    --8.5删除优先级
    drop broker priority conversation_priority_bookordercontract_bookstoredistributionservice


     创建处理消息的存储过程

    前面使用了临时的T-SQL来处理从队列传入的消息,也可以通过存储过程或外部应用程序创建服务程序,来自动的激活并处理队列中的消息的服务程序,同时还可以指定同时执行的服务程序的数量。

    use bookDistribution
    go
    
    create procedure dbo.usp_service_broker_ReceiveOrders
    as
    
    declare @conversation_handler uniqueidentifier
    declare @conversation_group uniqueidentifier
    declare @order_msg xml
    declare @text_response_msg varchar(8000)
    
    declare @response_msg xml
    declare @message_type_name nvarchar(156)
    declare @orderID int
    
    --当发生运行时错误时,会自动回滚事务
    set xact_abort on
    
    
    begin tran;
    	
    	--接收消息
    	receive top(1) 
    		@order_msg = message_body,
    	    @conversation_handler = conversation_handle,
    	    @conversation_group = conversation_group_id,
    	    @message_type_name = message_type_name
    	from dbo.bookStoreDistributionQueue
    	
    	--消息类型
    	if @message_type_name = '//SackConsulting/SendBookOrder'
    	begin
    		insert into dbo.bookOrderReceived
    		(conversation_handle,conversation_group_id,message_body)
    		values(@conversation_handler,
    		       @conversation_group,
    		       @order_msg)
    		       
    		select @orderID = @order_msg.value('(/order/@id)[1]','int')
    		
    		select @text_response_msg = '<orderreceived id="' + 
    		                             CAST(@orderID as varchar(10)) +
    		                             '"/>';
    		
    		select @response_msg = CAST(@text_response_msg as XML);
    		
    		send on conversation @conversation_handler
    		message type [//SackConsulting/BookOrderReceived]
    		(@response_msg);
    	end
    	
    	--如果收到结束会话的消息
    	if @message_type_name = 
    	       'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
        begin	
    		end conversation @conversation_handler;
    	end
    	
    commit tran
    
    go
    					
    
    --修改队列设置
    alter queue dbo.BookstoreDistributionQueue
    with activation 
    (
    	status = on,
    	procedure_name = dbo.usp_service_broker_receiveOrders,
    	max_queue_readers = 2,   --存储过程执行的最大数量
    	execute as self
    )	
    
    
    /*==========================================
    通过drop:删除与队列关联的所有激活信息
    
    alter queue dbo.BookstoreDistributionQueue
    with activation 
    (
    	drop
    )	
    ============================================*/   
    
    use bookstore
    go
    
    declare @conversation_handler uniqueidentifier
    
    declare @order_msg xml
    
    
    begin dialog conversation @conversation_handler
    from service [//SackConsulting/BookOrderService]
    to service '//SackConsulting/BookStoreDistributionService'
    on contract [//SackConsulting/BookOrderContract];
    
    
    set @order_msg = 
    	'<order id="1234" customer="22" orderdate="2012-10-01">
    	<LineItem ItemNumber="1" ISBN="1-12345-123-0" Quantity="1" />
    	</order>
    	';
    
    --send语句可以发送消息	
    send on conversation @conversation_handler
    message type [//SackConsulting/SendBookOrder]
    (@order_msg);
    
    
    --可以接收到一个消息,这个消息是对方在接受到下消息后,激活存储过程后,由存储过程回复的消息
    select conversation_handle,
           CAST(message_body as xml)
    from dbo.bookStoreQueue     
       


     实现远程服务器的Service Broker

    前面的例子是同一个SQL Server实例的两个数据库,但大部分情况会将Service Broker设置为,使用在两个或多个SQL Server实例上的数据库。为了实现跨越服务器通信,可以通过Windows身份验证或基于证书的身份验证,启用传输安全模式、启用对话安全模式、创建路由、创建远程绑定。

    --在实例1上
    use master
    go
    
    if exists(select 1 from sys.databases where name = 'bookstore')
    	drop database bookstore
    else
    	create database bookstore
    go
    
    
    alter database bookstore set enable_broker
    
    alter database bookstore set trustworthy on
    
    
    use bookstore
    go
    
    create message type [//BookConsulting/SendBookOrder]
    validation = well_formed_xml
    go
    
    create message type [//BookConsulting/BookOrderReceived]
    validation = well_formed_xml
    go
    
    
    create contract [//BookConsulting/BookOrderContract]
    (
    	[//BookConsulting/SendBookOrder] sent by initiator,
    	[//BookConsulting/BookOrderReceived] sent by target
    )
    go
    
    
    
    create queue BookStoreQueue
    with status = on
    
    
    create service [//BookConsulting/BookOrderService]
    on queue dbo.BookStoreQueue
    (
    	[//BookConsulting/BookOrderContract]
    )
    
    
    
    --在实例2上
    use master
    go
    
    if exists(select 1 from sys.databases where name = 'bookdistribution')
    	drop database bookdistribution
    else
    	create database bookdistribution
    go
    
    
    alter database bookdistribution set enable_broker
    
    alter database bookdistribution set trustworthy on
    
    
    use bookdistribution
    go
    
    create message type [//BookConsulting/SendBookOrder]
    validation = well_formed_xml
    go
    
    create message type [//BookConsulting/BookOrderReceived]
    validation = well_formed_xml
    go
    
    
    create contract [//BookConsulting/BookOrderContract]
    (
    	[//BookConsulting/SendBookOrder] sent by initiator,
    	[//BookConsulting/BookOrderReceived] sent by target
    )
    go
    
    
    
    create queue BookDistributionQueue
    with status = on
    
    
    create service [//BookConsulting/BookDistributionService]
    on queue dbo.BookDistributionQueue
    (
    	[//BookConsulting/BookOrderContract]
    )
    
    
    
    
    --启用传输安全模式,只是限制其他实例是否能访问本地服务器的端点
    
    --实例1
    
    use master 
    go
    
    
    --1.删除已经存在的数据库主密钥
    drop master key
    
    
    --2.创建数据库主密钥
    create master key encryption by password = '123456!@#'
    
    
    --3.创建证书
    create certificate bookMasterCert
    with subject = 'book Transport Security Service Broker',
    	 expiry_date = '2012-12-31' 
    
    
    --4.备份证书
    backup certificate bookMasterCert
    to file = 'c:\bookMasterCert.cer'
    go
    
    
    --5.创建端点
    create endpoint service_broker_book_endpoint
    state = started
    as tcp (listener_port = 4020)
    for service_broker (
    						authentication = certificate bookMasterCert,
    						encryption = required
                       )
    
    
    --6.创建SQL Server的登录名
    create login service_broker_login
    with password = 'service_broker_login123'
    
    
    --7.创建数据库用户名
    create user service_broker_user
    for login service_broker_login
    
    
    --8.授予数据库用户可以连接端点
    grant connect on endpoint::service_broker_book_endpoint 
                  to service_broker_login
    
    
    --9.通过另一个实例复制到本地服务器上的证书文件,来创建证书
    create certificate bookDistributionMasterCert
    authorization service_broker_user
    from file = 'c:\bookDistributionMasterCert.cer'
    go
    
    
    
    
    --实例2
    
    use master 
    go
    
    
    --删除数据库主密钥
    drop master key
    
    create master key encryption by password = '123456&^%'
    
    
    create certificate bookDistributionMasterCert
    with subject = 'bookDistribution Transport Security Service Broker',
    	 expiry_date = '2012-12-31' 
    
    
    backup certificate bookDistributionMasterCert
    to file = 'c:\bookDistributionMasterCert.cer'
    
    
    create endpoint service_broker_bookdistribution_endpoint
    state = started
    as tcp (listener_port = 4021)
    for service_broker (
    						authentication = certificate bookDistributionMasterCert,
    						encryption = required
                       )
    
    
    create login service_broker_login
    with password = 'service_broker_login123'
    
    
    create user service_broker_user
    for login service_broker_login
    
    
    grant connect on endpoint::service_broker_bookdistribution_endpoint 
                  to service_broker_login
    
    
    create certificate bookMasterCert
    authorization service_broker_user
    from file = 'c:\bookMasterCert.cer'
    go
    
    
    
    
    --启用对话安全模式
    --实例1
    use bookstore
    go
    
    --1.创建数据库主密钥
    create master key encryption by password = '123456!@#'  
    
    
    --2.创建证书,这里可以给当前数据库用户创建多个证书,不会有影响
    --当接收到其他服务器传送过来的消息时,可以用这个证书来解密消息
    create certificate BookStoreCert
    with subject = 'BookStore service broker cert',
         expiry_date = '2012-12-31'
    
    
    --3.备份证书
    backup certificate bookstorecert
    to file = 'c:\bookstorecert.cer'
    go
    
    
    --4.创建数据库用户,此用户只可以有一个证书
    create user bookDistributionUser
    without login
    go
    
    
    --5.通过从另一个实例复制过来的证书,来创建证书,并指定所有者为此用户
    create certificate bookDistributionCert
    authorization bookDistributionUser        --此用户只能拥有一个证书,
                                              --在发送消息时会用这个证书来加密消息
    from file = 'c:\bookDistributionCert.cer'
    
    
    --6.授予此用户名在某个服务上发送的权限
    grant send on service::[//BookConsulting/BookOrderService] to bookDistributionUser
    go
    
    
    --7.创建路由
    create route route_bookDistribution
    with service_name = '//BookConsulting/BookDistributionService',
         address = 'tcp://192.168.1.16:4021'
    
    
    --8.创建远程绑定
    create remote service binding bookDistributionBinding
    to service '//BookConsulting/BookDistributionService'
    with user = bookDistributionUser
    
    
    --9.开始会话,发送消息
    declare @conversation_handler uniqueidentifier
    declare @order_msg xml;
    
    
    begin dialog conversation @conversation_handler
    from service [//BookConsulting/BookOrderService]
    to service '//BookConsulting/BookDistributionService'
    on contract [//BookConsulting/BookOrderContract]
    
    
    set @order_msg = 
    	'<order id="1234" customer="22" orderdate="2012-10-01">
    	<LineItem ItemNumber="1" ISBN="1-12345-123-0" Quantity="1" />
    	</order>
    	';
    
    
    --send语句可以发送消息	
    send on conversation @conversation_handler
    message type [//BookConsulting/SendBookOrder]
    (@order_msg);
    
    
    
    
    --启用对话安全模式
    --实例2
    use bookdistribution
    go
    
    create master key encryption by password = '123456&^%'  
    
    
    --当接收到对方发送的消息后,用此证书来解密
    create certificate BookDistributionCert
    with subject = 'BookDistribution service broker cert',
         expiry_date = '2012-12-31'
    
    
    backup certificate bookDistributioncert
    to file = 'c:\bookDistributioncert.cer'
    
    
    create user bookStoreUser
    without login
    
    
    --在发送之前,用此证书来加密消息
    create certificate bookStoreCert
    authorization bookStoreUser
    from file = 'c:\bookStoreCert.cer'
    
    
    grant send on service::[//BookConsulting/BookDistributionService] to bookStoreUser
    
    
    create route route_bookStore
    with service_name = '//BookConsulting/BookOrderService',
         address = 'tcp://192.168.9.67:4020'
    
    
    create remote service binding bookStoreBinding
    to service '//BookConsulting/BookOrderService'
    with user = bookStoreUser
    
    
    --查询消息
    SELECT *
    FROM dbo.bookdistributionqueue  
            
    


    事件通知
    事件通知是集成到Service Broker的功能,这样可以在SQL Server实例中异步捕获SQL事件,将事件信息路由到特定的队列中。只需要最小的系统开销,就可以跟踪发生在SQL Server实例的事件,比如用户登录,存储过程重新编译,权限修改,对象处理(包括:对数据库、程序集、角色、表的create/alter/drop事件)。

    使用事件通知只需要创建队列和Service Broker组件,在SQL Server中已经内建了,用来捕捉和发送事件通知的消息类型、约定。

    IF NOT exists(select 1
                  from sys.databases
                  where name = 'EventTracking')
                  
    	create database EventTracking
    else
    	drop database EventTracking
    	
    go
    
    
    use eventtracking
    go
    
    
    --1.创建队列
    create queue SQLEventQueue
    with status = on
    go
    
    
    --2.在队列上创建服务,关联到内建的事件通知约定
    create service [//EventTracking/TrackLoginAlterService]
    on queue SQLEventQueue
    ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);
    
    
    --3.查询guid
    select service_broker_guid --6AE234DF-C4BB-4C1B-9E08-F4EA66359B6A
    from sys.databases
    where name = 'EventTracking'
    
    
    --4.创建server范围的事件通知,来跟踪SQL Server实例中所有登录名的创建,修改,删除
    create event notification EVENT_LoginEvent
    on server
    for create_login,alter_login,drop_login
    to service '//EventTracking/TrackLoginAlterService',
       '6AE234DF-C4BB-4C1B-9E08-F4EA66359B6A'   --service_broker_guid
    
    
    --5.创建一个登录名,事件通知,把消息放入队列
    create login login_ggg with password = '123456!@'
    
    
    --6.查询消息
    select CAST(message_body as xml) as event_Info
    from dbo.sqleventqueue
    

     

    展开全文
  • ASIHTTPRequest类库简介和使用说明 ...可以从上面下载到最新...使用iOS SDK中的HTTP网络请求API,相当的复杂,调用很繁琐,ASIHTTPRequest就是一个CFNetwork API进行了封装,并且使用起来非常简单的一套API,用Object

    ASIHTTPRequest类库简介和使用说明

    官方网站: http://allseeing-i.com/ASIHTTPRequest/ 。可以从上面下载到最新源码,以及获取到相关的资料。

    使用iOS SDK中的HTTP网络请求API,相当的复杂,调用很繁琐,ASIHTTPRequest就是一个对CFNetwork API进行了封装,并且使用起来非常简单的一套API,用Objective-C编写,可以很好的应用在Mac OS X系统和iOS平台的应用程序中。ASIHTTPRequest适用于基本的HTTP请求,和基于REST的服务之间的交互。

    ASIHTTPRequest功能很强大,主要特色如下:

    • l 通过简单的接口,即可完成向服务端提交数据和从服务端获取数据的工作
    • l 下载的数据,可存储到内存中或直接存储到磁盘中
    • l 能上传本地文件到服务端
    • l 可以方便的访问和操作请求和返回的Http头信息
    • l 可以获取到上传或下载的进度信息,为应用程序提供更好的体验
    • l 支持上传或下载队列,并且可获取队列的进度信息
    • l 支持基本、摘要和NTLM身份认证,在同一会话中授权凭证会自动维持,并且可以存储在Keychain(Mac和iOS操作系统的密码管理系统)中
    • l 支持Cookie
    • l 当应用(iOS 4+)在后台运行时,请求可以继续运行
    • l 支持GZIP压缩数据
    • l 内置的ASIDownloadCache类,可以缓存请求返回的数据,这样即使没有网络也可以返回已经缓存的数据结果
    • l ASIWebPageRequest –可以下载完整的网页,包括包含的网页、样式表、脚本等资源文件,并显示在UIWebView /WebView中。任意大小的页面都可以无限期缓存,这样即使没有网络也可以离线浏览
    • l 支持客户端证书
    • l 支持通过代理发起Http请求
    • l 支持带宽限制。在iOS平台,可以根据当前网络情况来自动决定是否限制带宽,例如当使用WWAN(GPRS/Edge/3G)网络时限制,而当使用WIFI时不做任何限制
    • l 支持断点续传
    • l 支持同步和异步请求
    2.1.1安装说明

    如果想在iOS项目中使用ASIHTTPRequest,需要在项目中进行简单的配置,步骤如下:

    1) 添加文件

    往一个Xcode项目中添加第三方类库文件,有两种方式:

    1. 第一种方式,在Finder中打开需要添加到文件或文件夹,在Xcode中打开要添加文件的项目,然后选中要添加的文件或文件夹,将它从Finder中拖到Xcode中,然后释放。在弹出的对话框中,如果文件已经拷贝到了项目文件目录中,则不需要选中“Copy items”的复选框;如果文件没有拷贝到项目文件目录,就需要选中“Copy items”的复选框,这样Xcode会自动把文件复制到项目文件目录下。如下图所示:
    clip_image002
    clip_image004

    2. 第二种方式,在Xcode中,在要添加文件的分组下点右键,选中“Add Files to “My Project”…”菜单,在弹出的文件浏览对话框中选中要添加到文件或文件夹。如果要添加文件已经拷贝到了项目文件目录中,则不需要选中“Copy items”的复选框;如果文件没有拷贝到项目文件目录,就需要选中“Copy items”的复选框,这样Xcode会自动把文件复制到项目文件目录下。如下图所示:
    clip_image006
    clip_image008

    根据上面的说明,添加ASIHTTPRequest相关文件到Xcode项目中,所需文件列表如下:

    ASIHTTPRequestConfig.h

    ASIHTTPRequestDelegate.h

    ASIProgressDelegate.h

    ASICacheDelegate.h

    ASIHTTPRequest.h

    ASIHTTPRequest.m

    ASIDataCompressor.h

    ASIDataCompressor.m

    ASIDataDecompressor.h

    ASIDataDecompressor.m

    ASIFormDataRequest.h

    ASIInputStream.h

    ASIInputStream.m

    ASIFormDataRequest.m

    ASINetworkQueue.h

    ASINetworkQueue.m

    ASIDownloadCache.h

    ASIDownloadCache.m

    ASIAuthenticationDialog.h

    ASIAuthenticationDialog.m

    Reachability.h (在源码的 External/Reachability 目录下)//再我项目中这两个文件没有用到

    Reachability.m (在源码的 External/Reachability 目录下)

    2) 链接相关类库

    1. 选中项目

    2. 选中目标

    3. 跳转到“Build Phases”标签

    4. 展开“Link Binary With Libraries”分组

    5. 点击“+”添加类库

    如下图所示:

    clip_image010

    6. 从列表中选择CFNetwork.framework,然后点击“Add”按钮。

    clip_image012

    7. 按照上一步相同的方法添加:SystemConfiguration.framework, MobileCoreServices.framework,CoreGraphics.framework和libz.1.2.3.dylib这几个类库。

    8. 添加完后,可以将添加好的一起类库拖到Xcode项目的Frameworks目录下
    clip_image014

    2.1.2使用说明

    ASIHTTPRequest有很多功能,所有功能说明都可以在其官方网站的相关文档中查到,限于篇幅,本章仅简单介绍一下如何使用ASIHTTPRequest来进行同步Http请求和异步Http请求。在后面的章节中,我们还会用到它的一些其他功能。

    在使用ASIHTTPRequest之前,请确认已经正确安装,然后在需要应用它的代码文件头部,加入:

    #import “ASIHTTPRequest.h”

    这样就可以在代码中使用ASIHTTPRequest相关的类。

    创建一个同步请求

    这是ASIHTTPRequest最简单的一种使用模式,发送startSynchronous消息后即开始在同一线程中执行HTTP请求,线程将一直等待直到请求结束(请求成功或者失败)。通过检查error属性可以判断请求是否成功或者有错误发生。

    要获取返回的文本信息,调用responseString方法。如果下载的是二进制文件,例如图片、MP3,则调用responseData方法,可以得到一个NSData对象。

    - (IBAction)grabURL:(id)sender

    {

    NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com"];

    ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];

    [request startSynchronous];

    NSError *error = [request error];

    if (!error) {

    NSString *response = [request responseString];

    }

    }

    一般情况下,应该优先使用异步请求代替同步请求,当在主线程中使用ASIHTTPRequest同步请求,应用程序的界面会锁定,无法进行任何操作,直到请求完成。

    创建一个异步请求

    上例中的同步请求,如果换成异步方式来调用,请求是在后台线程中运行,当请求执行完后再通知调用的线程。这样不会导致主线程进行网络请求时,界面被锁定等情况。

    - (IBAction)grabURLInBackground:(id)sender

    {

    NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com"];

    ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];

    [request setDelegate:self];

    [request startAsynchronous];

    }

    - (void)requestFinished:(ASIHTTPRequest *)request

    {

    // 当以文本形式读取返回内容时用这个方法

    NSString *responseString = [request responseString];

    // 当以二进制形式读取返回内容时用这个方法

    NSData *responseData = [request responseData];

    }

    - (void)requestFailed:(ASIHTTPRequest *)request

    {

    NSError *error = [request error];

    }

    demo:




    展开全文
  • 创建异步 HTTP 处理程序

    千次阅读 2007-07-30 11:34:00
    ASP.NET如何:创建异步 HTTP 处理程序 使用异步 HTTP 处理程序,您可以在启动一个外部进程(例如对远程服务器的方法调用)的同时继续该处理程序的处理工作,而不必等待外部进程的完成。在异步 HTTP 处理程序的处理...
  • 紧接连载四,我们接下从功耗控制、功能接口和可移植性的角度分别分析Android系统为应用程序提供的支撑,本次连载为本系列文章的最后一篇。 前情回顾: Android应用程序开发以及背后的设计思想深度剖析(1)
  • pAdTy_5 构建可穿戴设备的应用程序

    千次阅读 2015-12-23 22:00:35
    2015.12.23 - 2016.01.13 ...这些节描述如何构建在手持应用程序中会自动同步到可穿戴设备上的通知(Notifications),同时描述如何构建运行在可穿戴设备上的应用程序。注:关于用在笔记中会使用到的APIs的信
  • 轻松搞定应用程序集成(转载)

    千次阅读 2005-01-20 23:47:00
    作者:Rag Ramanathan 有限的可重用性、多种数据格式、紧密耦合以及许多其他问题妨碍着应用程序的集成,但 SOA 可以改变这一切。 应用程序集成是企业信息技术管理人员所面临的最为关键问题之一。企业使用了许多...
  • 应用程序互操作性:点

    千次阅读 2004-09-16 14:04:00
    发布日期: 09/15/2004 | 更新日期: 09/15/2004Microsoft Corporation摘要: 着重介绍 XML Web 服务和 .NET 远程处理的点点通讯方法。 主题包括二进制通讯和路由以及集成 Java 和 .NET 内容的第三方运行时桥的...
  • 初步过了一下,很多地方写得还是比较深入的,先转载,后面再仔细看看。...我们先会简单介绍一下Android里的应用程序编程,然后以这些应用程 序在运行环境上的需求来分析出,为什么我们的Android系统需要今天这样的设
  • Netty是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端。在网络编程领域,Netty是Java的卓越框架,它驾驭了Java高级API的能力,并将其隐藏在一个易于使用的API之后...
  • Service与Android系统实现(1)-- 应用程序里的Service

    万次阅读 多人点赞 2012-10-18 22:44:51
    Activity对应用程序来说是最重要的组件,但从Android系统设计的角度来看,Service系统层实现来说才最重要的。Service是构建系统的根本,支持整个系统运营的环境framework,本身就是由大量Service来构成的。也就是...
  • 开发端到端的Ajax应用程序(转)

    千次阅读 2007-12-16 23:33:00
    开发端到端的 Ajax 应用程序,第 3 部分: 集成、测试和调试应用程序 隔离应用程序层以产生干净优雅的 Web 应用程序
  • 应用程序中加入jBPM组件

    万次阅读 2006-10-23 17:39:00
    本文介绍怎样把jBPM组件添加到Web应用程序中。所需要用到的资源,可以在jbpm-starters-kit-3.1.2中找到。一、首先安装jBPM数据库。jBPM是一个停止状态的组件,需要数据库表持久化保存:1)业务程序定义和业务程序...
  • ,创建Webservice的内容一直到我原来文章的“这段代码的大致解读”为止,之前的都是一样的,从那里之后,我们的内容才会有变化,因为我现在的代码是才用的异步调用的方法。   然后我们将新的代码复制进去: ...
  • Delphi应用程序的Help编程

    千次阅读 2006-04-20 09:37:00
    转自:http://spaces.msn.com/OnlyDelphi/ Delphi应用程序的Help编程 以前做的帮助都是用第三方的工具做
  • 桌面应用更新程序 启动时,根据主程序本地的版本文件(LocalVersion.xml),拿到远程更新地址,比较远程配置的版本文件(ServerVersion.xml) 如果有新版本,则判断更新程序是否位于系统盘,且是否为管理员身份...
  • 转载一篇Android 的先关文章,无论什么时候看都觉得有收获 文章来源地址 ... ...本文内容,主题是透过应用程序来分析...我们先会简单介绍一下Android里的应用程序编程,然后以这些应用程 序在运行环境上的需求来分析出,
  • 设计由应用程序管理的授权

    千次阅读 2004-11-12 10:27:00
    摘要本指南介绍为基于 Microsoft® .NET 的单层或多层应用程序设计和编写由应用程序管理的授权的指导原则,主要讨论常见的授权任务和方案,并提供相应的信息帮助您选择最佳方法和技术。本指南适用于体系结构设计人员...
  • pAdTy_2 构建连接网络和云的应用程序

    千次阅读 2015-11-30 11:28:25
    2015.11.18 - 12.09 个人英文阅读练习笔记。...2015.11.18 此部分内容展示如何编写...即怎么在局域中“连接其它设备”、“连接互联网”、“备份及同步应用程序的数据”等内容。1. 无线连接设备如何用网络服务搜索来找
  • 以下编程做法可以节省内存和改善设备应用程序的性能。使用 Windows 窗体和图形节省内存 提供 BeginUpdate 和 EndUpdate 方法的控件使用这两种方法,提供这两种方法的控件包括 ComboBox、ListBox、ListView、...
  • 介绍分布式应用程序可以使用面向消息的中间件(MOM)在企业应用程序的组件之间异步发送和接收消息。由于消息传递的异步性,MOM 系统提供了很大的灵活性,它们不遵照几乎所有其它通信接口都使用的请求?响应模式。MOM ...
  • Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。 JDK 原生 NIO 程序的问题 JDK 原生也有一套网络应用程序 API,但是存在一系列问题,主要如下: NIO 的类库和 API ...
  • John Papa 代码下载位置: SLDataServices2008_09a.exe (234 KB) 在线浏览代码 本专栏基于 Silverlight 2 的... 下载本文中所用的代码: DataPoints2008_09a.exe (414 KB) 浏览在线代码 目录 示例应用程序 跨域通信 Silv

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 32,499
精华内容 12,999
关键字:

异步远程复制对应用程序