您当前的位置: 首页 > 

phymat.nico

暂无认证

  • 0浏览

    0关注

    1967博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

ACE的接受器(Acceptor)和连接器(Connector):连接建立模式

phymat.nico 发布时间:2017-12-21 00:10:04 ,浏览量:0

接受器 / 连接器模式设计用于降低连接建立与连接建立后所执行的服务之间的耦合。例如,在 WWW 浏览器中,所执行的服务或“实际工作”是解析和显示客户浏览器接收到的 HTML 页面。连接建立是次要的,可能通过 BSD socket 或其他一些等价的 IPC 机制来完成。使用这些模式允许程序员专注于“实际工作”,而最少限度地去关心怎样在服务器和客户之间建立连接。而另外一方面,程序员也可以独立于他所编写的、或将要编写的服务例程,去调谐连接建立的策略。

  因为该模式降低了服务和连接建立方法之间的耦合,非常容易改动其中一个,而不影响另外一个,从而也就可以复用以前编写的连接建立机制和服务例程的代码。在同样的例子中,使用这些模式的浏览器程序员一开始可以构造他的系统、使用特定的连接建立机制来运行它和测试它;然后,如果先前的连接机制被证明不适合他所构造的系统,他可以决定说他希望将底层连接机制改变为多线程的(或许使用线程池策略)。因为此模式提供了严格的去耦合,只需要极少的努力就可以实现这样的变动。

  在你能够清楚地理解这一章中的许多例子,特别是更为高级的部分之前,你必须通读有关反应堆和IPC_SAP的章节(特别是接受器和连接器部分)。此外,你还可能需要参考线程和线程管理部分。

 

 

 

7.1 接受器模式

    接受器通常被用在你本来会使用 BSD accept() 系统调用的地方。接受器模式也适用于同样的环境,但就如我们将看到的,它提供了更多的功能。在 ACE 中,接收器模式借助名为 ACE_Acceptor 的“工厂”( Factory )实现。工厂(通常)是用于对助手对象的实例化过程进行抽象的类。在面向对象设计中,复杂的类常常会将特定功能委托给助手类。复杂的类对助手的创建和委托必须很灵活。这种灵活性是在工厂的帮助下获得的。工厂允许一个对象通过改变它所委托的对象来改变它的底层策略,而工厂提供给应用的接口却是一样的,这样,可能根本就无需对客户代码进行改动(有关工厂的更多信息,请阅读“设计模式”中的参考文献)。

 

 

 

 

图7-1 工厂模式示意图

       ACE_Acceptor 工厂允许应用开发者改变“助手”对象,以用于:

  • 被动连接建立
  • 连接建立后的处理

   同样地, ACE_Connector 工厂允许应用开发者改变“助手”对象,以用于:

 

 

  • 主动连接建立
  • 连接建立后的处理

    下面的讨论同时适用于接受器和连接器,所以作者将只讨论接受器,而连接器同样具有相应的参数。

         ACE_Acceptor 被实现为模板容器,通过两个类作为实参来进行实例化。第一个参数实现特定的服务(类型为 ACE_Event_Handler。因为它被用于处理 I/O 事件,所以必须来自事件处理类层次),应用在建立连接后执行该服务;第二个参数是“具体的”接受器(可以是在 IPC_SAP 一章中讨论的各种变种)。

  特别要注意的是ACE_Acceptor工厂和底层所用的具体接受器是非常不同的。具体接受器可完全独立于ACE_Acceptor工厂使用,而无需涉及我们在这里讨论的接受器模式(独立使用接受器已在IPC_SAP一章中讨论和演示)。ACE_Acceptor工厂内在于接受器模式,并且不能在没有底层具体接受器的情况下使用。ACE_Acceptor使用底层的具体接受器来建立连接。如我们已看到的,有若干ACE的类可被用作ACE_Acceptor工厂模板的第二个参数(也就是,具体接受器类)。但是服务处理类必须由应用开发者来实现,而且其类型必须是ACE_Event_Handler。ACE_Acceptor工厂可以这样来实例化:

