1

I'm using the TransmitFile function and it's doing something different than I would expect.

Minimal example:

#include <mswsock.h>

int main(void)
{
    WSADATA wsa;
    SOCKET ListenSocket, ClientSocket;
    struct sockaddr_in server;

    if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
        return 1;

    if ((ListenSocket = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET)
        return 2;

    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(1234);

    if (bind(ListenSocket, (struct sockaddr *)&server, sizeof(server)) == SOCKET_ERROR)
        return 3;

    listen(ListenSocket, 1);

    if ((ClientSocket = accept(ListenSocket, NULL, NULL)) == INVALID_SOCKET)
        return 4;

    HANDLE hFile = CreateFile("alphabet.txt", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    do {
        if (!TransmitFile(ClientSocket, hFile, 3, 0, NULL, NULL, 0))
            return 5;
        Sleep(1000);
    } while (1);
}

Built with MinGW: g++ main.cpp -lws2_32 -lmswsock

A file alphabet.txt must exist and contain:

abcdefghijklmnopqrstuvwxyz

When I run the program and connect (e.g. with PuTTY or netcat) to port 1234, I get:

abcdefdefdefdefdef

I would expect:

abcdefghijklmnopqr
AndreKR
  • 32,613
  • 18
  • 106
  • 168

1 Answers1

2

Using your exact code, I was able to reproduce the issue. Using SetFilePointer() on each loop iteration, I can see that the file position advances to offset 3 after the first send as expected, but never advances beyond offset 3 on subsequent sends, so the same 3 bytes gets re-transmitted over and over. Why it works this way, I have no idea.

I was able to get TransmitFile() to send the correct file blocks (regardless of the flags passed to TransmitFile()) by passing an OVERLAPPED struct to TransmitFile() (specifying FILE_FLAG_OVERLAPPED on CreateFile() or not, it makes no difference), per the documentation:

You can use the lpOverlapped parameter to specify a 64-bit offset within the file at which to start the file data transfer by setting the Offset and OffsetHigh member of the OVERLAPPED structure.

Try something like this:

#include <mswsock.h>

int main(void)
{
    WSADATA wsa;
    SOCKET ListenSocket, ClientSocket;
    struct sockaddr_in server = {};
    HANDLE hFile;

    if (WSAStartup(MAKEWORD(2,2), &wsa) != 0)
        return 1;

    if ((ListenSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
        return 2;

    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(1234);

    if (bind(ListenSocket, (struct sockaddr *)&server, sizeof(server)) == SOCKET_ERROR)
        return 3;

    if (listen(ListenSocket, 1) == SOCKET_ERROR)
        return 4;

    if ((ClientSocket = accept(ListenSocket, NULL, NULL)) == INVALID_SOCKET)
        return 5;

    if ((hFile = CreateFileA("alphabet.txt", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
        return 6;

    ULARGE_INTEGER ul;
    ul.LowPart = GetFileSize(hFile, &ul.HighPart);

    if ((ul.LowPart == INVALID_FILE_SIZE) && (GetLastError() != 0))
        return 7;

    OVERLAPPED ov = {};
    ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!ov.hEvent)
        return 8;

    unsigned __int64 uiPos = 0;
    unsigned __int64 uiRemaining = ul.QuadPart;

    while (uiRemaining > 0)
    {
        ul.QuadPart = uiPos;

        ov.Offset = ul.LowPart;
        ov.OffsetHigh = ul.HighPart;

        DWORD dwNumToSend = (uiRemaining >= 3) ? 3 : (DWORD)uiRemaining;

        if (!TransmitFile(ClientSocket, hFile, dwNumToSend, 0, &ov, NULL, 0))
        {
            if ((GetLastError() != ERROR_IO_PENDING) && (WSAGetLastError() != WSA_IO_PENDING))
                break;

            WaitForSingleObject(ov.hEvent, INFINITE);
        }

        uiPos += dwNumToSend;
        uiRemaining -= dwNumToSend;

        Sleep(1000);
    }

    closesocket(ClientSocket);
    CloseHandle(ov.hEvent);
    CloseHandle(hFile);

    return 0;
}

Alternatively, instead of using OVERLAPPED, I was was also to make the code work correctly by simply advancing the file position manually after each send, again per the documentation:

If lpOverlapped is a NULL pointer, the transmission of data always starts at the current byte offset in the file.

#include <mswsock.h>

int main(void)
{
    WSADATA wsa;
    SOCKET ListenSocket, ClientSocket;
    struct sockaddr_in server = {};
    HANDLE hFile;

    if (WSAStartup(MAKEWORD(2,2), &wsa) != 0)
        return 1;

    if ((ListenSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
        return 2;

    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(1234);

    if (bind(ListenSocket, (struct sockaddr *)&server, sizeof(server)) == SOCKET_ERROR)
        return 3;

    if (listen(ListenSocket, 1) == SOCKET_ERROR)
        return 4;

    if ((ClientSocket = accept(ListenSocket, NULL, NULL)) == INVALID_SOCKET)
        return 5;

    if ((hFile = CreateFileA("alphabet.txt", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
        return 6;

    ULARGE_INTEGER ul;
    ul.LowPart = GetFileSize(hFile, &ul.HighPart);

    if ((ul.LowPart == INVALID_FILE_SIZE) && (GetLastError() != 0))
        return 7;

    unsigned __int64 uiPos = 0;
    unsigned __int64 uiRemaining = ul.QuadPart;

    while (uiRemaining > 0)
    {
        ul.QuadPart = uiPos;
        if ((SetFilePointer(hFile, (LONG)ul.LowPart, (PLONG)&ul.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER) && (GetLastError() != 0))
            break;

        DWORD dwNumToSend = (uiRemaining >= 3) ? 3 : (DWORD)uiRemaining;

        if (!TransmitFile(ClientSocket, hFile, dwNumToSend, 0, NULL, NULL, 0))
            break;

        uiPos += dwNumToSend;
        uiRemaining -= dwNumToSend;

        Sleep(1000);
    }

    closesocket(ClientSocket);
    CloseHandle(hFile);

    return 0;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Since with OVERLAPPED you're *also* manually keeping track of the position, doesn't the solution with OVERLAPPED add unnecessary complexity compared to the SetFilePointer() solution? – AndreKR Jun 05 '18 at 21:20
  • Not necessarily. The non-OVERLAPPED solution is actually seeking the file position on every loop iteration. The OVERLAPPED solution is not seeking manually. The OVERLAPPED solution allows more work to be done on the kernel side and less on the app side. – Remy Lebeau Jun 05 '18 at 21:43
  • Which Windows version did you use to reproduce this? – AndreKR Jun 06 '18 at 05:28
  • @AndreKR Windows 7 Home Premium 64bit – Remy Lebeau Jun 06 '18 at 16:38
  • @RemyLebeau, I take it this code, on the winsock client, would _not_ receive the binary data stream? `DWORD dwLen = 0, recvbuflen = 65536; CHAR* cRecvbuf [recvbuflen]; do { iResult = recv(ConnectSocket, cRecvbuf, recvbuflen, MSG_WAITALL); } while (iResult > 0);` – JeffR Jul 06 '22 at 21:24
  • 1
    @JeffR Why do you think that? In that code, `cRecvbuf` is a ([non-standard VLA!](https://stackoverflow.com/questions/1887097/)) array of pointers that don't point at any valid memory. The code would "work" only because `sizeof(CHAR*) > 1` and so `(sizeof(CHAR*) * recvbuflen) > recvbuflen`. `recv()` is overwriting the array itself, not where the array's pointers are pointing, so there would be no buffer overflow or invalid memory access. But you really need `cRecvbuf` to be an array of bytes instead of an array of pointers, eg: `BYTE cRecvbuf[recvbuflen];`. – Remy Lebeau Jul 06 '22 at 22:14