< Overlapped & AcceptEx() Example | Winsock2 I/O Method Main | Overlapped I/O with Callback Example - AcceptEx() >


 

 

Winsock 2 I/O Methods 5 Part 8

 

 

What do we have in this chapter 5 part 8?

  1. Completion Routines

  2. The Overlapped I/O Model With Callback Routines Example

  3. The Client-server Testing

 

Completion Routines

 

Completion routines are the other method your application can use to manage completed overlapped I/O requests. Completion routines are simply functions that you optionally pass to an overlapped I/O request and that the system invokes when an overlapped I/O request completes. Their primary role is to service a completed I/O request using the caller's thread. In addition, applications can continue overlapped I/O processing through the completion routine.

To use completion routines for overlapped I/O requests, your application must specify a completion routine, along with a WSAOVERLAPPED structure, to an I/O bound Winsock function (described previously). A completion routine must have the following function prototype:

 

void CALLBACK CompletionROUTINE(

    DWORD dwError,

    DWORD cbTransferred,

    LPWSAOVERLAPPED lpOverlapped,

    DWORD dwFlags

);

 

When an overlapped I/O request completes using a completion routine, the parameters contain the following information:

 

  1. The parameter dwError specifies the completion status for the overlapped operation as indicated by lpOverlapped.
  2. The cbTransferred parameter specifies the number of bytes that were transferred during the overlapped operation.
  3. The lpOverlapped parameter is the same as the WSAOVERLAPPED structure passed into the originating I/O call.
  4. The dwFlags parameter returns any flags that the operation may have completed with (such as from WSARecv()).

 

There is a major difference between overlapped requests submitted with a completion routine and overlapped requests submitted with an event object. The WSAOVERLAPPED structure's event field, hEvent, is not used, which means you cannot associate an event object with the overlapped request. Once you make an overlapped I/O call with a completion routine, your calling thread must eventually service the completion routine once it has completed. This requires you to place your calling thread in an alertable wait state and process the completion routine later, after the I/O operation has completed. The WSAWaitForMultipleEvents() function can be used to put your thread in an alertable wait state. The catch is that you must also have at least one event object available for the WSAWaitForMultipleEvents() function. If your application handles only overlapped requests with completion routines, you are not likely to have any event objects around for processing. As an alternative, your application can use the Windows SleepEx() function to set your thread in an alertable wait state. Of course, you can also create a dummy event object that is not associated with anything. If your calling thread is always busy and not in an alertable wait state, no posted completion routine will ever get called.

As you saw earlier, WSAWaitForMultipleEvents() normally waits for event objects associated with WSAOVERLAPPED structures. This function is also designed to place your thread in an alertable wait state and to process completion routines for completed overlapped I/O requests if you set the parameter fAlertable to TRUE. When overlapped I/O requests complete with a completion routine, the return value is WSA_IO_COMPLETION instead of an event object index in the event array. The SleepEx() function provides the same behavior as WSAWaitForMultipleEvents() except that it does not need any event objects. The SleepEx() function is defined as:

 

DWORD SleepEx(DWORD dwMilliseconds, BOOL bAlertable);

 

The dwMilliseconds parameter defines how long in milliseconds SleepEx() will wait. If dwMilliseconds is set to INFINITE, SleepEx() waits indefinitely. The bAlertable parameter determines how a completion routine will execute. If bAlertable is set to FALSE and an I/O completion callback occurs, the I/O completion function is not executed and the function does not return until the wait period specified in dwMilliseconds has elapsed. If it is set to TRUE, the completion routine executes and the SleepEx() function returns WAIT_IO_COMPLETION.

The following code outlines how to structure a simple server application that is capable of managing one socket request using completion routines as described earlier.

 

#define DATA_BUFSIZE    4096

 

SOCKET AcceptSocket, ListenSocket;

WSABUF DataBuf;

WSAEVENT EventArray[MAXIMUM_WAIT_OBJECTS];

DWORD Flags, RecvBytes, Index;

char buffer[DATA_BUFSIZE];

 

void main(void)

