From the April 2002 issue of MSDN Magazine
Using ATL Server to Build an Asynchronous SOAP Client in Unmanaged C++
|Pranish Kumar and Bogdan Crivat|
|This article assumes you're familiar with ATL, C++, and Web Services|
|Level of Difficulty 1 2 3 |
|Download the code for this article: SOAP.exe (51KB) |
|SUMMARY SOAP opens up a new world of Web Services, letting you make function calls across a network or the Internet. But this flexibility creates new problems when your app needs to wait for calls to return from halfway around the world. What you need is an asynchronous SOAP client that takes advantage of threading to continue execution while waiting for calls over the wire. This article covers the basics of building such a client with ATL.
|TL Server, which was designed to allow developers of unmanaged C++ code to create Web applications and Web Services, is an extension of the ATL MFC unmanaged C++ libraries. The samples provided with ATL Server (a part of Visual C++® .NET) already demonstrate how to build Web Services with ATL Server, including one sample that shows how to build an asynchronous SOAP (Web Service) Server with ATL Server. (See the PooledAsync sample in the Samples\C++\ATLMFC\ATL Server\PooledAsync directory.) Many people still want to know, however, how they can create an asynchronous SOAP client. In this article we explain how you can do exactly that with ATL Server. This is not an introduction to Web Services or SOAP, or even ATL Server Web Services. Therefore, we suggest that you already be familiar with these topics before continuing with this article.|
This article assumes that the SOAP communication uses HTTP as the underlying transport protocol (the most common SOAP binding). However, there is nothing in the sample code (which you can find at the link at the top of this article) that will prevent it from being used with other transports.
The Need for Asynchronous Web Service Calls Being able to use the Web to make programmatic calls is a very powerful technique for application developers. Unfortunately, critical problems can occur when you don't know how long it will take for the Web Service call to return. Sometimes you may actually know that the Web Service call will take a long time to complete. In these cases asynchronous Web Service calls can be very important.
Having an application block on a Web Service call for a significant period of time may result in the application becoming unresponsive, background processing being delayed, or real-time requirements not being met. The solution is to not block on the call, but to continue processing until the Web Service call returns from the remote destination.
The obvious solution to this problem is to make use of threads. All the active threads of your application can continue processing while only the single thread making the Web Service call blocks. While this is the heart of the solution, there is another problem.
In a real-world application it is often not possible to predict how many concurrent asynchronous calls will be required. Combine this issue with the overhead of creating and destroying threads, and the fact that there is a limit to the number of threads your application can have at any given time, and the simple solution of spinning off a thread for every asynchronous request does not seem plausible, does it?
ATL Server provides a very flexible thread pool implementation based on I/O completion ports. It is the same thread pool used in handling the HTTP requests on the server. We will use this thread pool to solve the thread overhead problem just described.
Processing the SOAP Request Let's now take a look at the specifics of a SOAP request. On the client side, a method invocation consists of an HTTP request/response pair of messages. The request message contains the parameters for the invocation (all the [in] parameters of the SOAP method) while the response message contains the result of the invocation (all the [out] parameters of the SOAP method). Hybrid parameters ([in, out]) are basically a pair consisting of an [in] parameter and an [out] parameter.
ATL Server provides a command-line utility—sproxy.exe—that, given a Web Service description in the Web Service Description Language (WSDL) format, will generate a proxy class. This class exposes all the methods for the specified Web Service, taking care of the SOAP invocation. For detailed information on the sproxy.exe tool and the process of creating Web Services clients in unmanaged C++, please see the Visual Studio® .NET help topics "SPROXY.EXE: XML Web Service Proxy Generator" at and "Creating XML Web Service Clients". (Visual Studio .NET must be installed to view these links.)
By looking inside the client header file generated by sproxy you will notice the following important calls, in this order:
The GenerateResponse and the BeginParse calls use the maps generated by sproxy which are specific to the method being invoked. Because these maps are specific, they cannot be separated from the method invocation.
- GenerateResponse generates the SOAP request XML message using the [in] or [in, out] parameters.
- SendRequest sends the SOAP request message and waits for the SOAP response message.
- BeginParse parses the SOAP response message and copies the significant data into the local [out] or [in, out] function parameters.
Figure 1 shows the current execution of a SOAP request. In Figure 1, the vertical lines below each call represent the call's scope as we wait for the HTTP operation that we have initiated to complete. At the highest level is the SoapMethodCall, which contains the entire SOAP request.
Figure 1 Waiting
We begin by generating the response and then sending the actual SOAP request. You can see that the operation blocks in WaitForResponse after actually sending the HTTP request as we wait for the response. After an unknown period of time, the response is received and we read it. If it is OK then we return S_OK (success), otherwise the appropriate error return value goes back.
Of all these calls, SendRequest is the most time consuming, so this is the one that is to be treated asynchronously. Fortunately for us, the SendRequest invocation calls into the SOAP Client archetype. The SOAP Client archetype is the specification for the classes that can be used to send SOAP messages to a server when using sproxy-generated proxies. The class matching this specification is denoted as TClient in the file generated by sproxy.exe. (For detailed information on the archetype, please see the Visual Studio .NET help topic "SOAP Client Archetype".) (Visual Studio .NET must be installed to view this link.)
The proxy-generated method invocation is agnostic of the actual implementation of SendRequest. It calls SendRequest once a request is ready to be sent. Then, if SendRequest returns S_OK (access), it parses the response.
Since TClient is the template parameter for the sproxy-generated class, it means that we have control over its implementation of the class. Therefore, the asynchronous process of sending the request and receiving the response will be handled by a new class which will implement the SOAP Client archetype for the proxy-generated class.
The Non-blocking Execution Model We want the new execution model for a SOAP invocation to allow the main thread to continue processing while the HTTP request is sent and the application waits for the HTTP response. This means we want the SOAP invocation to somehow store the request and then return immediately, without waiting for the request to complete. More specifically, we want the return code to notify the caller thread that the call is in progress and we want the worker thread to use the stored request and perform the HTTP request/response. Once this is done, we need the main thread to be notified about the completion of the transport operation as well as about the result of the operation (success or failure). After the main thread is notified, it also needs a way to collect the response ([out] parameters).
To maintain the separation between the transport and the SOAP implementation, the data passed between the two threads will not need further processing. The main thread will wrap the [in] parameters in a SOAP payload, pass that payload to the worker thread, and get back a response payload once the transmission is complete. Therefore, we will need the sproxy-generated code to generate the request body and, at the end, to parse the response and get the [out] parameters.
This is why an asynchronous invocation will look (in the code) like two synchronous invocations. The first one will generate the request payload and post it to the worker thread, and the second will collect the response and fill the [out] parameters.
In Figure 2 you can see how the new execution model operates. First, we break off our worker thread (which is created in the thread pool) from our main thread. Previously we saw that in the default ATL Server model the main thread gets blocked as we wait for a response from the service.
Figure 2 Non-blocking Execution Model
You can see that our execution model in the main thread has been altered. There are now two SoapMethodCalls. The first generates and sends the actual HTTP request, and the second is used to collect the returned information.
So in this execution model, we generate the request and then make the SendRequest call, which this time calls into our worker thread and returns immediately, thus allowing the main thread to continue execution.
The worker thread receives our request and completes the activities that would previously have been conducted in the main thread. Namely, it sends the request, waits on the response, and generates an appropriate return code (success or failure). Once this has completed, the worker thread completes its only unique operation (an operation that didn't have a counterpart in the default scenario where we made the request from the main thread). It calls back to the main thread to indicate that the request has been successfully completed.
The main thread can now (when it is ready) make another SoapMethodCall. In this case the GenerateRequest should do nothing (we've already completed the request), and the SendRequest will simply fetch the results from our Web Service call.
Execution on the main thread can now continue as it did previously. At this point, you would usually continue by examining and processing the results from the Web Service. As you can now see, the waiting time is spent in the working thread only, so the main thread is no longer blocked.
The Sample Code In this section we'll explain some of our implementation details so that you have a better feel for how all these pieces fit together.
The thread pool class, CThreadPool, is a template that depends on a worker class. The worker class is supposed to handle a job execution. An example of such a worker class is CAsyncSoapWorker in AsyncSocketClient.h (in the download for this article). Its Execute method is invoked by the thread pool whenever a new job is picked out of the job queue.
If you look at the AsyncClient.cpp file you will see how we are making use of the thread pool right away. Our first initialization in _tmain is to initialize the thread pool to four threads per CPU.
Now in order to make our request asynchronous we need a mechanism to place our Web Service call into this thread pool and not worry about it until we get a call back. To do this we need to subclass CSoapSocketClientT to make a new CAsyncSoapSocketClient, which can be seen in AsyncSocketClient.h.
The way our system will work is as follows:
It is clear that in both the second and final steps the client is making a call to the Web Service, and so the question arises, why do we need to make a call to the Web Service twice? In fact the call to the server is only made once, but the client needs to make the call twice because of the way our asynchronous call is implemented, as we will explain next.
- The client prepares the proxy for an async SOAP call, specifies the thread pool to be used, and registers its callback function.
- The client makes an asynchronous Web Service request.
- The request is handled by a free thread in the thread pool. In this thread, m_bInRequest has been initialized to false. (Thus we know that we have not yet sent the request.)
- We send the request and immediately return E_PENDING to the client so that it can continue processing. We set m_bInRequest to true so that we know we've sent the request. The client processing can continue without waiting for the response.
- When the request completes and we get our response, the registered callback is called and the free thread is returned to the pool.
- The client processes the response.
The first invocation of the SOAP method takes place in HelloWorld. The sproxy-generated class fills the socket client's request buffer with the XML payload of the request. Then it attempts to send that buffer through the socket client's SendRequest method.
As you can see from the code generated by sproxy, the parsing of the response happens only if SendRequest succeeds. Therefore, a SendRequest implementation returning an error code will prevent the parsing of the response payload.
Of course, when we return from this call (E_PENDING) we have not actually made the Web Service call. Therefore we have no valid data into the response buffer, so the output parameters of the call will not be filled with anything. This is why the [out] parameters of the SOAP method are ignored in the first invocation, so they can be NULL. It is not until our second call (described next) that we get valid data in our response buffer.
Now let's look at the second invocation of the SOAP method. Just as in the first case, the sproxy-generated class fills a request buffer with the XML payload of the request. When attempting to use SendRequest, the response buffer of the socket client object is already filled with the response from the first call, so no second HTTP request is made. This is why the [in] parameters of the SOAP request will be ignored in the second invocation, and this is why they can be NULL.
If there were any errors in making the Web Service call we would discover these on our second call to the Web Service function. It is from the second call that we get a proper return value (rather than just an E_PENDING) and thus it is then that we would learn if any errors occurred. Naturally, it is up to the client code to handle a failed request gracefully.
The Callback Function The callback function is defined as follows. It contains LPARAM—a general-use parameter. It can be specified by the main thread when PrepareAsync is invoked, and will be transmitted to the callback when the request is complete. It can be just an event object (to be signaled for the main thread) or a more complex structure, depending on the application's needs.
The HRESULT parameter is the result of the HTTP invocation. The main thread can use this and avoid looking for return values when the HTTP invocation failed.
typedef void (*SOAP_ASYNC_CALLBACK)(LPARAM lParam, HRESULT hRet);
So far we've described what happens with a single asynchronous SOAP client instance. The sample code, however, shows how an arbitrary number of instances of SOAP clients can use the same service at the same time.
Figure 3 shows what happens in the sample application. An array of proxy classes is defined as
and the array is represented in the diagram as the set of three-color rectangles. Each of the three colors represents a phase in the use of an asynchronous SOAP proxy client.
The blue boxes represent the initialization phase. The initialization phase is started by calling PrepareAsyncRequest in the code and continued by making the first invocation of HelloWorld for each client. The invocation should return E_PENDING, meaning the asynchronous processing is launched and a result is expected.
The green boxes represent the saved response from the server. Once the processing is complete (the completion counter reaches CLIENTS_COUNT), the main thread invokes HelloWorld again to fetch these results. After that, the main thread can use the returned results (in this case, by displaying them using the printf call at the bottom of the diagram).
We decided that it would be an interesting experiment to compare the performance of an asynchronous set of clients with the performance of successive calls on a single client. In order to perform the test, we implemented two small applications. The first application, Sync, uses a single client and it was generated by sproxy.exe (a synchronous client) and executes eight calls to the same SOAP method on the same server. The second, Async, is basically the same application as used for this article.
The applications use the same SOAP server with the ISAPI DLL tweaked to contain four processing threads per CPU. In both of these applications, the time between starting the invocations and getting the results was measured (using the SDK function GetTickCount). Here are the results:
In this particular situation, with no other client affecting the server's performance, the asynchronous application performed much better than the synchronous one. The performance difference (around one quarter of the synchronous version) is easily explainable in terms of scalability. As long as the ISAPI application can handle four requests in the same time (four processing threads), the overall execution time for the asynchronous clients should be one quarter of the execution time for sequential calls.
- Sync—single client, eight successive calls: 8047
- Async—eight asynchronous clients: 2234
Again, this is only an experimental result showing the effect of an asynchronous implementation on the client performance. When the server is used by multiple clients, or the number of clients on the client side is larger, the performance ratio will, of course, be adversely affected.
Conclusion By exploiting the flexibility of the ATL Server model we are enabling our application to make asynchronous requests to a server with relatively little effort. You can easily utilize the sample code, and more importantly the asynchronous architecture, in your own applications to make use of Web Services in an asynchronous fashion. It is important to remember that the server has no knowledge that the client is making an asynchronous request. (Similarly, in the asynchronous server sample included on the Visual Studio .NET CD, the client has no knowledge that the server is processing requests asynchronously.) Thus the clean separation of client and server is preserved. By using asynchronous Web Service calls your application can now take advantage of the power of Web Services without your users even realizing it.
| For related articles see:|
ATL Server and Visual Studio.NET: Developing High Performance Web Applications Gets Easier
SOAPing without a Web Server
XML Web Services Basics
For background information see:
Simple Object Access Protocol (SOAP) 1.1
| Pranish Kumar works on the ATLMFC team at Microsoft, predominantly on ATL Server. Pranish has programmed with C++ for a number of years including work on Windows, Unix, and embedded systems. Pranish graduated with degrees in Electrical Engineering and Computer Science from the University of New South Wales, Australia.|
Bogdan Crivat works on the ATL Server team at Microsoft. His has a BS in Mathematics from the University of Bucharest. He's been using Visual C++ and ATLMFC for five years.