You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Winsock使用TransmitFile无法传输完整文件问题求助

问题现象
  • 基于Winsock实现的C/S文件传输程序,始终无法收到完整的传输文件
  • 客户端接收缓冲区始终无法被传输数据填满
  • 单步调试时观测到服务端进程看似在正常回传数据,但客户端仅经过3次循环调用recv()就提前结束接收流程
  • 代码中涉及Winsock接口、memcpy的使用可能存在逻辑错误
附问题代码

服务端 winsockserver.cpp

#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <strsafe.h>
#include <process.h>
#include <mswsock.h>
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Mswsock.lib")

WCHAR REGPATH[260];

BOOL SetPrivilege() 
{
    HANDLE hToken;
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) != 0)
        {
        LUID luid;
        BOOL bLookupPrivilegeValue = LookupPrivilegeValueW(NULL, L"SeBackupPrivilege", &luid);
        if (bLookupPrivilegeValue == TRUE)
            {
            TOKEN_PRIVILEGES tp;
            tp.PrivilegeCount = 1;
            tp.Privileges[0].Luid = luid;
            tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
            BOOL bAdjustTokenPrivileges = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES) NULL, (PDWORD)NULL);
            return ((bAdjustTokenPrivileges == TRUE) && (GetLastError() == ERROR_SUCCESS));
            }
        }
    return TRUE;
}

SOCKET SetUpListener(CONST CHAR *pcAddress, INT nPort)
{
    DWORD dwWSA = 0;
    INT iBind = 0;
    ULONG nInterfaceAddr = 0;
    inet_pton(AF_INET, pcAddress, &nInterfaceAddr);
    if (nInterfaceAddr != INADDR_NONE) 
        {
        SOCKET sd = socket(AF_INET, SOCK_STREAM, 0);
        if (sd != INVALID_SOCKET) 
            {
            sockaddr_in sinInterface;
            sinInterface.sin_family = AF_INET;
            sinInterface.sin_addr.s_addr = nInterfaceAddr;
            sinInterface.sin_port = nPort;            
            iBind = bind(sd, (sockaddr*) &sinInterface, sizeof(sockaddr_in));
            if (iBind != SOCKET_ERROR)
                {
                listen(sd, SOMAXCONN);
                return sd;
                }
            dwWSA = WSAGetLastError();
            }
        }
    return INVALID_SOCKET;
}

BOOL ReceiveDoWorkThenReturnDataToClient(SOCKET sd)
{    
    CHAR acReadBuffer[1024];
    INT nReadBytes;
    do 
        {
        nReadBytes = recv(sd, acReadBuffer, 1024, 0);
        if (nReadBytes > 0) 
            {
                WCHAR* sTempFileOut = new WCHAR[MAX_PATH]();
                StringCchCopyW(sTempFileOut, MAX_PATH, L"c:\\temp\\~test.txt");

                HKEY key;
                LRESULT dresult = RegOpenKeyExW(HKEY_LOCAL_MACHINE, REGPATH, 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key);
                if (dresult == ERROR_SUCCESS)
                    {
                    DWORD dwErrorBackupPriv = 0;
                    if (SetPrivilege() == TRUE)
                        {
                        LSTATUS dStatus = RegSaveKeyW(key, sTempFileOut, NULL);
                        if ((dStatus == ERROR_SUCCESS) || (dStatus == ERROR_ALREADY_EXISTS))
                            {
                            HANDLE hFile = CreateFileW(sTempFileOut, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
                            if (hFile != INVALID_HANDLE_VALUE)
                                {
                                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(sd, 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(100);
                                    }

                                CloseHandle(ov.hEvent);
                                CloseHandle(hFile);
                                }
                            }
                        }
                    }
                delete[] sTempFileOut;
            }
        else if (nReadBytes == SOCKET_ERROR) 
            return FALSE;
        }
    while (nReadBytes != 0);
    return TRUE;
}

BOOL ShutdownConnection(SOCKET sd)
{
    if (shutdown(sd, SD_SEND) == SOCKET_ERROR) return FALSE;
    CHAR acReadBuffer[1024];
    while (1) 
        {
        INT nNewBytes = recv(sd, acReadBuffer, 1024, 0);
        if (nNewBytes == SOCKET_ERROR) return FALSE;
        else if (nNewBytes == 0) break;
        }
    return (closesocket(sd) != SOCKET_ERROR);
}

DWORD WINAPI ThreadHandler(void* sd_)
{
    INT nRetval = 0;
    SOCKET sd = (SOCKET) sd_;
    if (!ReceiveDoWorkThenReturnDataToClient(sd)) nRetval = 3;
    if (!ShutdownConnection(sd)) nRetval = 4;
    return nRetval;
}

unsigned WINAPI ListenAndAccept(void *)
{
    WSAData wsaData;
    INT nCode;    
    sockaddr_in sinRemote;
    INT nAddrSize = sizeof(sinRemote);
    DWORD nThreadID;

    if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != ERROR_SUCCESS) return nCode;
    SOCKET ListeningSocket = SetUpListener("localhost", htons(43210));
    if (ListeningSocket == INVALID_SOCKET)
        return 3;
    while (1)
        {
        SOCKET sd = accept(ListeningSocket, (sockaddr*)&sinRemote, &nAddrSize);
        if (sd != INVALID_SOCKET)
            CreateThread(0, 0, &ThreadHandler, (void*)sd, 0, &nThreadID);
        else
            break;
        }
    WSACleanup();
    return 0;
}