{

    WSAOVERLAPPED Overlapped;

 

    // Step 1:

    //  Start Winsock, and set up a listening socket

    ...

 

    // Step 2:

    //  Accept a new connection

    AcceptSocket = accept(ListenSocket, NULL, NULL);

 

    // Step 3:

    //  Now that we have an accepted socket, start

    //  processing I/O using overlapped I/O with a

    //  completion routine. To get the overlapped I/O

    //  processing started, first submit an overlapped WSARecv() request.

 

    Flags = 0;

 

    ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));

 

    DataBuf.len = DATA_BUFSIZE;

    DataBuf.buf = buffer;

    // Step 4:

    //  Post an asynchronous WSARecv() request

    //  on the socket by specifying the WSAOVERLAPPED

    //  structure as a parameter, and supply 

    //  the WorkerRoutine function below as the completion routine

 

    if (WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &Overlapped, WorkerRoutine) == SOCKET_ERROR)

    {

        if (WSAGetLastError() != WSA_IO_PENDING)

        {

            printf("WSARecv() failed with error %d\n", WSAGetLastError());

            return;

        }

    }

 

    // Because the WSAWaitForMultipleEvents() API

    // requires waiting on one or more event objects,

    // we will have to create a dummy event object.

    // As an alternative, we can use SleepEx() instead

    EventArray [0] = WSACreateEvent();

 

    while(TRUE)

    {

        // Step 5:

        Index = WSAWaitForMultipleEvents(1, EventArray, FALSE, WSA_INFINITE, TRUE);

 

        // Step 6:

        if (Index == WAIT_IO_COMPLETION)

        {

            // An overlapped request completion routine

            // just completed. Continue servicing more completion routines.

            continue;

        }

        else

        {

            // A bad error occurred: stop processing!

            // If we were also processing an event

            // object, this could be an index to the event array.

            return;

        }

    }

}

 

void CALLBACK WorkerRoutine(DWORD Error, DWORD BytesTransferred, LPWSAOVERLAPPED Overlapped, DWORD InFlags)

{

    DWORD SendBytes, RecvBytes;

    DWORD Flags;

 

    if (Error != 0 ││ BytesTransferred == 0)

    {

        // Either a bad error occurred on the socket or the socket was closed by a peer

        closesocket(AcceptSocket);

        return;

    }

 

    // At this point, an overlapped WSARecv() request

    // completed successfully. Now we can retrieve the

    // received data that is contained in the variable

    // DataBuf. After processing the received data, we

    // need to post another overlapped WSARecv() or

    // WSASend() request. For simplicity, we will post another WSARecv() request.

    Flags = 0;

 

    ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));

 

    DataBuf.len = DATA_BUFSIZE;

    DataBuf.buf = buffer;

 

    if (WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &Overlapped, WorkerRoutine) == SOCKET_ERROR)

    {

        if (WSAGetLastError() != WSA_IO_PENDING )

        {

            printf("WSARecv() failed with error %d\n", WSAGetLastError());

            return;

        }

    }

}

 

The application illustrates the following programming steps:

 

  1. Create a socket and begin listening for a connection on a specified port.
  2. Accept an inbound connection.
  3. Create a WSAOVERLAPPED structure for the accepted socket.
  4. Post an asynchronous WSARecv request on the socket by specifying the WSAOVERLAPPED structure as a parameter and supplying a completion routine.
  5. Call WSAWaitForMultipleEvents() with the fAlertable parameter set to TRUE and wait for an overlapped request to complete. When an overlapped request completes, the completion routine automatically executes and WSAWaitForMultipleEvents() returns WSA_IO_COMPLETION. Inside the completion routine, then post another overlapped WSARecv request with a completion routine.
  6. Verify that WSAWaitForMultipleEvents() returns WSA_IO_COMPLETION.
  7. Repeat steps 5 and 6.

 

The overlapped model provides high-performance socket I/O. It is different from all the previous models because an application posts buffers to send and receive data that the system uses directly. That is, if an application posts an overlapped receive with a 10 KB buffer and data arrives on the socket, it is copied directly into this posted buffer. In the previous models, data would arrive and be copied to the per-socket receive buffers at which point the application is notified of the capability to read. After the application calls a receive function, the data is copied from the per-socket buffer to the application's buffer. The WSARecvMsg(), AcceptEx(), ConnectEx(), TransmitFile(), TransmitPackets(), and DisconnectEx() API functions will be discussed in more detail in another chapter.

