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,现有代码直接读取旧文件,不会覆盖生成新的导出内容,需在导出前先删除已存在的临时文件。 - 文件发送逻辑错误:
- 每次循环固定发送3字节效率极低,且没有调用
WSAGetOverlappedResult获取重叠IO实际发送的字节数,直接按预设的3字节累加文件偏移,一旦实际发送字节数不足3字节会导致文件偏移错乱、内容截断。 - 重叠IO的事件对象没有在每次等待完成后手动重置为无信号状态,后续循环的等待逻辑会直接返回,不等待实际发送完成。
- 不需要加
Sleep(100),等待事件返回就代表发送完成,多余的睡眠只会拖慢传输速度。
- 每次循环固定发送3字节效率极低,且没有调用
客户端错误
- 连接地址错误:
dwIPv4初始化为0,没有调用inet_pton将localhost转换为127.0.0.1赋值给连接地址,部分系统环境下会导致连接失败或连接到非预期地址。 - 接收逻辑完全错误:
- 每次调用
recv都把数据写到cRecvbuf的起始位置,新收到的数据直接覆盖之前的内容,没有按已接收长度做偏移追加,之前收到的字节全部丢失。 memcpy拷贝长度写的是sizeof(cRecvbuf)*2,cRecvbuf是堆指针,sizeof只能拿到指针本身的长度(32位下4字节、64位下8字节),不仅拷贝长度完全错误,还会造成堆内存越界写入,触发未定义行为。且cRecvbuf2每次也从起始位置写入,历史数据同样被覆盖,完全没有存在的意义。
- 每次调用
- 长度计算逻辑错误:传输的是
RegSaveKey导出的二进制注册表文件,不是以\0结尾的C风格字符串,用strlen计算长度会在遇到第一个0x00字节时直接截断,得到的长度远小于实际接收长度。 - 数据解析逻辑错误:二进制注册表文件不是UTF-8编码的文本,直接调用
MultiByteToWideChar按UTF-8转宽字符会得到乱码,且转换失败概率极高。 - 内存泄漏:
cRecvbuf2分配后从来没有释放,会造成内存泄漏。
修复方向
- 服务端初始化Winsock2.2版本,修复权限判断、临时文件覆盖逻辑,发送文件时调整单次发送块大小(比如64KB),每次重叠操作完成后重置事件、取实际发送字节数更新文件偏移。
- 客户端正确解析服务器地址,接收数据时维护已接收字节偏移,每次
recv把数据写到缓冲区偏移位置,直到recv返回0(服务端正常关闭连接)为止,累计总接收长度。 - 二进制文件不要用字符串相关函数处理长度、转码,接收完成后直接把缓冲区内容写入本地文件即可,不需要转宽字符。
内容的提问来源于stack exchange,提问作者JeffR
相关产品推荐
相关产品推荐

