Winsock传输二进制文件时接收端缺失前4字节问题排查
Winsock二进制文件接收缺失前4字节问题根因与修复
核心根因
丢失的4字节恰好是客户端发送的网络字节序文件长度字段,问题完全出在服务端接收逻辑的冗余读操作上:
- 客户端发送流的顺序固定为:先发送4字节
ULONG类型的文件总长度(转网络字节序),再按顺序发送完整文件内容。 - 服务端进入
ReceiveDoWorkThenReturnDataToClient函数后,首先执行了一次多余的recv(sd, acReadBuffer, 1024, 0)调用,调试看到首次返回值为4,就是这一步把客户端发的前4字节(文件长度值)读到了acReadBuffer缓冲区中,这部分数据既没有被解析为文件长度,也没有写入目标文件,直接被丢弃。 - 后续服务端调用
readBytes(sd, &FileSize, sizeof(FileSize))尝试读取4字节长度时,TCP流最开头的4字节已经被前面的冗余recv取走,此时读到的是文件内容的前4字节,服务端把这4个字节错误解析为文件长度,再读取后续流内容写入文件,最终保存的文件就刚好少了开头4个字节,文件总大小却和源文件一致——因为错误解析出的长度值刚好和原文件长度偏差4字节,读完剩余流的总写入长度刚好等于原文件大小。
附带的逻辑缺陷
除了核心的冗余recv问题,服务端代码还有两个会导致异常的逻辑错误:
- 文件打开/删除逻辑放错位置:内层循环每次迭代都会执行
DeleteFile删旧文件、重新_wfopen_s开新文件,只要单次recv返回的数据长度大于1,就会反复清空已写入内容,最终只能保留最后一次写入的块。 - 回包逻辑存在内存越界:
send(sd, "1" + nSentBytes, (INT)strlen("1") - nSentBytes, 0)做了错误的指针运算,当nSentBytes大于0时,字符串指针会向后偏移越界,发送的内容是非法内存数据。
修复方案
- 删除
ReceiveDoWorkThenReturnDataToClient中最外层多余的acReadBuffer预读recv逻辑,连接建立后直接读取4字节长度字段,再按长度读取文件内容即可。 - 将删除旧文件、打开目标文件的逻辑移到接收流程最外层,整个文件接收过程只打开一次文件句柄,全部内容写入完成后再关闭句柄。
- 修正回包逻辑,文件接收完成后直接给客户端发送固定1字节的确认符即可,不需要循环拼接发送。
修正后的接收函数参考实现:
BOOL ReceiveDoWorkThenReturnDataToClient(CONST SOCKET sd) { WCHAR sTempFileOut[MAX_PATH] = L"E:\\temp\\newjpg.jpg"; // 接收前先删旧文件、打开新文件,仅执行一次 DeleteFile(sTempFileOut); FILE* fp = NULL; errno_t err = _wfopen_s(&fp, sTempFileOut, L"wb"); if (fp == NULL) return FALSE; // 读取4字节文件长度字段 ULONG FileSize; int iResult = readBytes(sd, &FileSize, sizeof(FileSize)); if (iResult <= 0) { fclose(fp); return FALSE; } FileSize = ntohl(FileSize); // 按长度循环读取文件内容写入磁盘 CHAR mfcc[1024]; while (FileSize > 0) { int Received = readBytes(sd, mfcc, min(sizeof(mfcc), FileSize)); if (Received <= 0) { fclose(fp); return FALSE; } if (fwrite(mfcc, 1, Received, fp) != Received) { fclose(fp); return FALSE; } FileSize -= Received; } fflush(fp); fclose(fp); // 向客户端回传1字节确认,修复原指针越界问题 const char ack = '1'; send(sd, &ack, 1, 0); return TRUE; }
内容的提问来源于stack exchange,提问作者JeffR
相关产品推荐
相关产品推荐