The disadvantage of using overlapped I/O with events is, again, the limitation of being able to wait on a maximum of 64 events at a time. Completion routines are a good alternative but care must be taken to ensure that the thread that posted the operation goes into an alertable wait state in order for the completion routine to complete. Also, care should be taken to make sure that the completion routines do not perform excessive computations so that these routines may fire as fast as possible under a heavy load.

 

The Overlapped I/O Model With Callback Routines Example

 

Create a new empty Win32 console mode application and add the project/solution name.

 

The Overlapped I/O Model with Callback Routines program example: Creating 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 callback routines.

//    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 {

            OVERLAPPED Overlapped;

            SOCKET Socket;

            CHAR Buffer[DATA_BUFSIZE];

            WSABUF DataBuf;

            DWORD BytesSEND;

            DWORD BytesRECV;

} SOCKET_INFORMATION, * LPSOCKET_INFORMATION;

 

void CALLBACK WorkerRoutine(DWORD Error, DWORD BytesTransferred, LPWSAOVERLAPPED Overlapped, DWORD InFlags);

DWORD WINAPI WorkerThread(LPVOID lpParameter);

 

SOCKET AcceptSocket;

 

int main(int argc, char **argv)

{

            WSADATA wsaData;

            SOCKET ListenSocket;

            SOCKADDR_IN InternetAddr;

            INT Ret;

            HANDLE ThreadHandle;

            DWORD ThreadId;

            WSAEVENT AcceptEvent;

 

            if ((Ret = WSAStartup((2,2),&wsaData)) != 0)

            {

                        printf("WSAStartup() failed with error %d\n", Ret);

                        WSACleanup();

                        return 1;

            }

            else

                        printf("WSAStartup() is OK!\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 pretty fine!\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("bind() is OK!\n");

 

            if (listen(ListenSocket, 5))

            {

                        printf("listen() failed with error %d\n", WSAGetLastError());

                        return 1;

            }

            else

                        printf("listen() is OK!\n");

 

            if ((AcceptEvent = WSACreateEvent()) == WSA_INVALID_EVENT)

            {

                        printf("WSACreateEvent() failed with error %d\n", WSAGetLastError());

                        return 1;

            }

            else

                        printf("WSACreateEvent() is OK!\n");

 

            // Create a worker thread to service completed I/O requests

            if ((ThreadHandle = CreateThread(NULL, 0, WorkerThread, (LPVOID) AcceptEvent, 0, &ThreadId)) == NULL)

            {

                        printf("CreateThread() failed with error %d\n", GetLastError());

                        return 1;

            }

            else

                        printf("CreateThread() should be fine!\n");

 

            while(TRUE)

            {

                        AcceptSocket = accept(ListenSocket, NULL, NULL);

 

                        if (WSASetEvent(AcceptEvent) == FALSE)

                        {

                                    printf("WSASetEvent() failed with error %d\n", WSAGetLastError());

                                    return 1;

                        }

                        else

                                    printf("WSASetEvent() should be working!\n");

            }

}

 

DWORD WINAPI WorkerThread(LPVOID lpParameter)

{

            DWORD Flags;

            LPSOCKET_INFORMATION SocketInfo;

            WSAEVENT EventArray[1];

            DWORD Index;

            DWORD RecvBytes;

 

            // Save the accept event in the event array

            EventArray[0] = (WSAEVENT) lpParameter;

 

            while(TRUE)

            {

                        // Wait for accept() to signal an event and also process WorkerRoutine() returns

                        while(TRUE)

                        {

                                    Index = WSAWaitForMultipleEvents(1, EventArray, FALSE, WSA_INFINITE, TRUE);

                                    if (Index == WSA_WAIT_FAILED)

                                    {

                                                printf("WSAWaitForMultipleEvents() failed with error %d\n", WSAGetLastError());

                                                return FALSE;

                                    }

                                    else

                                                printf("WSAWaitForMultipleEvents() should be OK!\n");

 

                                    if (Index != WAIT_IO_COMPLETION)

                                    {

                                                // An accept() call event is ready - break the wait loop

                                                break;

                                    }

                        }

 

                        WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]);

                        // Create a socket information structure to associate with the accepted socket

                        if ((SocketInfo = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR, sizeof(SOCKET_INFORMATION))) == NULL)

                        {

                                    printf("GlobalAlloc() failed with error %d\n", GetLastError());

                                    return FALSE;

                        }

                        else

                                    printf("GlobalAlloc() for SOCKET_INFORMATION is OK!\n");

 

                        // Fill in the details of our accepted socket

                        SocketInfo->Socket = AcceptSocket;

                        ZeroMemory(&(SocketInfo->Overlapped), sizeof(WSAOVERLAPPED));

                        SocketInfo->BytesSEND = 0;

                        SocketInfo->BytesRECV = 0;

                        SocketInfo->DataBuf.len = DATA_BUFSIZE;

                        SocketInfo->DataBuf.buf = SocketInfo->Buffer;

 

                        Flags = 0;

                        if (WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes, &Flags,

                                            &(SocketInfo->Overlapped), WorkerRoutine) == SOCKET_ERROR)

                        {

                                    if (WSAGetLastError() != WSA_IO_PENDING)

                                    {

                                                printf("WSARecv() failed with error %d\n", WSAGetLastError());

                                                return FALSE;

                                    }

                        }

                        else

                                    printf("WSARecv() is OK!\n");

 

                        printf("Socket %d got connected...\n", AcceptSocket);

            }

            return TRUE;

}

 