int main()
{
    UINT threadID = 0;
    StringCchCopy(REGPATH, 260, L"Software\\Microsoft\\Direct3D");
    HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, &ListenAndAccept, NULL, 0, &threadID);
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);

    while (1)
        ;
}

客户端 Winsockclient.cpp

#include <winsock2.h>
#include <windows.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#include <strsafe.h>
#include <windns.h>
#include <process.h>
#include <windns.h>

#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "dnsapi.lib")

DWORD SendWinsockCommandToRemoteServer(CONST DWORD dwCharsAllocatedReturnString, WCHAR **sStringReturned)
{
    SOCKET ConnectSocket = INVALID_SOCKET;
    INT recvbuflen = 65536;
    DWORD dwIPv4 = 0;
    DWORD dwErr = 0;

    SecureZeroMemory(*sStringReturned, dwCharsAllocatedReturnString * sizeof(CHAR));
    ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ConnectSocket == INVALID_SOCKET) 
        return WSAGetLastError();

    sockaddr_in clientService{};
    clientService.sin_family = AF_INET;
    clientService.sin_addr.s_addr = dwIPv4;
    clientService.sin_port = htons(43210);

    INT iResult = connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService));
    if ((iResult != 0) || (iResult == SOCKET_ERROR))
        {
        closesocket(ConnectSocket);
        return WSAGetLastError();
        }

    iResult = send(ConnectSocket, "test", 5, 0);
    if ((iResult == INVALID_SOCKET) || (iResult == SOCKET_ERROR))
        {
        closesocket(ConnectSocket);
        return 5;
        }

    iResult = shutdown(ConnectSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) 
        {
        closesocket(ConnectSocket);
        return 6;
        }

    DWORD dwLen = 0;    
    BYTE * cRecvbuf = (BYTE*) calloc(65536, sizeof(BYTE));
    BYTE * cRecvbuf2 = (BYTE*) calloc(65536, sizeof(BYTE));
    do 
        {
        iResult = recv(ConnectSocket, (CHAR *) cRecvbuf, recvbuflen, MSG_WAITALL);
        memcpy(cRecvbuf2, cRecvbuf, sizeof(cRecvbuf)*2);
        }
    while (iResult > 0);

    DWORD dwLenOfRecvbuf = strlen((CHAR *) cRecvbuf);
    if (dwLenOfRecvbuf > 0)
        dwLen = dwLenOfRecvbuf + 1;

    if (dwLen > 0)
        {
        WCHAR *sRecvbuf = new WCHAR[dwLen]();
        DWORD dwCharWrittenToBuffer = MultiByteToWideChar(CP_UTF8, 0, (CHAR*)cRecvbuf, (INT)dwLen, sRecvbuf, dwLen);
        if (dwCharWrittenToBuffer != 0)
            StringCchCopyW(*sStringReturned, dwLen, sRecvbuf);
        delete[] sRecvbuf;
        free(cRecvbuf);
        if (dwCharWrittenToBuffer == 0)
            return 7;
        }
    else
        {
        free(cRecvbuf);
        return 8;
        }

    closesocket(ConnectSocket);
    return 0;
}