typedef ACE_Acceptor MyAcceptor;

    这里,名为 My_Service_Handler 的事件处理器和具体接受器 ACE_SOCK_ACCEPTOR 被传给 MyAcceptor 。 ACE_SOCK_ACCEPTOR 是基于 BSD socket 流家族的 TCP 接受器(各种可传给接受器工厂的不同类型的接受器,见表 7-1 和 IPC 一章)。请再次注意,在使用接受器模式时,我们总是处理两个接受器:名为 ACE_Acceptor 的工厂接受器,和 ACE 中的某种具体接受器,比如 ACE_SOCK_ACCEPTOR (你可以创建自定义的具体接受器来取代 ACE_SOCK_ACCEPTOR ,但你将很可能无需改变 ACE_Acceptor 工厂类中的任何东西)。

重要提示

:ACE_SOCK_ACCEPTOR实际上是一个宏,其定义为:

 

#define ACE_SOCK_ACCEPTOR ACE_SOCK_Acceptor, ACE_INET_Addr

 

我们认为这个宏的使用是必要的,因为在类中的typedefs在某些平台上无法工作。如果不是这样的话,ACE_Acceptor就可以这样来实例化:

 

typedef ACE_AcceptorMyAcceptor;

 

在表7-1中对宏进行了说明。

 

7.1.1 组件

 

    如上面的讨论所说明的,在接受器模式中有三个主要的参与类:

 

 

 

    具体接受器

:它含有建立连接的特定策略,连接与底层的传输协议机制系在一起。下面是在ACE中的各种具体接受器的例子:ACE_SOCK_ACCEPTOR(使用TCP来建立连接)、ACE_LSOCK_ACCEPTOR(使用UNIX域socket来建立连接),等等。

  • 具体服务处理器

:由应用开发者编写,它的open()方法在连接建立后被自动回调。接受器构架假定服务处理类的类型是ACE_Event_Handler,这是ACE定义的接口类(该类已在反应堆一章中详细讨论过)。另一个特别为接受器和连接器模式的服务处理而创建的类是ACE_Svc_Handler。该类不仅基于ACE_Event_Handler接口(这是使用反应堆所必需的),同时还基于在ASX流构架中使用的ACE_Task类。ACE_Task类提供的功能有:创建分离的线程、使用消息队列来存储到来的数据消息、并发地处理它们,以及其他一些有用的功能。如果与接受器模式一起使用的具体服务处理器派生自ACE_Svc_Handler、而不是ACE_Event_Handler,它就可以获得这些额外的功能。对ACE_Svc_Handler中的额外功能的使用,在这一章的高级课程里详细讨论。在下面的讨论中,我们将使用ACE_Svc_Handler作为我们的事件处理器。在简单的ACE_Event_Handler和ACE_Svc_Handler类之间的重要区别是,后者拥有一个底层通信流组件。这个流在ACE_Svc_Handler模板被实例化的时候设置。而在使用ACE_Event_Handler的情况下,我们必须自己增加I/O通信端点(也就是,流对象),作为事件处理器的私有数据成员。因而,在这样的情况下,应用开发者应该将他的服务处理器创建为ACE_Svc_Handler类的子类,并首先实现将被构架自动回调的open()方法。此外,因为ACE_Svc_Handler是一个模板,通信流组件和锁定机制是作为模板参数被传入的。

  • 反应堆

:与ACE_Acceptor协同使用。如我们将看到的,在实例化接受器后,我们启动反应堆的事件处理循环。反应堆,如先前所解释的,是一个事件分派类;而在此情况下,它被接受器用于将连接建立事件分派到适当的服务处理例程。

 

