The receiver/connector mode is designed to reduce the coupling between connection establishment and service execution after the connection is established. For example, in the WWW browser, the service or "actual work" performed is to parse and display the HTML page received by the client's browser. Connection establishment is secondary and may be accomplished through BSD sockets or some other equivalent IPC mechanism. Using these patterns allows programmers to focus on "real work" and at least to care about how to establish a connection between the server and the customer. On the other hand, the programmer can also tune the strategy of the connection setup independently of the service routines he or she is writing.
Because this mode reduces the coupling between service and connection building methods, it is very easy to change one without affecting the other, so that you can reuse the code of the previously written connection establishment mechanism and service routines. In the same example, a browser programmer using these patterns can initially construct his system, use a specific connection-building mechanism to run it and test it, and then, if the previous connection mechanism proves unsuitable for the system he is constructing, He can decide that he wants to change the underlying connection mechanism to multi-threaded (perhaps using a thread pool policy). Because this pattern provides strict decoupling, this change can be achieved with minimal effort.
Before you can clearly understand many of the examples in this chapter, especially the more advanced ones, you must read through the chapters on reactors and IPC_SAP (especially the receiver and connector sections). In addition, you may also need to refer to the thread and thread Management section.
7.1 Receiver Mode
The receiver is usually used where you would have used the BSD accept () system call. The receiver mode also applies to the same environment, but as we will see, it provides more functionality. In Aces, the receiver pattern is implemented with a "factory" (Factory) named Ace_acceptor. A factory (typically) is a class that is used to abstract the instantiation process of an helper object. In object-oriented design, complex classes often delegate specific functionality to helper classes. Complex class-to-helper creation and delegation must be flexible. This flexibility was obtained with the help of the factory. The factory allows an object to change its underlying strategy by altering the object it is delegating, and the interface that the factory provides to the application is the same, so that there may be no need to change the customer code (for more information about the factory, read the references in design mode).
Fig. 7-1 schematic diagram of Factory mode
The Ace_acceptor factory allows application developers to change the "helper" object for use when a passive connection establishes a connection-based processing
Similarly, the Ace_connector factory allows application developers to change "helper" objects for:
The active connection establishes the processing after the connection is established
The following discussion applies to both the receiver and the connector, so the author will only discuss the receiver, and the connector has the corresponding parameters.
Ace_acceptor is implemented as a template container, instantiated by two classes as arguments. The first parameter implements a specific service (type Ace_event_handler. Because it is used to handle I/O events, it must come from the event-handling class hierarchy, the application executes the service after the connection is established, and the second parameter is the "specific" receiver (which can be a variety of variants discussed in the IPC_SAP chapter).
It is particularly important to note that the Ace_acceptor factory and the specific receiver used at the bottom are very different. The specific receiver can be used entirely independently of the Ace_acceptor factory, without involving the receiver pattern we are discussing here (independent use of the receiver has been discussed and demonstrated in the Ipc_sap chapter). The Ace_acceptor factory is in the receiver mode and cannot be used without the underlying specific receiver. The Ace_acceptor uses the underlying specific receiver to establish the connection. As we have seen, classes with several aces can be used as the second parameter of the Ace_acceptor factory template (that is, the specific receiver Class). However, the service processing class must be implemented by the application developer, and its type must be Ace_event_handler. The Ace_acceptor factory can be instantiated like this:
typedef ace_acceptor<my_service_handler,ace_sock_acceptor> Myacceptor;
Here, the event handler named My_service_handler and the specific receiver Ace_sock_acceptor are passed to Myacceptor. The ace_sock_acceptor is a TCP receiver based on the BSD socket family (various types of receiver that can be passed to the receiver factory, see table 7-1 and IPC chapter). Again, when using the receiver mode, we always process two receiver: the factory receiver named Ace_acceptor, and a specific receiver in the ACE, such as Ace_sock_acceptor (you can create a custom specific receiver to replace the Ace_sock_ Acceptor, but you will probably not have to change anything in the Ace_acceptor factory class.
Important Notes
: Ace_sock_acceptor is actually a macro, which is defined as:
#define Ace_sock_acceptor Ace_sock_acceptor, ace_inet_addr
We think that the use of this macro is necessary because the typedefs in the class does not work on some platforms. If this is not the case, Ace_acceptor can instantiate this:
typedef ace_acceptor<my_service_handler,ace_sock_acceptor>myacceptor;
The macros are described in table 7-1.
7.1.1 Components
As explained in the above discussion, there are three main participating classes in the receiver mode:
Specific receiver
: It contains a specific policy for establishing a connection, and the connection is tied to the underlying transport protocol mechanism. The following are examples of the various specific types of receiver in Aces: Ace_sock_acceptor (using TCP to establish a connection), Ace_lsock_acceptor (using a UNIX domain socket to establish a connection), and so on. Specific service processor
: Written by the application developer, its open () method is automatically recalled after the connection is established. The receiver architecture assumes that the type of the service processing class is Ace_event_handler, which is the interface class defined by the ACE (which has been discussed in detail in the reactor chapter). Another class created specifically for the service processing of the receiver and connector patterns is ace_svc_handler. This class is based not only on the Ace_event_handler interface (which is required for the use of reactors), but also on the Ace_task classes used in the ASX flow architecture. The Ace_task class provides the ability to create detached threads, use message queues to store incoming data messages, process them concurrently, and other useful features. If the specific service processor used with the receiver pattern is derived from Ace_svc_handler instead of Ace_event_handler, it can gain these additional capabilities. The use of additional features in Ace_svc_handler is discussed in detail in this chapter of the Advanced course. In the following discussion, we will use Ace_svc_handler as our event handler. The important difference between the simple Ace_event_handler and the Ace_svc_handler class is that the latter has an underlying communication flow component. This stream is set when the Ace_svc_handler template is instantiated. In the case of using Ace_event_handler, we have to increase the I/O communication endpoint (that is, the stream object) ourselves as a private data member of the event handler. Thus, in such a case, the application developer should create his service processor as a subclass of the Ace_svc_handler class, and first implement the Open () method that will be automatically callback by the schema. Also, because Ace_svc_handler is a template, the communication flow component and locking mechanism are passed in as template parameters. Reactors
: Used in conjunction with Ace_acceptor. As we will see, after instantiating the receiver, we start the reactor's event processing cycle. The reactor, as previously explained, is an event dispatch class; In this case, it is used by the receiver to dispatch connection-building events to the appropriate service-handling routines.
7.1.2 Usage
By observing a simple example, you can learn more about the receiver. This example is a simple application that uses the receiver to accept the connection and then callbacks the service routine. When the service routine is called back, it allows the user to know that the connection has been successfully established.
Example 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 is called back
automatically. This
Class must derive from Ace_svc_handler which are an
interface and as can 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
would do nothing.
Class My_svc_handler:
Public Ace_svc_handler <ACE_SOCK_STREAM,ACE_NULL_SYNCH>
{
The open method which is called back automatically after the
Connection has been
Established.
Public
int open (void*)
{
cout<< "Connection established" <<endl;
}
};
Create the acceptor as described above.
typedef ace_acceptor<my_svc_handler,ace_sock_acceptor> Myacceptor;
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 would automatically take the
Host ' s IP address as the IP
Address for the Addr object
Ace_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 ();
}
In the example above, we first create the endpoint address where we want to accept the connection. Because we decided to use TCP/IP as the underlying connection protocol, we created a ace_inet_addr to serve as our endpoint and passed the port number we were listening to. We pass this address and the example of the reactor monomer to the receiver that we want to instantiate afterwards. After the receiver is instantiated, any connection requests on the Port_num port are automatically accepted, and the open () method of My_svc_handler is called back after the connection is established for such a request. Note that when we instantiate the Ace_acceptor factory, we pass it to the specific receiver (that is, ace_sock_acceptor) and the specific service processor (that is, my_svc_handler) that we want to use.
Now let's try something more interesting. In the next example, we will register the service processor back to the reactor after the connection request arrives, and the service processor is recalled. Now, if any data arrives on the newly created connection, our service processing routines
The Handle_input () method will be automatically recalled. Thus, in this case, we are using the characteristics of both the reactor and the receiver:
Example 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<my_svc_handler,ace_sock_acceptor>
Myacceptor;
Create a service handler similar to as seen in Example 1. Except this
Time include the Handle_input () method which is called back
Automatically by the reactor if new data arrives on the newly
Established connection
Class My_svc_handler:
Public Ace_svc_handler <ACE_SOCK_STREAM,ACE_NULL_SYNCH>
{
Public
My_svc_handler ()
{
Data= New Char[data_size];
}
int open (void*)
{
cout<< "Connection established" <<endl;
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 have been received.
This data was stored in the data array and then printed out
Peer (). Recv_n (Data,data_size);
Ace_os::p rintf ("<<%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 ();
}
The only difference between this example and the previous example is that we register the service processor on the reactor in the service processor's open () method. Therefore, we must write the Handle_input () method, which will be recalled by the reactor when the data arrives on the connection. In this case we just print the data we received to the screen. The peer () method of the Ace_svc_handler class returns a reference to the underlying peer stream. We use the Recv_n () method of the underlying stream wrapper class to get the data received on the connection.
The real power of this pattern is that the underlying connection-building mechanism is completely encapsulated in a specific receiver, making it easy to change. In the next example, we change the underlying connection-building mechanism so that it uses
The UNIX domain socket, not the TCP socket. This example (the underscore indicates a small change) is as follows:
Example 7-3
Class My_svc_handler:
Public Ace_svc_handler <ace_lsock_stream,ace_null_synch>{
Public
int open (void*)
{
cout<< "Connection established" <<endl;
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::p rintf ("<<%s\n", data);
return 0;
}
};
typedef ace_acceptor<my_svc_handler,ace_lsock_acceptor> Myacceptor;
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 ();
}
Cases
7-2 and Example