Introduction
Asynchronous IO under ISAPI is often viewed as something of a black art. But it's actually quite straight forward and it will yield the highest level of performance and scalability for your application.
A Project to Demonstrate Asynchronous IO
Browsing the Code Project site, I stumbled across an example ISAPI project by Jorge Lodos Vigil which demonstrated sending back a page of HTML and an image. Two different types of data are returned by Jorges' ISAPI. The page is contained in an in-memory buffer while image is contained in a file on disk. Sending a buffer of data to a client and sending a file to a client just happens to encompass the two possible ways to send data asynchronously using the ISAPI API - WriteClient
and TransmitFile
.
ImageServer Project
The ImageServer
ISAPI extension essentially brokers requests for images from some hidden directory. ImageServer
satisfies those requests by returning a page of HTML containing an IMG
element that has, as its source, the ImageServer
extension. The source URL is specially formatted to let ImageServer
know that it should send back the actual image instead of a containing page.
Two Ways to Perform Asynchronous IO
Within the context of an ISAPI module, there are two ways to receive or send data asynchronously. You can use ReadClient
or WriteClient
to read/write an arbitrary buffer of data to the client asynchronously or you can use TransmitFile
to send a file to a client asynchronously. TransmitFile
is a Win32 function which is accessed by using the ISAPI function ServerSupportFunction
and specifying the HSE_REQ_TRANSMIT_FILE
option.
The MSDN online library provides a good overview of asynchronous IO but in general, you will need to perform these steps:
- Setup a callback through which you will be notified when an IO completion event occurs.
- Start the asynchronous IO.
- Return
HSE_STATUS_PENDING
from the HttpExtensionProc
. - When IO completes, cleanup any context information and notify IIS you're done with this session.
There's really only one other thing to keep in mind about asynchronous IO. That is, whatever data is being sent to the client must survive until a completion notification is received. Which means, it's almost always going to be placed on the heap.
This last requirement usually entails the most grief from a design standpoint. The extreme levels of performance and reliability required from ISAPI extensions precludes a design that constantly accesses the heap. Limiting access to the heap is often accomplished by implementing pools of buffer objects that are re-used.
ImageServer
s requirements are not as stringent. When ImageServer
needs a buffer, or transmission context, it simply creates it on the heap. Likewise, when it's finished with a buffer, it just destroys it. Although creating a protected buffer pool is not difficult, it would distract from the central purpose of ImageServer
- which is to demonstrate basic asynchronous IO.
Tour of the Code
The code is simple and heavily commented so it should be easy to understand. Here are the highlights of what you'll find.
- There is one main class
CImageServer
which encapsulates all the functionality required for this application. This class is declared in Server.h and implemented in Server.cpp. - A single static instance of
CImageServer
resides in ImageServer.cpp. Also in this file are the main entry points for the ISAPI DLL - DllMain
, GetExtensionVersion
, TerminateExtension
and HttpExtensionProc
. They are all basic boiler plate code except HttpExtensionProc
which delegates its behavior CImageServer::ServiceRequest
- There are two other classes which serve as transmission contexts for the asynchronous IO calls. The first is
CFilePacket
which is a context for TransmitFile
. The second is tWriteClientPacket
, which is actually a structure not a class. It serves as a context for, you guessed it, WriteClient
. Both CFilePacket
and tWriteClientPacket
are declared as nested classes/structures inside the declaration of CImageServer
. There was no real need for this but it makes the code a little cleaner as those classes are only used inside the functions of CImageServer
.
Understanding how CImageServer
works is best accomplished by reading the source. Everything starts with CImageServer::ServiceRequest
and the comments will guide you from there.
Setup
As with any ISAPI module, you'll have to locate the DLL in some path your web server has access too - with execute permissions. You'll also need a directory containing images that matches whatever you've hard coded into CImageServer::GetImageDirectory
.
About the Author
The company I work for, Financial Insight Systems Inc., specializes in creating high performance internet applications using Microsoft Back Office products. I have a MCSD, with certifications in Visual C++ and SQL Server, and for the last five years, most of my time has been spent developing ISAPI modules and Winsock based client/server solutions. Suggestions and comments are welcome.
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.