void CALLBACK WorkerRoutine(DWORD Error, DWORD BytesTransferred, LPWSAOVERLAPPED Overlapped, DWORD InFlags)

{

            DWORD SendBytes, RecvBytes;

            DWORD Flags;

 

            // Reference the WSAOVERLAPPED structure as a SOCKET_INFORMATION structure

            LPSOCKET_INFORMATION SI = (LPSOCKET_INFORMATION) Overlapped;

 

            if (Error != 0)

            {

                        printf("I/O operation failed with error %d\n", Error);

            }

 

            if (BytesTransferred == 0)

            {

                        printf("Closing socket %d\n\n", SI->Socket);

            }

 

            if (Error != 0 || BytesTransferred == 0)

            {

                        closesocket(SI->Socket);

                        GlobalFree(SI);

                        return;

            }

 

            // 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->DataBuf.buf = SI->Buffer + SI->BytesSEND;

                        SI->DataBuf.len = SI->BytesRECV - SI->BytesSEND;

 

                        if (WSASend(SI->Socket, &(SI->DataBuf), 1, &SendBytes, 0, &(SI->Overlapped), WorkerRoutine) == SOCKET_ERROR)

                        {

                                    if (WSAGetLastError() != WSA_IO_PENDING)

                                    {

                                                printf("WSASend() failed with error %d\n", WSAGetLastError());

                                                return;

                                    }

                        }

                        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->DataBuf.len = DATA_BUFSIZE;

                        SI->DataBuf.buf = SI->Buffer;

 

                        if (WSARecv(SI->Socket, &(SI->DataBuf), 1, &RecvBytes, &Flags, &(SI->Overlapped), WorkerRoutine) == SOCKET_ERROR)

                        {

                                    if (WSAGetLastError() != WSA_IO_PENDING )

                                    {

                                                printf("WSARecv() failed with error %d\n", WSAGetLastError());

                                                return;

                                    }

                        }

                        else

                                    printf("WSARecv() is fine!\n");

            }

}

 

Build and run the project.

 

--------------------------------------------------------

The Overlapped I/O Model with Callback Routines program example: running the program, waiting connection from sender/client

 

The Client-server Testing

 

Firstly we run the server program.

 

The Overlapped I/O Model with Callback Routines program example: running the program for client connection

 

Then we run the client program.

 

The Overlapped I/O Model with Callback Routines program example: running the client program twice

 

The previous server sample output is shown below after some client connections were completed.

 

The Overlapped I/O Model with Callback Routines program example: the receiver/server sample output when communication was completed

 

 

 


< Overlapped & AcceptEx() Example | Winsock2 I/O Method Main | Overlapped I/O with Callback Example - AcceptEx() >