7.1.2 用法

 

    通过观察一个简单的例子,可以进一步了解接受器。这个例子是一个简单的应用,它使用接受器接受连接,随后回调服务例程。当服务例程被回调时,它就让用户知道连接已经被成功地建立。

 

例7-1

 

#include ”ace/Reactor.h”

#include ”ace/Svc_Handler.h”

#include ”ace/Acceptor.h”

#include ”ace/Synch.h”

#include ”ace/SOCK_Acceptor.h”

 

//Create a Service Handler whose open() method will be called back

//automatically. This

 class MUST derive from ACE_Svc_Handler which is an

//interface and as can be seen is a

 template container class itself. The

//first parameter to ACE_Svc_Handler is the

 underlying stream that it

//may use for communication. Since we are using TCP sockets the

 stream

//is ACE_SOCK_STREAM. The second is the internal synchronization

//mechanism it

 could use. Since we have a single threaded application we

//pass it a “null” lock which

 will do nothing.

class My_Svc_Handler:

public ACE_Svc_Handler

{

//the open method which will be called back automatically after the

//connection has been

 established.

public:

int open(void*)

{

cout

thr_mgr()->wait();

 

return 0;

}

};

 

ACE_INET_Addr *addr;

 

int main(int argc, char* argv[])

{

addr= new ACE_INET_Addr(PORT_NUM,argv[1]);

 

//Creation Strategy

NULL_CREATION_STRATEGY creation_strategy;

 

//Concurrency Strategy

NULL_CONCURRENCY_STRATEGY concurrency_strategy;

 

//Connection Strategy

CACHED_CONNECT_STRATEGY caching_connect_strategy;

 

//instantiate the connector

STRATEGY_CONNECTOR connector(

ACE_Reactor::instance(), //the reactor to use

&creation_strategy,

&caching_connect_strategy,

&concurrency_strategy);

 

//Use the thread manager to spawn a single thread

//to connect multiple times passing it the address

//of the strategy connector

if(ACE_Thread_Manager::instance()->spawn(

(ACE_THR_FUNC) make_connections,

(void *) &connector,

THR_NEW_LWP) == -1)

 

ACE_ERROR ((LM_ERROR, ”(%P|%t) %p\n%a”, ”client thread spawn failed”));

 

while(1) /* Start the reactor’s event loop */

ACE_Reactor::instance()->handle_events();

}

 

//Connection establishment function, tries to establish connections

//to the same server again and re-uses the connections from the cache

void make_connections(void *arg)

{

ACE_DEBUG((LM_DEBUG,”(%t)Prepared to connect \n”));

STRATEGY_CONNECTOR *connector= (STRATEGY_CONNECTOR*) arg;

for (int i = 0; i < 10; i++)

{

My_Svc_Handler *svc_handler = 0;

 

// Perform a blocking connect to the server using the Strategy

// Connector with a connection caching strategy. Since we are

// connecting to the same these calls will return the

// same dynamically allocated for each call.

if (connector->connect (svc_handler, *addr) == -1)

{

ACE_ERROR ((LM_ERROR, ”(%P|%t) %p\n”, ”connection failed\n”));

return;

}

 

// Rest for a few seconds so that the connection has been freed up

ACE_OS::sleep (5);

}

}

在上面的例子中,缓存式连接策略被用于缓存连接。要使用这一策略,需要一点额外的工作:定义

ACE_Cached_Connect_Strategy在内部使用的哈希映射管理器的hash()方法。这个hash()方法用于对服务处理器和ACE_Cached_Connect_Strategy内部使用的连接进行哈希运算,放入缓存映射中。它简单地使用IP地址和端口号的总和作为哈希函数,这也许并不是很好的哈希函数。

  这个例子比至今为止我们所列举的例子都要复杂一点,所以有理由多进行一点讨论。

  我们为