void QueryWinsockServer(CONST DWORD dwDataAllocatedSize, WCHAR** sDataBack, DWORD* dwErrorFromSendWinsockCommandToRemoteServer)
{
    SecureZeroMemory(*sDataBack, dwDataAllocatedSize * sizeof (WCHAR));
    WCHAR *sRV = new WCHAR[dwDataAllocatedSize]();
    *dwErrorFromSendWinsockCommandToRemoteServer = SendWinsockCommandToRemoteServer(dwDataAllocatedSize, &sRV);
    if (*dwErrorFromSendWinsockCommandToRemoteServer == ERROR_SUCCESS)
        {
        DWORD dwLenToCopy = wcslen(sRV) + 1;
        StringCchCopyW(*sDataBack, dwLenToCopy, sRV);
        }
    delete[] sRV;
}

int main(int argc, char **argv) 
{
    DWORD dwDataAllocatedSize = 1024;
    DWORD dwErr = 0;
    WCHAR* sData = new WCHAR[dwDataAllocatedSize]();
    WSADATA wsaData{};

    INT iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    QueryWinsockServer(dwDataAllocatedSize, &sData, &dwErr);

    wprintf(L"Press any key to end.");
    getchar();

    return 0;
}
核心错误排查与修复

服务端错误

  • Winsock版本初始化错误:ListenAndAccept中调用WSAStartup传的是MAKEWORD(1,1),仅初始化Winsock1.1版本,而TransmitFile是Winsock2提供的扩展接口,版本不匹配会导致接口行为异常,需改为MAKEWORD(2,2)。
  • 权限判断逻辑错误:SetPrivilege函数在打开进程令牌失败、查询权限LUID失败时直接返回TRUE,会导致后续RegSaveKey因无备份权限静默失败,需在权限获取失败时返回FALSE并打印错误。
  • 导出文件逻辑错误:RegSaveKeyW遇到目标文件已存在时会返回ERROR_ALREADY_EXISTS,现有代码直接读取旧文件,不会覆盖生成新的导出内容,需在导出前先删除已存在的临时文件。
  • 文件发送逻辑错误:
    1. 每次循环固定发送3字节效率极低,且没有调用WSAGetOverlappedResult获取重叠IO实际发送的字节数,直接按预设的3字节累加文件偏移,一旦实际发送字节数不足3字节会导致文件偏移错乱、内容截断。
    2. 重叠IO的事件对象没有在每次等待完成后手动重置为无信号状态,后续循环的等待逻辑会直接返回,不等待实际发送完成。
    3. 不需要加Sleep(100),等待事件返回就代表发送完成,多余的睡眠只会拖慢传输速度。

客户端错误

  • 连接地址错误:dwIPv4初始化为0,没有调用inet_pton将localhost转换为127.0.0.1赋值给连接地址,部分系统环境下会导致连接失败或连接到非预期地址。
  • 接收逻辑完全错误:
    1. 每次调用recv都把数据写到cRecvbuf的起始位置,新收到的数据直接覆盖之前的内容,没有按已接收长度做偏移追加,之前收到的字节全部丢失。
    2. memcpy拷贝长度写的是sizeof(cRecvbuf)*2,cRecvbuf是堆指针,sizeof只能拿到指针本身的长度(32位下4字节、64位下8字节),不仅拷贝长度完全错误,还会造成堆内存越界写入,触发未定义行为。且cRecvbuf2每次也从起始位置写入,历史数据同样被覆盖,完全没有存在的意义。
  • 长度计算逻辑错误:传输的是RegSaveKey导出的二进制注册表文件,不是以\0结尾的C风格字符串,用strlen计算长度会在遇到第一个0x00字节时直接截断,得到的长度远小于实际接收长度。
  • 数据解析逻辑错误:二进制注册表文件不是UTF-8编码的文本,直接调用MultiByteToWideChar按UTF-8转宽字符会得到乱码,且转换失败概率极高。
  • 内存泄漏:cRecvbuf2分配后从来没有释放,会造成内存泄漏。

修复方向

  1. 服务端初始化Winsock2.2版本,修复权限判断、临时文件覆盖逻辑,发送文件时调整单次发送块大小(比如64KB),每次重叠操作完成后重置事件、取实际发送字节数更新文件偏移。
  2. 客户端正确解析服务器地址,接收数据时维护已接收字节偏移,每次recv把数据写到缓冲区偏移位置,直到recv返回0(服务端正常关闭连接)为止,累计总接收长度。
  3. 二进制文件不要用字符串相关函数处理长度、转码,接收完成后直接把缓冲区内容写入本地文件即可,不需要转宽字符。

内容的提问来源于stack exchange,提问作者JeffR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 19:03:49