< WSAEventSelect Program Example | Winsock2 I/O Method Main | Overlapped & AcceptEx() Example >
What do we have in this chapter 5 part 6?
|
The Overlapped Model
The overlapped I/O model in Winsock offers applications better system performance than any of the I/O models explained so far. The overlapped model's basic design allows your application to post one or more asynchronous I/O requests at a time using an overlapped data structure. At a later point, the application can service the submitted requests after they have completed. This model is available on all Windows platforms except Windows CE. The model's overall design is based on the Windows overlapped I/O mechanisms available for performing I/O operations on devices using the ReadFile() and WriteFile() functions. Originally, the Winsock overlapped I/O model was available only to Winsock 1.1 applications running on Windows NT. Applications could take advantage of the model by calling ReadFile() and WriteFile() on a socket handle and specifying an overlapped structure. Since the release of Winsock 2, overlapped I/O has been incorporated into new Winsock functions, such as WSASend() and WSARecv(). As a result, the overlapped I/O model is now available on all Windows platforms that feature Winsock 2. With the release of Winsock 2, overlapped I/O can still be used with the functions ReadFile() and WriteFile() under Windows NT and Windows 2000. However, this functionality was not available for Windows 95, Windows 98, and Windows Me. For compatibility across platforms, you should always consider using the WSARecv() and WSASend() functions instead of the Windows ReadFile() and WriteFile() functions. This section will only describe how to use overlapped I/O through the Winsock 2 functions. To use the overlapped I/O model on a socket, you must first create a socket that has the overlapped flag set. After you successfully create a socket and bind it to a local interface, overlapped I/O operations can commence by calling the Winsock functions listed below and specifying an optional WSAOVERLAPPED structure.
|
To use overlapped I/O, each function takes a WSAOVERLAPPED structure as a parameter. When these functions are called with a WSAOVERLAPPED structure, they complete immediately, regardless of the socket's mode (described at the beginning of this chapter). They rely on the WSAOVERLAPPED structure to manage the completion of an I/O request. There are essentially two methods for managing the completion of an overlapped I/O request: your application can wait for event object notification or it can process completed requests through completion routines. The first six functions in the list have another parameter in common: a WSAOVERLAPPED_COMPLETION_ROUTINE. This parameter is an optional pointer to a completion routine function that gets called when an overlapped request completes. We will explore the event notification method next. Later in this chapter, you will learn how to use optional completion routines instead of events to process completed overlapped requests.
The event notification method of overlapped I/O requires associating Windows event objects with WSAOVERLAPPED structures. When I/O calls such as WSASend() and WSARecv() are made using a WSAOVERLAPPED structure, they return immediately. Typically, you will find that these I/O calls fail with the return value SOCKET_ERROR and that WSAGetLastError() reports a WSA_IO_PENDING error status. This error status simply means that the I/O operation is in progress. At a later time, your application will need to determine when an overlapped I/O request completes by waiting on the event object associated with the WSAOVERLAPPED structure. The WSAOVERLAPPED structure provides the communication medium between the initiation of an overlapped I/O request and its subsequent completion, and is defined as:
typedef struct WSAOVERLAPPED
{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent;
} WSAOVERLAPPED, FAR * LPWSAOVERLAPPED;
The Internal, InternalHigh, Offset, and OffsetHigh fields are all used internally by the system and an application should not manipulate or directly use them. The hEvent field, on the other hand, allows an application to associate an event object handle with this operation.
When an overlapped I/O request finally completes, your application is responsible for retrieving the overlapped results. In the event notification method, Winsock will change the event-signaling state of an event object that is associated with a WSAOVERLAPPED structure from non-signaled to signaled when an overlapped request finally completes. Because an event object is assigned to the WSAOVERLAPPED structure, you can easily determine when an overlapped I/O call completes by calling the WSAWaitForMultipleEvents() function, which we also described in the WSAEventSelect() I/O model. WSAWaitForMultipleEvents() waits a specified amount of time for one or more event objects to become signaled. We can't stress this point enough: remember that WSAWaitForMultipleEvents() is capable of waiting on only 64 event objects at a time. Once you determine which overlapped request has completed, you need to determine the success or failure of the overlapped call by calling WSAGetOverlappedResult(), which is defined as:
BOOL WSAGetOverlappedResult(
SOCKET s,
LPWSAOVERLAPPED lpOverlapped,
LPDWORD lpcbTransfer,
BOOL fWait,
LPDWORD lpdwFlags
);
The s parameter identifies the socket that was specified when the overlapped operation was started. The lpOverlapped parameter is a pointer to the WSAOVERLAPPED structure that was specified when the overlapped operation was started. The lpcbTransfer parameter is a pointer to a DWORD variable that receives the number of bytes that were actually transferred by an overlapped send or receive operation. The fWait parameter determines whether the function should wait for a pending overlapped operation to complete. If fWait is TRUE, the function does not return until the operation has been completed. If fWait is FALSE and the operation is still pending, WSAGetOverlappedResult() returns FALSE with the error WSA_IO_INCOMPLETE. Because in our case we waited on a signaled event for overlapped completion, this parameter has no effect. The final parameter, lpdwFlags, is a pointer to a DWORD that will receive resulting flags if the originating overlapped call was made with the WSARecv() or the WSARecvFrom() function.
If the WSAGetOverlappedResult() function succeeds, the return value is TRUE. This means that your overlapped operation has completed successfully and that the value pointed to by lpcbTransfer has been updated. If the return value is FALSE, one of the following statements is true:
Upon failure, the value pointed to by lpcbTransfer will not be updated, and your application should call the WSAGetLastError() function to determine the cause of the failure.
The following sample of code demonstrates how to structure a simple server application that is capable of managing overlapped I/O on one socket using the event notification described above.
#define DATA_BUFSIZE 4096
void main(void)
{
WSABUF DataBuf;
char buffer[DATA_BUFSIZE];
DWORD EventTotal = 0, RecvBytes=0, Flags=0;
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAOVERLAPPED AcceptOverlapped;
SOCKET ListenSocket, AcceptSocket;
// Step 1:
// Start Winsock and set up a listening socket
...
// Step 2:
// Accept an inbound connection
AcceptSocket = accept(ListenSocket, NULL, NULL);
// Step 3:
// Set up an overlapped structure
EventArray[EventTotal] = WSACreateEvent();
ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent = EventArray[EventTotal];
DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer;
EventTotal++;
// Step 4:
// Post a WSARecv request to begin receiving data on the socket
if (WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// Error occurred
}
}
// Process overlapped receives on the socket
while(TRUE)
{
DWORD Index;
// Step 5:
// Wait for the overlapped I/O call to complete
Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
// Index should be 0 because we
// have only one event handle in EventArray
// Step 6:
// Reset the signaled event
WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
// Step 7:
// Determine the status of the overlapped request
WSAGetOverlappedResult(AcceptSocket, &AcceptOverlapped, &BytesTransferred, FALSE, &Flags);
// First check to see whether the peer has closed
// the connection, and if so, close the socket
if (BytesTransferred == 0)
{
printf("Closing socket %d\n", AcceptSocket);
closesocket(AcceptSocket);
WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
return;
}
// Do something with the received data
// DataBuf contains the received data
...
// Step 8:
// Post another WSARecv() request on the socket
Flags = 0;
ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent = EventArray[Index - WSA_WAIT_EVENT_0];
DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = buffer;
if (WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// Unexpected error
}
}
}
}
The application outlines the following programming steps:
This example can easily be expanded to handle more than one socket by moving the overlapped I/O processing portion of the code to a separate thread and allowing the main application thread to service additional connection requests.
If a Winsock function is called in an overlapped fashion (either by specifying an event within the WSAOVERLAPPED structure or with a completion routine), the operation might complete immediately. For example, calling WSARecv() when data has already been received and buffered causes WSARecv() to return NO_ERROR. If any overlapped function fails with WSA_IO_PENDING or immediately succeeds, the completion event will always be signaled and the completion routine will be scheduled to run (if specified). For overlapped I/O with a completion port, this means that completion notification will be posted to the completion port for servicing.
The following program example tries to demonstrate the use of overlapped model. Create a new empty Win32 console mode application and add the project/solution name.
Add the following source code.
// Description:
//
// This sample illustrates how to develop a simple echo server Winsock
// application using the Overlapped I/O model with event notification. This
// sample is implemented as a console-style application and simply prints
// messages when connections are established and removed from the server.
// The application listens for TCP connections on port 5150 and accepts them
// as they arrive. When this application receives data from a client, it
// simply echos (this is why we call it an echo server) the data back in
// it's original form until the client closes the connection.
//
// Note: There are no command line options for this sample.
// Link to ws2_32.lib
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#define PORT 5150
#define DATA_BUFSIZE 8192
typedef struct _SOCKET_INFORMATION {
CHAR Buffer[DATA_BUFSIZE];
WSABUF DataBuf;
SOCKET Socket;
WSAOVERLAPPED Overlapped;
DWORD BytesSEND;
DWORD BytesRECV;
} SOCKET_INFORMATION, * LPSOCKET_INFORMATION;
DWORD WINAPI ProcessIO(LPVOID lpParameter);
DWORD EventTotal = 0;
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
LPSOCKET_INFORMATION SocketArray[WSA_MAXIMUM_WAIT_EVENTS];
CRITICAL_SECTION CriticalSection;
int main(int argc, char **argv)
{
WSADATA wsaData;
SOCKET ListenSocket, AcceptSocket;
SOCKADDR_IN InternetAddr;
DWORD Flags;
DWORD ThreadId;
DWORD RecvBytes;
InitializeCriticalSection(&CriticalSection);
if (WSAStartup((2,2),&wsaData) != 0)
{
printf("WSAStartup() failed with error %d\n", WSAGetLastError());
WSACleanup();
return 1;
}
else
printf("WSAStartup() looks nice!\n");
if ((ListenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("Failed to get a socket %d\n", WSAGetLastError());
return 1;
}
else
printf("WSASocket() is OK lol!\n");
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(PORT);
if (bind(ListenSocket, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
{
printf("bind() failed with error %d\n", WSAGetLastError());
return 1;
}
else
printf("YOu see, bind() is working!\n");
if (listen(ListenSocket, 5))
{
printf("listen() failed with error %d\n", WSAGetLastError());
return 1;
}
else
printf("listen() is OK maa...\n");
// Setup the listening socket for connections
if ((AcceptSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("Failed to get a socket %d\n", WSAGetLastError());
return 1;
}
else
printf("WSASocket() looks OK!\n");
if ((EventArray[0] = WSACreateEvent()) == WSA_INVALID_EVENT)
{
printf("WSACreateEvent() failed with error %d\n", WSAGetLastError());
return 1;
}
else
printf("WSACreateEvent() is OK!\n");
// Create a thread to service overlapped requests
if (CreateThread(NULL, 0, ProcessIO, NULL, 0, &ThreadId) == NULL)
{
printf("CreateThread() failed with error %d\n", GetLastError());
return 1;
}
else
printf("Nothing to say, CreateThread() is OK!\n");
EventTotal = 1;
while(TRUE)
{
// Accept inbound connections
if ((AcceptSocket = accept(ListenSocket, NULL, NULL)) == INVALID_SOCKET)
{
printf("accept() failed with error %d\n", WSAGetLastError());
return 1;
}
else
printf("accept() is OK!\n");
EnterCriticalSection(&CriticalSection);
// Create a socket information structure to associate with the accepted socket
if ((SocketArray[EventTotal] = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR, sizeof(SOCKET_INFORMATION))) == NULL)
{
printf("GlobalAlloc() failed with error %d\n", GetLastError());
return 1;
}
else
printf("GlobalAlloc() for LPSOCKET_INFORMATION is pretty fine!\n");
// Fill in the details of our accepted socket
SocketArray[EventTotal]->Socket = AcceptSocket;
ZeroMemory(&(SocketArray[EventTotal]->Overlapped), sizeof(OVERLAPPED));
SocketArray[EventTotal]->BytesSEND = 0;
SocketArray[EventTotal]->BytesRECV = 0;
SocketArray[EventTotal]->DataBuf.len = DATA_BUFSIZE;
SocketArray[EventTotal]->DataBuf.buf = SocketArray[EventTotal]->Buffer;
if ((SocketArray[EventTotal]->Overlapped.hEvent = EventArray[EventTotal] = WSACreateEvent()) == WSA_INVALID_EVENT)
{
printf("WSACreateEvent() failed with error %d\n", WSAGetLastError());
return 1;
}
else
printf("WSACreateEvent() is OK!\n");
// Post a WSARecv() request to to begin receiving data on the socket
Flags = 0;
if (WSARecv(SocketArray[EventTotal]->Socket,
&(SocketArray[EventTotal]->DataBuf), 1, &RecvBytes, &Flags, &(SocketArray[EventTotal]->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
return 1;
}
}
else
printf("WSARecv() should be working!\n");
EventTotal++;
LeaveCriticalSection(&CriticalSection);
// Signal the first event in the event array to tell the worker thread to
// service an additional event in the event array
if (WSASetEvent(EventArray[0]) == FALSE)
{
printf("WSASetEvent() failed with error %d\n", WSAGetLastError());
return 1;
}
else
printf("Don't worry, WSASetEvent() is OK!\n");
}
}
DWORD WINAPI ProcessIO(LPVOID lpParameter)
{
DWORD Index;
DWORD Flags;
LPSOCKET_INFORMATION SI;
DWORD BytesTransferred;
DWORD i;
DWORD RecvBytes, SendBytes;
// Process asynchronous WSASend, WSARecv requests
while(TRUE)
{
if ((Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE)) == WSA_WAIT_FAILED)
{
printf("WSAWaitForMultipleEvents() failed %d\n", WSAGetLastError());
return 0;
}
else
printf("WSAWaitForMultipleEvents() is OK!\n");
// If the event triggered was zero then a connection attempt was made
// on our listening socket.
if ((Index - WSA_WAIT_EVENT_0) == 0)
{
WSAResetEvent(EventArray[0]);
continue;
}
SI = SocketArray[Index - WSA_WAIT_EVENT_0];
WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
if (WSAGetOverlappedResult(SI->Socket, &(SI->Overlapped), &BytesTransferred, FALSE, &Flags) == FALSE || BytesTransferred == 0)
{
printf("Closing socket %d\n", SI->Socket);
if (closesocket(SI->Socket) == SOCKET_ERROR)
{
printf("closesocket() failed with error %d\n", WSAGetLastError());
}
else
printf("closesocket() is OK!\n");
GlobalFree(SI);
WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
// Cleanup SocketArray and EventArray by removing the socket event handle
// and socket information structure if they are not at the end of the arrays
EnterCriticalSection(&CriticalSection);
if ((Index - WSA_WAIT_EVENT_0) + 1 != EventTotal)
for (i = Index - WSA_WAIT_EVENT_0; i < EventTotal; i++)
{
EventArray[i] = EventArray[i + 1];
SocketArray[i] = SocketArray[i + 1];
}
EventTotal--;
LeaveCriticalSection(&CriticalSection);
continue;
}
// Check to see if the BytesRECV field equals zero. If this is so, then
// this means a WSARecv call just completed so update the BytesRECV field
// with the BytesTransferred value from the completed WSARecv() call.
if (SI->BytesRECV == 0)
{
SI->BytesRECV = BytesTransferred;
SI->BytesSEND = 0;
}
else
{
SI->BytesSEND += BytesTransferred;
}
if (SI->BytesRECV > SI->BytesSEND)
{
// Post another WSASend() request.
// Since WSASend() is not guaranteed to send all of the bytes requested,
// continue posting WSASend() calls until all received bytes are sent
ZeroMemory(&(SI->Overlapped), sizeof(WSAOVERLAPPED));
SI->Overlapped.hEvent = EventArray[Index - WSA_WAIT_EVENT_0];
SI->DataBuf.buf = SI->Buffer + SI->BytesSEND;
SI->DataBuf.len = SI->BytesRECV - SI->BytesSEND;
if (WSASend(SI->Socket, &(SI->DataBuf), 1, &SendBytes, 0, &(SI->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSASend() failed with error %d\n", WSAGetLastError());
return 0;
}
}
else
printf("WSASend() is OK!\n");
}
else
{
SI->BytesRECV = 0;
// Now that there are no more bytes to send post another WSARecv() request
Flags = 0;
ZeroMemory(&(SI->Overlapped), sizeof(WSAOVERLAPPED));
SI->Overlapped.hEvent = EventArray[Index - WSA_WAIT_EVENT_0];
SI->DataBuf.len = DATA_BUFSIZE;
SI->DataBuf.buf = SI->Buffer;
if (WSARecv(SI->Socket, &(SI->DataBuf), 1, &RecvBytes, &Flags, &(SI->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
return 0;
}
}
else
printf("WSARecv() is OK!\n");
}
}
}
Build and run the project. The following screenshot shows a sample output which is ready for connection.
------------------------------------------------------------------
Firstly we run the server program.
Next, we run the client program several times from different computer.
The previous server sample output is shown below.
< WSAEventSelect Program Example | Winsock2 I/O Method Main | Overlapped & AcceptEx() Example >