【问题标题】:Sending files in socket programming tcp在socket编程tcp中发送文件
【发布时间】:2015-06-16 20:07:32
【问题描述】:

我正在尝试实现一个简单的文件传输。下面是我一直在测试的两种方法:

方法一:发送和接收不拆分文件。 为了便于测试,我对文件大小进行了硬编码。

发件人:

send(sock,buffer,107,NULL); //sends a file with 107 size

接收者:

char * buffer = new char[107];                      
recv(sock_CONNECTION,buffer,107,0);

std::ofstream outfile (collector,std::ofstream::binary);
outfile.write (buffer,107); 

输出与预期一致,文件没有损坏,因为我发送的.txt 文件包含与原始文件相同的内容。

方法二:在接收端拆分内容进行发送和接收。每个循环 5 个字节。

发件人:

send(sock,buffer,107,NULL);   

接收者:

char * buffer = new char[107];                      //total file buffer
char * ptr = new char[5];                           //buffer
int var = 5;                
int sizecpy = size; //orig size

while(size > var ){                                //collect bytes

    recv(sock_CONNECTION,ptr,5,0);

    strcat(buffer,ptr);                     //concatenate
    size= size-var;     //decrease
    std::cout<<"Transferring.."<<std::endl;

    }

    std::cout<<"did it reach here?"<<std::endl;
    char*last = new char[size];

    recv(sock_CONNECTION,last,2,0);    //last two bytes
    strcat(buffer,last);    
    std::ofstream outfile (collector,std::ofstream::binary);
    outfile.write (buffer,107); 

输出:文本文件包含无效字符,尤其是在开头和结尾处。

问题:我怎样才能使方法 2 起作用?大小相同,但产生不同的结果。方法2的原始文件和新文件的相似度约为98~99%,而方法1的相似度为100%。传输文件的最佳方法是什么?

【问题讨论】:

  • 您的网络堆栈有望阻止您一次仅通过网络发送 5 个字节。赞美纳格尔!!
  • 我使用了 5 个字节以便于测试(因为 .txt 文件的大小很小,并且内容很容易比较)并知道我的方法是否有效。如果是这样,那么我计划将其增加到喜欢 1024 或其他东西。
  • strcat 可能会杀了你。您发送的内容不一定会以 NULL 结尾,因此 strcat 将不知道在哪里结束。
  • 您忽略了recv 的返回值。它返回 实际 读取了多少字节 - 您总是假设读取了完整的 5 个字节,但情况不一定如此。只需确保使用 recv 的返回值而不是 var
  • 您不能将strcat() 用于初始化缓冲区。它会在追加之前通过内存寻找空值。

标签: c++ sockets tcp


【解决方案1】:

传输文件的最佳方法是什么?

通常我不会回答诸如什么是最好的方法之类的问题。但在这种情况下很明显:

  1. 您在开始传输时以网络字节顺序发送了文件大小和校验和
  2. 可选择发送更多标头数据(例如文件名)
  3. 客户端读取文件大小和校验和,并将其解码为主机字节顺序
  4. 您以合理大小的块发送文件数据(5 字节不是合理的大小),块应与 tcp/ip 帧最大可用负载大小匹配
  5. 您在客户端逐块接收,直到与之前发送的文件大小匹配
  6. 您在客户端计算收到的数据的校验和,并检查它是否与之前收到的数据匹配

注意:您不需要在客户端将内存中的所有块组合在一起,只需将它们附加到存储介质上的文件中即可。此外,校验和(CRC)通常可以通过运行数据块来计算。

【讨论】:

  • 我所做的是......我首先发送包含文件名和文件大小的标题,然后我计划循环直到满足该文件大小。
  • o 我还想问一下,我是否需要在客户端也拆分文件大小?或者我可以一次性发送它们吗?
  • 我正在努力使我的代码灵活,我在想如果文件大小像 14000000 字节会怎样。
  • @CarloBrew TCP 会为您解决这个问题。它会将您的数据分成小块。我能想到的唯一原因是提供用户反馈。
【解决方案2】:

不同意 Galik。最好不要使用 strcat、strncat 或除预期输出缓冲区之外的任何东西。

TCP 很有趣。你永远不知道你会得到多少数据,但你会得到它或一个错误。

这将一次读取最多 MAX 个字节。 #defineMAX 随心所欲。

std::unique_ptr<char[]> buffer (new char[size]);
int loc = 0; // where in buffer to write the next batch of data
int bytesread; //how much data was read? recv will return -1 on error                        

while(size > MAX)
{                                //collect bytes
    bytesread = recv(sock_CONNECTION,&buffer[loc],MAX,0);
    if (bytesread < 0)
    {
         //handle error.
    }
    loc += bytesread;
    size= size-bytesread;     //decrease
    std::cout<<"Transferring.."<<std::endl;
}
bytesread = recv(sock_CONNECTION,&buffer[loc],size,0);
if (bytesread < 0)
{
     //handle error
}

std::ofstream outfile (collector,std::ofstream::binary);
outfile.write (buffer.get(),size); 

更有趣的是,写入输出缓冲区,这样您就不必存储整个文件。在这种情况下,MAX 应该是一个更大的数字。

std::ofstream outfile (collector,std::ofstream::binary);
char buffer[MAX];
int bytesread; //how much data was read? recv will return -1 on error                        

while(size)
{                                //collect bytes
    bytesread = recv(sock_CONNECTION,buffer,MAX>size?size:MAX,0);
    // MAX>size?size:MAX is like a compact if-else: if (MAX>size){size}else{MAX}
    if (bytesread < 0)
    {
         //handle error.
    }
    outfile.write (buffer,bytesread); 
    size -= bytesread;     //decrease
    std::cout<<"Transferring.."<<std::endl;
}

【讨论】:

  • 当我尝试使用std::unique_ptr&lt;char[]&gt; buffer (new char[size]);时,我在outfile.write (buffer,size);这一行出现错误
  • 谢谢。那就是教我不要编译。更正了示例,添加了更好的版本。
  • 方法二的问题是一旦文件低于最大值。让我尝试添加。 ty
  • @CarloBrew 应该由 MAX&gt;size?size:MAX 处理,但它会在 size==0 应该停止的地方爆炸。修复。
  • @CarloBrew 已修复。读取 MAX 个字节直到 size 小于 MAX,然后读取 size 直到没有剩余。
【解决方案3】:

我看到的最初问题是std::strcat。您不能在未初始化的缓冲区上使用它。此外,您没有复制空终止的 c 字符串。您正在复制一个大小的缓冲区。最好使用std::strncat

char * buffer = new char[107];                      //total file buffer
char * ptr = new char[5];                           //buffer
int var = 5;                
int sizecpy = size; //orig size

// initialize buffer
*buffer = '\0'; // add null terminator

while(size > var ){                                //collect bytes

    recv(sock_CONNECTION,ptr,5,0);

    strncat(buffer, ptr, 5); // strncat only 5 chars

    size= size-var;     //decrease
    std::cout<<"Transferring.."<<std::endl;

}

除此之外,您应该真正进行 错误检查,以便套接字库可以告诉您通信是否有任何问题。

【讨论】:

  • 这成功了。谢谢。不过,只有一个问题,这会灵活吗?假设这会增加文件大小。
  • @CarloBrew 以何种方式灵活?您显然需要重新编写它,以便您可以处理可变长度文件。如果是我,我会使用 std::vector&lt;char&gt; 而不是原始分配的字符数组。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-01-27
  • 2015-06-24
  • 2018-01-31
  • 1970-01-01
  • 1970-01-01
  • 2019-07-01
  • 1970-01-01
相关资源
最近更新 更多