Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Parallel

Fast Networking with WinSock 2.0


FEB96: Fast Networking with WinSock 2.0

Derek has done extensive development work on Windows-based communications software. He can be reached at [email protected]. Martin is cofounder and CTO of Stardust Technologies, and can be reached at [email protected].


The Windows Sockets specification has made Windows a viable network-software platform. WinSock 1.1, for instance, provided Windows with a standard TCP/IP networking API. This led to an explosion of network-enabled software for the PC, which coincided with the popularity of the Internet and World Wide Web. WinSock 2.0 builds on the earlier specification by providing true multiprotocol support, implementing socket sharing, and offering significant performance enhancements. Still, the upcoming WinSock 2.0 retains binary and source compatibility with WinSock 1.1.

To better support wireless, ATM, and ISDN networks, for example, WinSock 2.0 has added quality-of-service features to give you greater flexibility and control over a multitude of network media. These features make it possible for the application to discover bandwidth and latency for ATM, ISDN, and other types of networks. This information is particularly useful to multimedia applications.

The WinSock 2.0 spec also includes explicit multipoint/multicast support. Although some vendors provide this with WinSock 1.1, the spec doesn't require it. In addition to the window-based, asynchronous notification of network events in WinSock 1.1, WinSock 2.0 has event-driven asynchronous notification and supports overlapped I/O.

One networking problem addressed by WinSock 2.0 involves a variety of different name-resolution services. WinSock 2.0 allows a program to ask which name-resolution services are available (for example, DNS or X.500) and then ask a particular name service to resolve a particular name. This allows WinSock 2.0 apps to work comfortably in heterogenous network environments.

WinSock 2.0 features such as overlapped I/O and asynchronous-event objects rely on operating-system functionality not found in 16-bit Windows. For that reason, WinSock 2.0 will be initially supported only in Windows 95 and Windows NT. Implementing WinSock 2.0 on 16-bit Windows and Win32s will require emulating some services of the newer Windows platforms.

In this article, we'll describe how to get maximum performance from WinSock 2.0 applications. In particular, we'll focus on extremely fast network-file transfers, where the transfer rate is limited only by the speed of the network connection, the speed of the underlying media, and the overhead of the operating system itself. In the world of WinSock, this is as fast as it gets.

To achieve this performance, we'll take advantage of two features new to WinSock 2.0: event objects and overlapped I/O.

Event Objects and Overlapped I/O

Event objects are used for thread synchronization. Suppose you have two threads, one of which relies on some component of the other thread to complete. By calling one of the Win32 event-object routines, the first thread can be suspended until the second signals an event. The event is a Windows handle with two states, signaled or nonsignaled. When the first thread requires processing in the second, it blocks and waits for the event. When the second thread completes the necessary processing, it signals the event, allowing the first thread to continue.

In the following discussion, our file I/O thread will be blocked until a particular read or write completes. An event object is associated with an asynchronous file I/O call, then WaitForSingleObject() or WaitForMultipleObjects() is called. The thread is truly blocked until something--in this case the completion of the I/O--signals the event. Once the event is signaled, the blocked thread resumes.

One common use of event objects is synchronizing a file I/O thread when using overlapped I/O. Normally, file I/O operations require multiple buffer copies. When an application calls write(), the buffer passed into write() is copied internally, and the write() call completes. The internally copied buffer is (eventually) written to the file system. Overlapped I/O saves a buffer copy by using the buffer passed to WriteFile() directly, rather than making an internal copy. Since the operating system is using the application's buffer rather than an internal one, the operating system must notify the application when the I/O is complete. Until that time, the application may not and should not change the buffer. In Win32, that notification is done by signaling an event.

Fast Network I/O

The program we'll present here provides the meat of what could be a generic FTP client and server. On the server side, the program needs to open a file, read it, send it, then close it. On the client side, the program needs to open the file, read from the network, write the file, then close it.

To perform this operation, we first need a connection from client to server. On both ends of the connection, the socket needs to be created with WSASocket() to enable the overlapped I/O. We are using a stream-based protocol and, for simplicity, assume that we want TCP. On the server side, we then bind() to a known port number for the client to connect to. The client can bind() to any available address.

