TransmitFle internal

>> Thursday, October 15, 2009


You may want to know the internal of the winsock function TransmitFile. Here's the tip.

Prototype :


BOOL
TransmitFile (
IN SOCKET SocketHandle,
IN LPTSTR FileName
)



Arguments:

SocketHandle - a connected socket handle. This routine will change
the SO_SNDBUF setting of the socket to zero so that no bufferring
of sends occurs.

FileName - the name of the file to transmit over the socket. The
caller must have read access to the socket.

Internally, there're 3 situations : the file is small, big and very big. For each situation, this function behaves differently.
At first, we declare variables:
HANDLE fileHandle;
BY_HANDLE_FILE_INFORMATION fileInformation;
HANDLE fileMapping;
PBYTE fileBuffer;
DWORD bytesSent;
DWORD fileSize;
INT i;
INT err;
BOOL ret;
INT bytesToSend;
DWORD bytesWritten;
DWORD bytesPending;
HANDLE events[PEND_COUNT];
OVERLAPPED overlapped[PEND_COUNT];


I think they're seft-explained variables.
If the file is small, just use normal, synchronouse I/O to transmit the file. The behavious likes that:
i = SYNC_IO_LENGTH; 
//SYNC_IO_LENGTH is the size of individual synchronous I/O requests that we submit.

err = setsockopt(SocketHandle, SOL_SOCKET, SO_SNDBUF, (char*) &i, sizeof(i));
if (err < 0)
{
return FALSE;
}

for (bytesSent = 0; bytesSent < fileSize; bytesSent += i)
{

bytesToSend = MIN(SYNC_IO_LENGTH, fileSize - bytesSent);

i = send(SocketHandle, fileBuffer + bytesSent, bytesToSend, 0);
if (i < 0)
{
return FALSE;
}
}



It's straight-forward here. For small files, the overhead associated with asynchronous I/O outweighs the advantages.

If the file is big. We're going to use asynchronous I/O to transmit the file. Set the send buffer size on the socket to zero. This prevents send bufferring from occurring. Normally applications want send bufferring, but because we're going to be very careful about keeping plenty of data around for TCP/IP to send and we want maximum performance, avoiding the extra buffer copy is beneficial.
i = 0;

err = setsockopt(SocketHandle, SOL_SOCKET, SO_SNDBUF, (char*) &i, sizeof(i));
if (err < 0)
{
return FALSE;
}

//
// If the file isn't too big, send it all with a single
// WriteFile() call.
//

if (fileSize < MULTI_CALL_THRESHOLD)
{

//
// Initialize the overlapped structure that we'll use for
// the I/O. Rather than waiting on an event, we'll just use
// the GetOverlappedResult() API to learn of I/) completion.
//
// See <a href="http://msdn.microsoft.com/en-us/library/ms684342(VS.85).aspx">here</a> for more information about struct OVERLAPPED
overlapped[0].Internal = 0;
overlapped[0].InternalHigh = 0;
overlapped[0].Offset = 0;
overlapped[0].OffsetHigh = 0;
overlapped[0].hEvent = NULL;

//
// Send the entire file in one big chunk. The system will
// send it directly out of the file cache onto the wire.
//

ret = WriteFile((HANDLE)SocketHandle, fileBuffer, fileSize, &bytesWritten,
&overlapped[0]);

if (!ret)
{

//
// If the write failed with any error except the one
// which indicates that the I/O was successfully pended,
// fail.
//

if (GetLastError() != ERROR_IO_PENDING)
{
return FALSE;
}

//
// Wait for the pended write operation and determine
// whether it was successful.
//

ret = GetOverlappedResult((HANDLE)SocketHandle, &overlapped[0],
&bytesWritten, TRUE);
if (!ret)
{
return FALSE;
}
}

//
// The entire file was transmitted successfully.
//

return TRUE;
}



And the last situation, this is a big file that we're going to use multiple asynchronous I/O calls to transmit. Open the events that we'll use to wait for I/O to complete and initialize the overlapped structures that we'll use.

Firstly, initialize array of events:
for (i = 0; i < PEND_COUNT; i++)
{

events[i] = CreateEvent(NULL, FALSE, TRUE, NULL);
if (events[i] == NULL)
{
return FALSE;
}

overlapped[i].Internal = 0;
overlapped[i].InternalHigh = 0;
overlapped[i].Offset = 0;
overlapped[i].OffsetHigh = 0;
overlapped[i].hEvent = events[i];
}



Now loop through the file data writing it to the socket asynchronously. Whenever a write completes, pend a new write request so that we always have several writes outstanding and TCP/IP always has data available to send.
bytesSent = 0;
bytesPending = 0;

while (TRUE)
{

//
// Wait for one of the I/O events to become set. This signals
// us that we can send some data on the socket.
//

i = WaitForMultipleObjects(PEND_COUNT, events, FALSE, INFINITE) -
WAIT_OBJECT_0;

//
// Determine if the pended write operation was successful.
//

ret = GetOverlappedResult((HANDLE)SocketHandle, &overlapped[i],
&bytesWritten, FALSE);
if (!ret)
{
return FALSE;
}

//
// Update the count of bytes that we've sent from the file
// thus far. Note that if this is the first pass through
// the loop, GetOverlappedResult() will return 0 bytes
// written because no actual WriteFile() calls have been
// made yet, and we initialized the overlapped structures
// above.
//

bytesSent += bytesWritten;
bytesPending -= bytesWritten;

//
// Check whether we have completed sending the file.
//

if (bytesSent == fileSize)
{
return TRUE;
}

//
// Check whether we're nearly done sending the file and have
// pended writes for all the file data. If this is the case,
// just reset the event which got hit and wait for the
// remainder of the pended sends to complete.
//

if (bytesSent + bytesPending == fileSize)
{

ret = ResetEvent(events[i]);
if (!ret)
{
return FALSE;
}

continue;
}

//
// Issue a write from the file buffer to the socket.
// Typically, this write will return ERROR_IO_PENDING which
// means that the write is in progress and has not completed
// yet. The event will be set by the system when the write
// completes.
//

bytesToSend = MIN(ASYNC_IO_LENGTH, fileSize - (bytesSent + bytesPending));

ret = WriteFile((HANDLE)SocketHandle, fileBuffer + bytesSent + bytesPending,
bytesToSend, &bytesWritten, &overlapped[i]);
if (!ret && GetLastError() != ERROR_IO_PENDING)
{
return FALSE;
}

bytesPending += bytesToSend;
}


0 comments: