第7章接受器(Acceptor)和连接器(Connector):连接建立模式
接受器/连接器模式设计用于降低连接建立与连接建立后所执行的服务之间的耦合。例如,在WWW浏览器中,所执行的服务或“实际工作”是解析和显示客户浏览器接收到的HTML页面。连接建立是次要的,可能通过BSD socket或其他一些等价的IPC机制来完成。使用这些模式允许程序员专注于“实际工作”,而最少限度地去关心怎样在服务器和客户之间建立连接。而另外一方面,程序员也可以独立于他所编写的、或将要编写的服务例程,去调谐连接建立的策略。
因为该模式降低了服务和连接建立方法之间的耦合,非常容易改动其中一个,而不影响另外一个,从而也就可以复用以前编写的连接建立机制和服务例程的代码。在同样的例子中,使用这些模式的浏览器程序员一开始可以构造他的系统、使用特定的连接建立机制来运行它和测试它;然后,如果先前的连接机制被证明不适合他所构造的系统,他可以决定说他希望将底层连接机制改变为多线程的(或许使用线程池策略)。因为此模式提供了严格的去耦合,只需要极少的努力就可以实现这样的变动。
在你能够清楚地理解这一章中的许多例子,特别是更为高级的部分之前,你必须通读有关反应堆和IPC_SAP的章节(特别是接受器和连接器部分)。此外,你还可能需要参考线程和线程管理部分。
接受器通常被用在你本来会使用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
这里,名为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_Acceptor
在表7-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#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<<”Connection established”<
}
};
// Create the acceptor as described above.
typedef ACE_Acceptor
int main(int argc, char* argv[])
{
//create the address on which we wish to connect. The constructor takes
//the port
number on which to listen and will automatically take the//host’s IP address as the IP
Address for the addr objectACE_INET_Addr addr(PORT_NUM);
//instantiate the appropriate acceptor object with the address on which
//we wish to
accept and the Reactor instance we want to use. In this//case we just use the global
ACE_Reactor singleton. (Read more about//the reactor in the previous chapter)
MyAcceptor acceptor(addr, ACE_Reactor::instance());
while(1)
// Start the reactor’s event loop
ACE_Reactor::instance()->handle_events();
}
在上面的例子中,我们首先创建我们希望在其上接受连接的端点地址。因为我们决定使用
TCP/IP作为底层的连接协议,我们创建一个ACE_INET_Addr来作为我们的端点,并将我们所要侦听的端口号传给它。我们将这个地址和反应堆单体的实例传给我们要在此之后进行实例化的接受器。这个接受器在被实例化后,将自动接受任何在PORT_NUM端口上的连接请求,并且在为这样的请求建立连接之后回调My_Svc_Handler的open()方法。注意当我们实例化ACE_Acceptor工厂时,我们传给它的是我们想要使用的具体接受器(也就是ACE_SOCK_ACCEPTOR)和具体服务处理器(也就是My_Svc_Handler)。现在让我们尝试一下更为有趣的事情。在下一个例子中,我们将在连接请求到达、服务处理器被回调之后,将服务处理器登记回反应堆。现在,如果新创建的连接上有任何数据到达,我们的服务处理例程
handle_input()方法都会被自动回调。因而在此例中,我们在同时使用反应堆和接受器的特性:
例
7-2#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_Svc_Handler;
//Create the Acceptor class
typedef ACE_Acceptor
MyAcceptor;
//Create a service handler similar to as seen in example 1. Except this
//time include the handle_input() method which will be called back
//automatically by the reactor when new data arrives on the newly
//established connection
class My_Svc_Handler:
public ACE_Svc_Handler
{
public:
My_Svc_Handler()
{
data= new char[DATA_SIZE];
}
int open(void*)
{
cout<<”Connection established”<
//Register the service handler with the reactor
ACE_Reactor::instance()->register_handler(this,
ACE_Event_Handler::READ_MASK);
return 0;
}
int handle_input(ACE_HANDLE)
{
//After using the peer() method of ACE_Svc_Handler to obtain a
//reference to the underlying stream of the service handler class
//we call recv_n() on it to read the data which has been received.
//This data is stored in the data array and then printed out
peer().recv_n(data,DATA_SIZE);
ACE_OS::printf(”<< %s\n”,data);
//keep yourself registered with the reactor
return 0;
}
private:
char* data;
};
int main(int argc, char* argv[])
{
ACE_INET_Addr addr(PORT_NUM);
//create the acceptor
MyAcceptor acceptor(addr, //address to accept on
ACE_Reactor::instance()); //the reactor to use
while(1)
//Start the reactor’s event loop
ACE_Reactor::instance()->handle_events();
}
这个例子和前一例子的唯一区别是我们在服务处理器的
open()方法中将服务处理器登记到反应堆上。因此,我们必须编写handle_input()方法;当数据在连接上到达时,这个方法会被反应堆回调。在此例中我们只是将我们接收到的数据打印到屏幕上。ACE_Svc_Handler类的peer()方法返回对底层的对端流的引用。我们使用底层流包装类的recv_n()方法来获取连接上接收到的数据。该模式真正的威力在于,底层的连接建立机制完全封装在具体接受器中,从而可以很容易地改变。在下一个例子里,我们改变底层的连接建立机制,让它使用
UNIX域socket、而不是TCP socket。这个例子(下划线标明少量变动)如下所示:
例
7-3class My_Svc_Handler:
public ACE_Svc_Handler <ACE_LSOCK_STREAM,ACE_NULL_SYNCH>{
public:
int open(void*)
{
cout<<”Connection established”<
ACE_Reactor::instance()
->register_handler(this,ACE_Event_Handler::READ_MASK);
}
int handle_input(ACE_HANDLE)
{
char* data= new char[DATA_SIZE];
peer().recv_n(data,DATA_SIZE);
ACE_OS::printf(”<< %s\n”,data);
return 0;
}
};
typedef ACE_Acceptor
int main(int argc, char* argv[])
{
ACE_UNIX_Addr addr(”/tmp/addr.ace”);
MyAcceptor acceptor(address, ACE_Reactor::instance());
while(1) /* Start the reactor’s event loop */
ACE_Reactor::instance()->handle_events();
}
例
7-2和例7-3不同的地方标注了下划线。正如我们所说过的,两个程序间的不同非常之少,但是它们使用的连接建立范式却极不相同。ACE中可用的连接建立机制在表7-1中列出:
接受器类型 | 所用地址 | 所用流 | 具体接受器 |
TCP 流接受器 | ACE_INET_Addr | ACE_SOCK_STREAM | ACE_SOCK_ACCEPTOR |
UNIX 域本地流socket接受器 | ACE_UNIX_Addr | ACE_LSOCK_STREAM | ACE_LSOCK_ACCEPTOR |
管道作为底层通信机制 | ACE_SPIPE_Addr | ACE_SPIPE_STREAM | ACE_SPIPE_ACCEPTOR |
表
7-1 ACE中的连接建立机制连接器
连接器与接受器非常类似。它也是一个工厂,但却是用于主动地连接远程主机。在连接建立后,它将自动回调适当的服务处理对象的
open()方法。连接器通常被用在你本来会使用BSD connect()调用的地方。在ACE中,连接器,就如同接受器,被实现为名为ACE_Connector的模板容器类。如先前所提到的,它需要两个参数,第一个是事件处理器类,它在连接建立时被调用;第二个是“具体的”连接器类。你必须注意,底层的具体连接器和
ACE_Connector工厂是非常不一样的。ACE_Connector工厂使用底层的具体连接器来建立连接。随后ACE_Connector工厂使用适当的事件或服务处理例程(通过模板参数传入)来在具体的连接器建立起连接之后处理新连接。如我们在IPC一章中看到的,没有ACE_Connector工厂,也可以使用这个具体的连接器。但是,没有具体的连接器类,就会无法使用ACE_Connector工厂(因为要由具体的连接器类来实际处理连接建立)。下面是对
ACE_Connector类进行实例化的一个例子:
typedef ACE_Connector
这个例子中的第二个参数是具体连接器类
ACE_SOCK_CONNECTOR。连接器和接受器模式一样,在内部使用反应堆来在连接建立后回调服务处理器的open()方法。我们可以复用我们为前面的接受器例子所写的服务处理例程。一个使用连接器的例子可以进一步说明这一点:
例
7-4typedef ACE_Connector
int main(int argc, char * argv[])
{
ACE_INET_Addr addr(PORT_NO,HOSTNAME);
My_Svc_Handler * handler= new My_Svc_Handler;
//Create the connector
MyConnector connector;
//Connects to remote machine
if(connector.connect(handler,addr)==-1)
ACE_ERROR(LM_ERROR,”%P|%t, %p”,”Connection failed”);
//Registers with the Reactor
while(1)
ACE_Reactor::instance()->handle_events();
}
在上面的例子中,
HOSTNAME和PORT_NO是我们希望主动连接到的机器和端口。在实例化连接器之后,我们调用它的连接方法,将服务例程(会在连接完全建立后被回调),以及我们希望连接到的地址传递给它。
