Win32API远程桌面服务器接收JPEG截图时抛出std::bad_alloc求助
问题分析与修复方案
核心问题点
- recv错误未处理:当
recv返回-1(网络错误)时,直接把负数加到bytesReceived里,导致bufferLenNetworkOrder读取错误,最终bufferLen变成非法值(超大正数或负数),new char[bufferLen]自然抛出std::bad_alloc。 - 类型不匹配导致溢出:
ntohll返回ULONGLONG类型,却赋值给int变量bufferLen。若截图大小解析后超出int范围,会溢出成负数,new负数大小直接触发异常。 - MAX_BYTES检查太晚:拿到
bufferLen后没有立刻判断合法性,而是先分配内存、接收数据,无效操作已经触发才终止。 - WM_PAINT中做阻塞网络操作:WM_PAINT是窗口绘制消息,在此调用
recv会阻塞窗口线程导致界面卡死,且每次触发WM_PAINT都接收数据,极易造成数据顺序混乱,后续bufferLen完全错误。 - 重复分配内存:代码末尾
imageBuffer.reset(new char[bufferLen])完全多余,既浪费内存又可能引发额外分配异常。 - GlobalFree重复释放:
CreateStreamOnHGlobal第二个参数传TRUE时,释放pStream会自动释放hGlobal,手动调用GlobalFree(hGlobal)会导致双重释放,引发未定义行为。
修复后的代码示例
case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(Chwnd, &ps); // 注意:WM_PAINT仅负责绘制,网络接收需放在独立工作线程中! // 以下先修正内存分配与错误处理问题,线程部分需自行补充 SOCKET selectedSocket = clientSockets[itemIndex]; ULONGLONG bufferLenNetworkOrder = 0; int bytesReceived = 0; int rerr = 0; const ULONGLONG MAX_BYTES = 100000; // 改用ULONGLONG避免类型不匹配 // 接收buffer长度,处理recv错误 while (bytesReceived < sizeof(bufferLenNetworkOrder)) { rerr = recv(selectedSocket, (char*)&bufferLenNetworkOrder + bytesReceived, sizeof(bufferLenNetworkOrder) - bytesReceived, 0); if (rerr == SOCKET_ERROR) { MessageBoxA(NULL, "接收长度时网络错误", "错误", MB_OK); EndPaint(Chwnd, &ps); break; } bytesReceived += rerr; } ULONGLONG bufferLen = ntohll(bufferLenNetworkOrder); // 用ULONGLONG存储长度 if (bufferLen > MAX_BYTES || bufferLen == 0) { MessageBoxA(NULL, "非法的图片长度", "错误", MB_OK); EndPaint(Chwnd, &ps); break; } std::unique_ptr<char[]> imageBuffer; try { imageBuffer.reset(new char[bufferLen]); } catch (const std::bad_alloc& e) { MessageBoxA(NULL, "内存分配失败", "错误", MB_OK); EndPaint(Chwnd, &ps); break; } bytesReceived = 0; while (bytesReceived < bufferLen) { rerr = recv(selectedSocket, imageBuffer.get() + bytesReceived, bufferLen - bytesReceived, 0); if (rerr == SOCKET_ERROR) { MessageBoxA(NULL, "接收图片数据时网络错误", "错误", MB_OK); EndPaint(Chwnd, &ps); break; } if (rerr == 0) { MessageBoxA(NULL, "客户端已断开", "提示", MB_OK); EndPaint(Chwnd, &ps); break; } bytesReceived += rerr; } // 接收完成,处理图片 HGLOBAL hGlobal = GlobalAlloc(GHND, bufferLen); if (!hGlobal) { MessageBoxA(NULL, "全局内存分配失败", "错误", MB_OK); EndPaint(Chwnd, &ps); break; } void* pData = GlobalLock(hGlobal); memcpy(pData, imageBuffer.get(), bufferLen); GlobalUnlock(hGlobal); IStream* pStream = NULL; if (CreateStreamOnHGlobal(hGlobal, TRUE, &pStream) != S_OK) { GlobalFree(hGlobal); // 创建流失败,手动释放内存 MessageBoxA(NULL, "创建流失败", "错误", MB_OK); EndPaint(Chwnd, &ps); break; } Gdiplus::Bitmap bitmap(pStream); Gdiplus::Status status = bitmap.GetLastStatus(); if (status != Gdiplus::Ok) { MessageBoxA(NULL, "图片解析失败", "错误", MB_OK); pStream->Release(); EndPaint(Chwnd, &ps); break; } int imgWidth = bitmap.GetWidth(); int imgHeight = bitmap.GetHeight(); Gdiplus::Graphics graphics(hdc); RECT clientRect; GetClientRect(Chwnd, &clientRect); graphics.DrawImage(&bitmap, 0, 0, imgWidth, imgHeight); // 释放资源:pStream释放时会自动释放hGlobal pStream->Release(); EndPaint(Chwnd, &ps); break; }
关键额外建议
- 迁移网络接收至独立线程:禁止在窗口消息处理函数中做阻塞网络操作,否则窗口会失去响应,且消息触发时机不可控易导致数据混乱。正确做法是开工作线程专门接收图片数据,接收到后通过
PostMessage给窗口发绘制消息,WM_PAINT仅负责绘制已接收的图片。 - 添加数据包校验:传输时可增加魔法数或校验位,确保
bufferLen合法,避免网络丢包或同步错误导致的非法长度。 - 用智能指针管理资源:比如
std::unique_ptr管理动态内存,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Rocka
相关产品推荐
相关产品推荐