Our client resolves the host name using the generic TCP/IP addressing scheme. (This sample code doesn't delve into the various address families supported by WinSock 2.0.) After successfully binding to a local address, the client calls (a blocking) connect(). Once the connection is established, it's time to actually send the file.

With WinSock 1.1, the client would just enter a tight loop, reading from the network and writing to the file system as fast as it could. However, each successive call to recv() would not be initiated until the previous block could be written to disk. Each successive write call could not complete at least until the buffers were copied internally; see Listing One. With WinSock 2.0 and the Win32 overlapped I/O mechanism, we can dramatically increase the performance.

The WinSock 2.0 client code begins in the function get_file_from_server() in Listing Three. For simplicity, the connection is established using the old-style blocking routines. A buffer pool is created into which the file is read and from which the file writes will be sent. An event object is created for each buffer in the pool. By associating each buffer with an event, we ensure that each thread will be able to proceed in the proper order. Once the buffer pool is created and initialized, we begin the process by calling WSARecv() with each buffer/event object pair.

At this point, the operating system begins filling the buffers. Next, we create the NetworkThread thread for handing the completion of file I/O. That thread is blocked, waiting for the event associated with the first buffer to be signaled. When the first network receive is complete, we create FileThread, a second thread for completion of file I/O. We wait to create the second thread to ensure the threads are properly synchronized. When the first buffer is filled, the associated event will be signaled and the WSAWaitForMultipleEvents() will return. Since we want to read the file sequentially, we just wait for the event object associated with each buffer in turn.

As each buffer is filled, WSAWaitForMultipleEvents() returns (we're only looking for one event at a time, but you could use the same call to wait for multiple events). The buffer we originally sent to WSARecv() is now full, and we want to write it to disk. By using WriteFile(), the buffer will be sent as is (without a copy into a buffer internal to the file system), and we'll wait for completion in our file I/O thread. As each file operation completes, the event associated with that buffer is signaled. We can then send that buffer back into the pool of buffers waiting to be filled from the network, again calling WSARecv(). Using this rotating buffer pool, we can implement a write-behind scheme that allows the operating system to read and write at its earliest convenience.

The server code also starts by opening a socket, bound to a local address. We listen() for incoming connections, then enter a blocking accept(). When the client calls connect(), the server accept()s the incoming connection and we're ready to send the file.

As with the client, the WinSock 1.1 server in Listing Two reads and sends blocks of data one at a time. The write to the network layer can't begin until the data is read from disk. Again, time is wasted in internal buffer copies, as data is first written into the kernel-disk buffers, then into the user buffer, and finally into the protocol stack's buffers waiting to be sent.

To speed things up, the server code in Listing Four uses the same--but reversed--procedure as the client code. The setup procedure send_file_to_client() first establishes the connection with the client. We create and initialize the buffer pool and call ReadFile() with each buffer/event object pair. After the first event is signaled (from the first call to ReadFile()), we start the thread to handle the network I/O notifications. Again, to make sure the threads start out synchronized, we don't start the NetworkThread until the first file-system read is complete. As file system reads complete, we'll initiate WSASend() calls to send those buffers straight to the network layer.

The most notable improvement would be to take advantage of the multiple protocol support WinSock 2.0 offers. In this code, we established the connection based on the familiar IP constructs and resolved the host names using DNS. The new WinSock 2.0 name/services resolution routines let you perform transport-independent calls that use IPX/SPX, AppleTalk, DECNet, or any other protocol for which a WinSock 2.0 namespace service provider exists.

For More Information

One way to follow (or contribute to) the WinSock Group is to join a mailing list. Send e-mail to [email protected] with subscribe list in the body of your e-mail. list can be either winsock-2 or winsock-hackers. The winsock-2 list is the focal point of net-based discussion of WinSock 2.0. The winsock-hackers list is a hang-out for WinSock 1.1 programmers, although a lot more discussion happens in the WinSock newsgroups.

Among the newsgroups that provide WinSock 2.0 information are alt.winsock (general WinSock discussion) and alt.winsock.programming (programmer-specific WinSock discussions). WinSock information is also available at ftp://ftp.stardust.com/pub/winsock/ (WinSock 1.1 and 2.0 documents, freeware/shareware programs, and so on), http://www.intel.com/IAL/winsock2/ (WinSock 2.0 information), and ftp://ftp.cica.indiana.edu/pub/pc/win3/winsock/ (freeware and shareware programs).

Finally, the World Wide Web sites providing WinSock information include http://www.stardust.com, http://www.intel.com/IAL/winsock2/index.html, http://sunsite.unc.edu/winsock/, and http://www/microsoft.com/pages/developer/winsock/.

Listing One

/* o_client.c -- This is the "Old way" of doing a file transfer. Set up the 
 * connection, then go into a loop, alternating between recv()s and write()s.
 */
int get_file_from_server(char * servername, char * filename)
{
    struct hostent FAR * he;
    SOCKET sock;
    int nRc;
    struct sockaddr_in addr;
    char buf[1024];
    int hFile;
    int recvlen;
    hFile = open(filename, _O_CREATE | _O_WRONLY);
    
    he = gethostbyname(servername);
    if(!he)
    {
        return ERR_NO_HOSTNAME;
    }
    sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sock == INVALID_SOCKET)
    {
        return ERR_NO_SOCKET;
    }
    /* We want to bind to any available address we have. */
    addr.sin_family = PF_INET;
    addr.sin_port = 0;
    addr.sin_addr.s_addr = INADDR_ANY;
    nRc=bind(sock, (struct sockaddr FAR *)&addr, sizeof(struct sockaddr));
    if(nRc != 0)
    {
        closesocket(sock);
        return ERR_BIND_FAILED;
    }
    /* Now connect to the server.  We'll just use a normal blocking
     * connect, because it's easiest for this example. */
    addr.sin_port = htons(KNOWN_PORT);
    addr.sin_addr.s_addr = *(unsigned long *) he->h_addr;
    nRc = connect(sock, (struct sockaddr FAR *)&addr, sizeof(struct sockaddr));
    if(nRc != 0)
    {
        closesocket(sock);
        return ERR_CONNECT_FAILED;
    }
    /* Here we enter our loop. The calls are done sequentially, and each 
     * must wait for it's respective kernel subsystem to complete the
     * operation before continuing. */
    while((recvlen = recv(sock, buf, 1024, 0)) > 0)
    {
        write(hFile, buf, recvlen);
    }
    closesocket(sock);
    return;
}

Listing Two

/* o_server.c -- This is the "Old way" of doing a file transfer. Set up the 
 * connection, then go into a loop, alternating between read()s and send()s.
 */
int give_file_to_client(char * filename)
{
    SOCKET sock, SocketID;
    int nRc;
    struct sockaddr_in addr;
    int fileptr = 0;
    int hFile = open(filename, _O_RDONLY);
    int bytesread;
    sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    if(sock == INVALID_SOCKET)
    {
        return ERR_NO_SOCKET;
    }
    /* Bind to a known port, so the client knows where to find us. */
    addr.sin_family = PF_INET;
    addr.sin_port = htons(KNOWN_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;
    nRc = bind(sock, (struct sockaddr FAR *)&addr,sizeof(struct sockaddr));
    if(nRc != 0)
    {
        closesocket(sock);
        return ERR_BIND_FAILED;
    }
    /* now listen(), and wait for the client to try to connect */
    nRc = listen(sock, 2);
    if(nRc != 0)
    {
        closesocket(sock);
        return LISTEN_FAILED;
    }
    SocketID = accept(sock, (struct sockaddr FAR *)&addr,
                      sizeof(struct sockaddr_in));
    if(SocketID == INVALID_SOCKET)
    {
        closesocket(sock);
        return ACCEPT_FAILED;
    }
    /* Here we enter our loop. The calls are done sequentially, and each 
     * must wait for it's respective kernel subsystem to complete the
     * operation before continuing. */
    while((bytesread = read(hFile, buf, 1024)) > 0)
    {
        send(SocketID, buf, bytesread, 0);
    }
    closesocket(SocketID);
    closesocket(sock);
}

Listing Three

/* client.c -- The new and faster way of doing a file transfer. The buffers 
 * will be filled and emptied as soon as the network and file kernel 
 * subsystems, respectively, are able. First, establish the connection with 
 * the server. Create the socket using the overlapped I/O flag. Recall that 
 * we are assuming TCP. Note that in many places, psuedo-code is used.  At
 * the time it was written, the WinSock 2 SDK was not yet available. 
*/
#define DATA_BUF_SIZE       1024
#define BUF_POOL_SIZE       10
HANDLE  file_thread;
DWORD       file_thread_id;
HANDLE  network_thread;
DWORD       network_thread_id;
typedef struct _buf_pool_entry
{
    OVERLAPPED overlapped;
    WSABUF    data;
    int last_buffer;
    
} BUF_POOL_ENTRY;
BUF_POOL_ENTRY buffer_pool[BUF_POOL_SIZE];
HANDLE FileHandle = 0;
SOCKET SocketID = 0;
DWORD NetworkThread(LPVOID threadparams);
int get_file_from_server(char * servername, char * filename)
{
    struct hostent FAR * he;
    SOCKET sock;
    int nRc;
    struct sockaddr_in addr;
    
    he = gethostbyname(servername);
    if(!he)
    {
        return ERR_NO_HOSTNAME;
    }
    sock = WSASocket(PF_INET, SOCK_STREAM, IPPROTO_TCP,
                          NULL, // we're just using TCP, skip protocol
                                  // independence stuff for now
                          NULL, // we'll not worry about socket groups,
                                  // either
                          WSA_FLAG_OVERLAPPED);
    if(sock == INVALID_SOCKET)
    {
        return ERR_NO_SOCKET;
    }
    /* We want to bind to any available address we have. */
    addr.sin_family = PF_INET;
    addr.sin_port = 0;
    addr.sin_addr.s_addr = INADDR_ANY;
    nRc = bind(sock, (struct sockaddr FAR *)&addr, sizeof(struct sockaddr));
    if(nRc != 0)
    {
        closesocket(sock);
        return ERR_BIND_FAILED;
    }
    /* Now connect to the server. We'll just use a normal blocking
     * connect, because it's easiest for this example. */
    addr.sin_port = htons(KNOWN_PORT);
    addr.sin_addr.s_addr = *(unsigned long *) he->h_addr;
    nRc = connect(sock, (struct sockaddr FAR *)&addr,
                  sizeof(struct sockaddr));
    if(nRc != 0)
    {
        closesocket(sock);
        return ERR_CONNECT_FAILED;
    }
    SocketID = sock;
    /* initialize the buffer pool */
    for(i = 0; i < BUF_POOL_SIZE; i++)
    {
        buffer_pool[i].overlapped.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
                if(buffer_pool[i].overlapped.hEvent == NULL)
        {
            // clean up all the objects and exit. We failed initialization
        }
        buffer_pool[i].data.buf = malloc(DATA_BUF_SIZE);
        if(buffer_pool[i].data.buf == NULL)
        {
            // clean up all the objects and exit.  We failed initialization
        }
        buffer_pool[i].data.size = DATA_BUF_SIZE;
        buffer_pool[i].last_buffer = FALSE;
    }
    /* Send all of the buffers in the pool to WSARecv, so they're ready for 
     * incoming data. */
    for(i = 0; i < BUF_POOL_SIZE; i++)
    {
        if(WSARecv(SocketID, &(buffer_pool[i].data), 1, &bytesread, &flags,
                                    &(buffer_pool[i].overlapped), NULL) == 0)
        {
            // Function succeeded immediately. Set the event object ourselves
            SetEvent(buffer_pool[i].overlapped.hEvent);
        }
        else
        {
            if(WSAGetLastError() == WSA_IO_PENDING)
                /* exactly what we expected */
            else
                /* we've failed, clean up and return */
        }
    }
    /* Create the network thread.  The network thread waits for network 
     * operations to complete, and then takes those (full) buffers and sends 
     * them to the filesystem via WriteFile. */
    network_thread = CreateThread(NULL, 0, NetworkThread, NULL, 
                                  0, // run immediately
                                  &network_thread_id);
    /* Wait until network thread is complete */
    return SUCCESS;
}
DWORD NetworkThread(LPVOID threadparams)
{
    int nRc;
    int pool_ptr = 0;
    BOOL firstpass = 1;
    DWORD bytesread, flags;
    BOOL success;
    int file_ptr = 0;
    do
    {
        /* No need to check the return code. Since we specified WSA_INFINITE, 
         * the only time it will return is when our event is signalled. */
        WSAWaitForMultipleEvents(1, &(buffer_pool[pool_ptr].overlapped.hEvent),
                                                  FALSE, WSA_INFINITE, FALSE);
        /* WaitForMultipleEvents() blocks until the next recv() buffer
         * is filled.  We check (below) to see how many bytes were
         * actually read, and then send off to be written to disk. */
        success = WSAGetOverlappedResult(SocketID,
                                         &(buffer_pool[pool_ptr].overlapped),
                                         &bytesread, FALSE, &flags);
        /* If we've received the event notification, but the number
         * of bytes is zero, then the connection;s been closed.
         */
        if(bytesread == 0)
        {
            buffer_pool[i].last_buffer = TRUE;
            // set the event, so that the file thread will receive this "EOF"
            SetEvent(buffer_pool[pool_ptr].overlapped.hEvent);
            // we're done receiving
            break;
        }
        buffer_pool[pool_ptr].overlapped.Offset = file_ptr;
        file_ptr += bytesread;
        if(WriteFile(FileHandle, buffer_pool[pool_ptr].data.buf, bytesread, 
                 &bytes_written, &(buffer_pool[pool_ptr].overlapped)) == TRUE)
        {
            // function succeeded immediately.  Set the event object ourselves
            SetEvent(buffer_pool[pool_ptr].overlapped.hEvent);
        }
        if(firstpass)
        {
            // This is the first buffer to be filled.  Create the
            // thread that handles completion of file I/O
            file_thread = CreateThread(NULL, 0, FileThread, NULL,
                                        0, // run immediately
                                        &file_thread_id);
            firstpass = FALSE;
        }
        if(++pool_ptr >= BUF_POOL_SIZE)
            pool_ptr = 0;
    }
    /* when bytesread is zero, we're don
    while(bytesread > 0 );
    /* Wait for file thread to complete before leaving */
    ExitThread(0L);
}
/* Everytime a write to the filesystem completes, take the buffer, and call 
 * WSARecv() so that the network subsystem can fill it back up. */
DWORD FileThread(LPVOID threadparams)
{
    int nRc;
    int pool_ptr = 0;
    DWORD bytesread, flags;
    BOOL success;
    int file_ptr = 0;
    do
    {
        /* We don't need to check return code. Since we specified INFINITE,
         * the only time  it will return is when our event is signalled. */
        WaitForSingleObject(&(buffer_pool[pool_ptr].overlapped.hEvent),
                                      INFINITE);
        success = GetOverlappedResult(FileHandle,
                                      &(buffer_pool[pool_ptr].overlapped),
                                      &bytesread, FALSE);
        /* If we've received the event notification, but the number
         * of bytes is zero, then the connection;s been closed. */
        if(buffer_pool[i].last_buffer == TRUE)
        {
            // we're done
            break;
        }
        buffer_pool[pool_ptr].overlapped.Offset = file_ptr;
        file_ptr += bytesread;
        /* Buffer is ready to be returned to the protocol stack for reading */
        if(WSARecv(SocketID, &(buffer_pool[pool_ptr].data), 1, &bytesread,
                      &flags, &(buffer_pool[pool_ptr].overlapped), NULL) == 0)
        {
            // function succeeded immediately.  Set the event object ourselves
            SetEvent(buffer_pool[pool_ptr].overlapped.hEvent);
        }
        if(++pool_ptr >= BUF_POOL_SIZE)
            pool_ptr = 0;       
    }
    /* when bytesread is zero, we're done
    while(bytesread > 0 );
    ExitThread(0L);
}

Listing Four

/* server.c -- The new and faster way of doing a file transfer. The buffers 
 * will be filled and emptied as soon as the network and file kernel 
 * subsystems, respectively, are able. First, establish the connection with 
 * the server. Create the socket using the overlapped I/O flag. Recall that we 
 * are assuming TCP. Note that in many places, psuedo-code is used.  At
 * the time it was written, the WinSock 2 SDK was not yet available.
 */
#define DATA_BUF_SIZE       1024
#define BUF_POOL_SIZE       10
HANDLE  file_thread;
DWORD       file_thread_id;
HANDLE  network_thread;
DWORD       network_thread_id;
typedef struct _buf_pool_entry
{
    OVERLAPPED overlapped;
    WSABUF    data;
    int last_buffer;
    
} BUF_POOL_ENTRY;
BUF_POOL_ENTRY buffer_pool[BUF_POOL_SIZE];
HANDLE FileHandle = 0;
SOCKET SocketID = 0;
DWORD NetworkThread(LPVOID threadparams);
int give_file_to_client(char * filename)
{
    SOCKET sock;
    int nRc;
    struct sockaddr_in addr;
    int fileptr = 0;
    dword bytesread;
    sock = WSASocket(PF_INET, SOCK_STREAM, IPPROTO_TCP,
                     NULL,  // we're just using TCP, skip protocol
                            // independence stuff for now
                     NULL,  // we'll not worry about socket groups, either
                     WSA_FLAG_OVERLAPPED);
    if(sock == INVALID_SOCKET)
    {
        return ERR_NO_SOCKET;
    }
    /* Bind to a known port, so the client knows where to find us. */
    addr.sin_family = PF_INET;
    addr.sin_port = htons(KNOWN_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;
    nRc = bind(sock, (struct sockaddr FAR *)&addr, sizeof(struct sockaddr));
    if(nRc != 0)
    {
        closesocket(sock);
        return ERR_BIND_FAILED;
    }
    /* Now listen(), and wait for the client to try to connect */
    nRc = listen(sock, 2);
    if(nRc != 0)
    {
        closesocket(sock);
        return LISTEN_FAILED;
    }
    SocketID = accept(sock, (struct sockaddr FAR *)&addr,
                      sizeof(struct sockaddr_in));
    if(SocketID == INVALID_SOCKET)
    {
        closesocket(sock);
        return ACCEPT_FAILED;
    }
    /* Initialize the buffer pool */
    for(i = 0; i < BUF_POOL_SIZE; i++)
    {
      buffer_pool[i].overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        
      if(buffer_pool[i].overlapped.hEvent == NULL)
      {
            // clean up all the objects and exit.  We failed initialization
      }
      buffer_pool[i].data.buf = malloc(DATA_BUF_SIZE);
      if(buffer_pool[i].data.buf == NULL)
      {
            // clean up all the objects and exit.  We failed initialization
      }
      buffer_pool[i].data.len = DATA_BUF_SIZE;
      buffer_pool[i].last_buffer = FALSE;
    }
    /* Send all of the buffers in the pool to WSARecv, so they're ready for 
     * incoming data. */
        for(i = 0; i < BUF_POOL_SIZE; i++)
    {
        buffer_pool[i].overlapped.Offset = fileptr;
        if(ReadFile(FileHandle
                    &(buffer_pool[i].data.buf), DATA_BUF_SIZE, &bytesread,
                    &(buffer_pool[i].overlapped)) == 0)
        {
            // function succeeded immediately.  Set the event object ourselves
            SetEvent(buffer_pool[i].overlapped.hEvent);
        }
        else
        {
            if(WSAGetLastError() == WSA_IO_PENDING)
                // good, that's what we expected
            else
                /* we've failed, clean up and return */
        }
        fileptr += DATA_BUF_SIZE;
    }
    /* Create the file I/O thread. The file I/O thread waits until the buffers
     * have been filled, and then takes those full buffers and sends them out 
     * to the network (via WSASend(). */
    file_thread = CreateThread(NULL, 0, FileThread, (LPVOID) (LPINT) &fileptr,
                               0, // run immediately
                               &file_thread_id);
    /* Wait until network thread is complete */
    return SUCCESS;
}
DWORD NetworkThread(LPVOID threadparams)
{
    int nRc;
    int pool_ptr = 0;
    BOOL firstpass = 1;
    DWORD bytesread, flags;
    BOOL success;
    int file_ptr = *(LPINT) threadparams;
    do
    {
        /* No need to check the return code. Since we specified WSA_INFINITE, 
         * the only time it will return is when event is signalled. */
        WSAWaitForMultipleEvents(1, &(buffer_pool[pool_ptr].overlapped.hEvent),
                                                  FALSE, WSA_INFINITE, FALSE);
        /* WaitForMultipleEvents() blocks until the next send() buffer
         * is empty. We then send it back to the filesystem to be refilled.
         * If this is the last buffer, we just complete. */
        success = WSAGetOverlappedResult(SocketID,
                                         &(buffer_pool[pool_ptr].overlapped),
                                         &bytesread, FALSE, &flags);
        if(buffer_pool[i].last_buffer == TRUE)
        {
            // we're done
            break;
        }
        buffer_pool[pool_ptr].overlapped.Offset = file_ptr;
        file_ptr += DATA_BUF_SIZE;
        if(ReadFile(FileHandle, buffer_pool[pool_ptr].data.buf,
                    DATA_BUF_SIZE, &bytes_written,
                    &(buffer_pool[pool_ptr].overlapped)) == TRUE)
        {
            // function succeeded immediately.  Set the event object ourselves
            SetEvent(buffer_pool[pool_ptr].overlapped.hEvent);
        }
        file_ptr += DATA_BUF_SIZE;    
        if(++pool_ptr >= BUF_POOL_SIZE)
            pool_ptr = 0;
    }
    /* when bytesread is zero, we're done
    while(bytesread > 0 );
    ExitThread(0L);
}
/* Everytime a write to the filesystem completes, take the buffer, and call 
 * WSARecv() so that the network subsystem can fill it back up. */
DWORD FileThread(LPVOID threadparams)
{
    int nRc;
    int pool_ptr = 0;
    DWORD bytesread, flags;
    BOOL success;
    int file_ptr = 0;
    do
    {
        /* We don't need to check the return code. Since we specified INFINITE,
         * the only time it will return is when our event is signalled. */
        WaitForSingleObject(&(buffer_pool[pool_ptr].overlapped.hEvent),
                            INFINITE);
        success = GetOverlappedResult(FileHandle,
                                      &(buffer_pool[pool_ptr].overlapped),
                                      &bytesread, FALSE);
        /* If we've received the event notification, but the number
         * of bytes is zero, then we've reached EOF. */
        if(bytesread == 0)
        {
            buffer_pool[i].last_buffer == TRUE;
            // set the event
            SetEvent(buffer_pool[pool_ptr].overlapped.hEvent);
            // we're done reading
            break;
        }
        buffer_pool[pool_ptr].overlapped.Offset = file_ptr;
        file_ptr += bytesread;
        /* This buffer is ready to be returned to protocol stack for sending */
        if(WSASend(SocketID, &(buffer_pool[pool_ptr].data), 1, &bytesread,
                   &flags, &(buffer_pool[pool_ptr].overlapped), NULL) == 0)
        {
            // function succeeded immediately.  Set the event object ourselves
            SetEvent(buffer_pool[pool_ptr].overlapped.hEvent);
        }
        if(firstpass)
        {
            // This is the first buffer to be filled.  Create the
            // thread that handles completion of file I/O.
            network_thread = CreateThread(NULL, 0, NetworkThread, threadparams,
                                        0, // run immediately 
                                        &network_thread_id);
            firstpass = FALSE;
        }
        if(++pool_ptr >= BUF_POOL_SIZE)
            pool_ptr = 0;       
    }
    /* when bytesread is zero, we're done
    while(bytesread > 0 );
    /* Wait for the network thread to complete */
    ExitThread(0L);
}


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.