ACE_Strategy_Acceptor使用空操作并发和创建策略。使用空操作创建策略是必须的。如上面所解释的,如果没有使用ACE_NOOP_Creation_Strategy,ACE_Cached_Connection_Strategy将会产生断言失败。但是,在使用ACE_Cached_Connect_Strategy时,任何并发策略都可以和策略接受器一起使用。如上面所提到的,ACE_Cached_Connect_Strategys所用的底层创建策略可以由用户来设置。还可以设置recycling策略。这是在实例化caching_connect_strategy时,通过将所需的创建和recycling策略的对象传给它的构造器来完成的。在这里我们没有这样做,而是使用了缺省的创建和recycling策略。

  在适当地设置连接器后,我们使用

Thread_Manager来派生新线程,且将make_connections()方法作为线程的入口。该方法使用我们的新的策略连接器来连接到远地站点。在连接建立后,该线程休眠5秒钟,然后使用我们的缓存式连接器来重新创建同样的连接。于是该线程应该在连接器缓存中找到这个连接并复用它。

  和平常一样,一旦连接建立后,反应堆回调我们的服务处理器(

My_Svc_Handler)。随后My_Svc_Handler的open()方法通过调用My_Svc_Handler的activate()方法来使它成为主动对象。svc()方法随后发送三条消息给远地主机,并通过调用服务处理器的idle()方法将该连接标记为空闲。注意this->thr_mrg_wait()要求线程管理器等待所有在线程管理器中的线程终止。如果你不要求线程管理器等待其它线程,根据在ACE中设定的语义,一旦ACE_Task(在此例中是ACE_Task类型的ACE_Svc_Handler)中的线程终止,ACE_Task对象(在此例中是ACE_My_Svc_Handler)就会被自动删除。如果发生了这种情况,在Cache_Connect_Strategy查找先前缓存的连接时,它就不会如我们期望的那样找到My_Svc_Handler,因为它已经被删除掉了。

  在

My_Svc_Handler中还重载了ACE_Svc_Handler中的recycle()方法。当有旧连接被ACE_Cache_Connect_Strategy找到时,这个方法就会被自动回调,这样服务处理器就可以在此方法中完成回收利用所特有的操作。在我们的例子中,我们只是打印出在缓存中找到的处理器的this指针的地址。在程序运行时,每次连接建立后所使用的句柄的地址是相同的,从而说明缓存工作正常。

 

7.6

通过接受器和连接器模式使用简单事件处理器

  有时, ,使用重量级的

ACE_Svc_Handler作为接受器和连接器的处理器不仅没有必要,而且会导致代码臃肿。在这样情况下,用户可以使用较轻的ACE_Event_Handler方法来作为反应堆在连接一旦建立时所回调的类。要采用这种方法,程序员需要重载get_handle()方法,并包含将要用于事件处理器的具体底层流。下面的例子有助于演示这些变动。这里我们还编写了新的peer()方法,它返回底层流的引用(reference),就像在ACE_Svc_Handler类中所做的那样。

 

7-10

#include ”ace/Reactor.h”

#include ”ace/Svc_Handler.h”

#include ”ace/Acceptor.h”

#include ”ace/Synch.h”

#include ”ace/SOCK_Acceptor.h”

 

#define PORT_NUM 10101

#define DATA_SIZE 12

 

//forward declaration

class My_Event_Handler;

 

//Create the Acceptor class

typedef ACE_Acceptor

MyAcceptor;

 

//Create an event handler similar to as seen in example 2. We have to

//overload the get_handle() method and write the peer() method. We also

//provide the data member peer_ as the underlying stream which is

//used.

class My_Event_Handler: public ACE_Event_Handler

{

private:

char* data;

 

//Add a new attribute for the underlying stream which will be used by

//the Event Handler

ACE_SOCK_Stream peer_;

 

public:

My_Event_Handler()

{

data= new char[DATA_SIZE];

}

 

int open(void*)

{

cout

关注
打赏
1659628745
查看更多评论
立即登录/注册

微信扫码登录

0.0545s