ACE provides a set of macros that consolidate the printing of debug-ging and error messages via a f -like format. The most common
macros are ACE_DEBUG, and which
encapsulate the () method. This method takes a variable number of arguments, so the first argument to all these macros is actually a compound argument wrapped in an extra set of parenthe-ses to make it appear as one argument to the C++ preprocessor,
The first argument to the : log method is the sever-ity code, for example, for general errors and for di-agnostic information. The next parameter is a format string that accepts most printf () conversion specifiers. ACE defines some additional for-mat specifiers that are useful for tracing and logging operations, includ-ing
Displays the line number where the error occurred Displays the filename where the error occurred Displays the name of the program
Displays the current process ID
Takes a const char * argument and displays it and the error string corresponding to errno perror
Displays the current time Displays the calling thread's ID
The ACE_ERROR_RETURN macro is a shortcut for logging an error mes-sage and returning a value from the current function. Hence, it takes two arguments: the first is the same as the other macros and the second is the value to return from the function after logging the message.
We can reuse the inherited wait_for_multiple_events method since we're implementing an iterative server. Thus, the
method simply blocks until it can accept the next client connection.
94 CHAPTER 4 Implementing the Networked Logging Service
virtual int () {
ACE_INET_Addr if (acceptor
== -1) ACE_ERROR_RETURN
ACE_DEBUG "Accepted connection from return
After the server accepts a new connection from a client, its handle_
data hook method continues to receive log records from that client until the connection closes or a failure occurs.
virtual int handle_data (ACE_SOCK_Stream *) {
while () -1)
// Close the socket handle.
return }
The function instantiates an Iterative_Logging_Server ob-ject and calls its run method to process all client requests iteratively.
This code is in file . cpp.
#include
#include
int main (int argc, char {
if (argc, argv) == -1) ACE_ERROR_RETURN
return
This example illustrates how much easier it is to program networked applications using ACE instead of the native C Socket API calls. All the Socket API's accidental complexities in Section 2.3 have been re-moved via the use of ACE wrapper facade classes. With this complexity removed, application developers are freed to focus on strategic application logic. Note that most of the source code we've shown is concerned with application-specific and I/O code in the
Section The Application 95
methods shown in Section and none of the "traditional" socket ma-nipulation, buffer management, or byte ordering code found in applications programmed directly to the Socket API.
Despite its many improvements, however, our first logging server's im-plementation is limited by its iterative server design. Subsequent examples in this book illustrate how concurrency patterns and ACE wrapper facades help to overcome these problems. Since the logging server decouples the and roles, we can modify its imple-mentation details without changing its overall design.
4.5 The Client Application
The following client application illustrates how to use the ACE wrapper facades to establish connections, marshal log records, and send the data to our logging server. This example reads lines from standard input, sends each line to the logging server in a separate log record, and stops when it reads EOF from standard input. We first include the ACE header files needed for the classes this code uses. It's a good idea to specifically include all ACE headers corresponding to the classes used. For example, ace/OS . h
includes a definition of which we use as the
default host name.
We include ace/streams which contains the conditional code to make the use of C++ iostreams more portable. Based on configuration settings in ACE, it includes the proper form of header file (e.g., <iostream> vs.
If the iostreams are supplied in the std namespace, the . h file brings the classes used into the global namespace.
We start by defining a class whose method
transmits one over a connected This
class also implements a helper method that returns a reference to its ACE_
96 CHAPTER 4 Implementing the Networked Logging Service
SOCK_Stream instance and a destructor that closes the underlying socket handle.
class Logging Client {
// Send <log_record> to the server.
int send (const // Accessor method.
() { return }
// Close the connection to the server.
~Logging_Client () { }
ACE_SOCK_Stream // Connected to server.
The : send method implements the sender side of the message framing mechanism described in Sidebar 9 on page 86. This method is portable and interoperable since it uses the insertion operator on page 79 to marshal the contents of the log record
into an stream.
4.5 The Client Application 97
23
2 4 return (iov,
25 }
Lines We allocate enough space for a complete ACE_Log_Record, insert the contents of log_record into the payload CDR stream, and get the number of bytes used by the stream.
Lines We next create a CDR-encoded header so the receiver can determine the byte order and size of the incoming CDR stream.
Lines We use an iovec to send the header and payload CDR streams using a single sendv_n method call.
Finally, we show the client application's main function.
1 int main (int argc, char 2 {
11 if (logger_port != 0) 12 result =
31 // Limit the number of characters read on each record.
32
98 CHAPTER 4 Implementing the Networked Logging Service
40 ACE_Log_Record log_record now, 41
49 return 0; // destructor closes TCP connection.
50 }
Lines If the TCP port and host name are passed as command-line arguments, we use them to initialize the network endpoint where the log-ging server listens passively to accept connections. Otherwise, we use the default settings.