首页 > 解决方案 > 通过套接字将文件数据发送到服务器时,我得到了一些垃圾值?为什么?

问题描述

我正在通过 C++ 中的套接字建立客户端服务器连接。我成功地连接了它们,但是在发送文件大小和文件数据时,我也在我的服务器中收到了一些垃圾值。

我首先通过客户端的发送系统调用发送文件大小,然后将文件缓冲区发送到服务器。

我在成功接收文件大小的服务器中有recv系统调用,但是在一些字节后获取文件数据时,我得到了垃圾。

客户代码

File = fopen("index.jpg", "rb");
if (!File)
{
    MessageBox(L"Error while readaing the file");

}
fseek(File, 0, SEEK_END);
Size = ftell(File);
fseek(File, 0, SEEK_SET);

char* Buffer = new char[Size];

fread(Buffer, Size, 1, File);



char cSize[MAX_PATH];
sprintf(cSize, "%lu", Size);



send(Socket, cSize, MAX_PATH, 0); // File size
send(Socket, Buffer, Size, 0); // File Binary

服务器代码

          unsigned long Size;
    char *Filesize = new char[1024];

    if (recv(Sub, Filesize, 1024, 0)) // File size
    {
       Size = strtoul(Filesize, NULL, 0);  //getting filesize
    }


    Buffer = new char[Size];
    int reader = recv(Sub, Buffer, Size, 0);
    Buffer[Size] = '\0';
    if (reader == -1) // File Binary
    {
        MessageBox(L"Perror Recv");
    }
    else if (reader == 0)
    {
        MessageBox(L"Connection is Closed");
    }
    else
    {
        FILE *File;
        File = fopen("test.jpg", "wb");
        fwrite((const char*)Buffer, 1, Size, File);  
        MessageBox(L"DATA Received");
        fclose(File);
    }

标签: c++sockets

解决方案


一个问题是您没有recv()正确处理返回值。例如:

if (recv(Sub, Filesize, 1024, 0)) // File size

...当上面引用的函数返回时,它已将一些字节数(大于 0,小于 1025)写入Filesize. 多少?您的程序不知道,因为您没有将返回值存储到变量中以查找(而是您只检查它是否非零,然后丢弃该值)。因此,很可能不仅Filesize包含您的文件大小值,还包含文件数据的某些部分......这就是为什么您的程序稍后不会将这部分文件数据写入磁盘的原因。

这里有一个类似的问题:

int reader = recv(Sub, Buffer, Size, 0);

您检查reader它是否是-1or 0(这很好),但在您的最后一种情况下,您只是从数组中fwrite()取出字节,当包含字节而不是字节(并且可以具有和之间的任何值,具体取决于 TCP 堆栈的字节数决定在那个特定的电话中交付给您。SizeBufferreaderSizereader1Sizerecv()

另一个问题是您发送MAX_PATH文件大小的字节,但您收到(最多)1024 个字节的文件大小。MAX_PATH等于1024 ?如果没有,那么即使recv()确实填写了所有 1024 个字节,您的发送方和接收方仍然会彼此不同步,因为多余的字节会出现在未来的recv()调用中,或者(或者)您会从后续send()调用中获取字节放入您的 FileSize 缓冲区。

这就是直接的问题——我认为根本的问题是您对 TCP 网络的工作方式做出了一些不正确的假设。尤其:

  • send()不保证和recv()调用之间的一一对应关系。(TCP 是一种字节流协议,不做任何数据分帧)

  • 您不能依赖从单个调用到send()通过单个调用传递的N 字节数据recv()send()您将按顺序传递的字节,但不能保证需要多少次调用才能recv()全部接收它们,也不保证任何给定调用recv()将写入您的接收缓冲区的字节数。

  • 您不能依靠recv()填满您传递给它的整个缓冲区。 recv()将写入尽可能少或尽可能多的字节,并且无论每次recv()调用获得多少字节,都取决于您的代码是否正确处理它。

在实践中,这意味着您需要recv()循环调用,并仔细跟踪每次recv()调用的返回值,因此您始终知道到目前为止您收到了多少字节,因此下一次recv()调用在缓冲区内的位置应该开始写作。


推荐阅读