【问题标题】:Sending files through a poco stream socket通过 poco 流套接字发送文件
【发布时间】:2015-10-13 13:26:12
【问题描述】:

我必须说的第一件事是,这篇文章的灵感来自 [Remy Lebeau][1]https://stackoverflow.com/users/65863/remy-lebeau 在此处的问题的回答:

Send binary file over TCP/IP connection

我正在使用 poco 库和 C++。

消息头

目标是通过互联网发送一些大的二进制文件。因此,为了实现这一点,我使用以下消息头:

class FileDesc
{
  size_t file_size = 0;
  size_t chunk_size = 0;
  size_t file_name_len = 0;
  char file_name[0];

public:

  FileDesc(size_t file_sz, size_t buf_sz) 
    : file_size(ByteOrder::toNetwork(file_sz)), 
      chunk_size(ByteOrder::toNetwork(buf_sz)) {}

  size_t get_file_size() const { return ByteOrder::fromNetwork(file_size); }

  size_t get_chunk_size() const { return ByteOrder::fromNetwork(chunk_size); }

  string get_file_name() const 
  {
    const auto len = get_file_name_len();
    string ret; ret.reserve(len + 1);
    for (size_t i = 0; i < len; ++i)
      ret.push_back(file_name[i]);
    return ret;
  }

  size_t get_file_name_len() const 
  {
    return ByteOrder::fromNetwork(file_name_len); 
  }

  size_t size() const 
  {
    return sizeof(*this) + ByteOrder::fromNetwork(file_name_len);
  }

  static shared_ptr<FileDesc> allocate(size_t file_sz, size_t buf_sz, 
                       const string & name)
  {
    const auto n = name.size();
    const size_t sz = sizeof(FileDesc) + n;
    shared_ptr<FileDesc> ret((FileDesc*) malloc(sz), free);
    new (ret.get()) FileDesc(file_sz, buf_sz);
    memcpy(ret->file_name, name.data(), n);
    ret->file_name_len = ByteOrder::toNetwork(n);
    return ret;
  }
};

文件将按固定长度的块发送,可能最后一个除外。此标头在传输开始时发送。发送方通知接收方将发送file_size 字节的文件,大小为chunk_size 的块,并且文件名具有file_name_len 字符。在此消息的末尾,通过字符数组file_name 划定文件名,其长度是可变的并且取决于文件名长度。静态方法allocate 负责为这个头分配足够的大小来存储完整的文件名。

发送者例程

这里是发件人部分的代码:

bool send_file(StreamSocket & sock, const string & file_name, long buf_size)
{
  stringstream s;

  ifstream file(file_name, ios::binary | ios::ate);
  if (not file.is_open() or not file.good()) // verify file reading
    {
      s << "cannot open file " << file_name;
      throw domain_error(s.str());
    }
  long file_size = file.tellg();
  if (file_size == 0)
    {
      s << "file " << file_name << " has zero bytes";
      throw domain_error(s.str());
    }
  file.seekg(0);

  unique_ptr<char[]> chunk(new char[buf_size]);

  auto desc_ptr = FileDesc::allocate(file_size, buf_size, file_name);

    // send the file description
  int status = sock.sendBytes(desc_ptr.get(), desc_ptr->size());
  if (status != desc_ptr->size())
    return false;

  do // now send the file
    {
      long num_bytes = min(file_size, buf_size);
      file.read(chunk.get(), num_bytes);

      char * pbuf = chunk.get();
      auto n = num_bytes;
      while (n > 0)
      {
        status = sock.sendBytes(pbuf, n);
        pbuf += status;
        n -= status;
      }

      file_size -= num_bytes;
    } 
  while (file_size > 0);

  char ok;  // now I wait for receiving part notifies reception
  status = sock.receiveBytes(&ok, sizeof(ok)); 

  return ok == 0;
}

send_file(sock, file_name, buf_size) 打开文件file_name 并通过poco 套接字sock 以大小为buf_len 的块发送其内容。所以它会执行file_size/buf_len传输加上文件描述符的初始传输。

接收器部分

接收端实例化一个pocoServerSocket如下:

  ServerSocket server_socket(addr);
  auto sock = server_socket.acceptConnection();
  auto p = receive_file(sock, 256);

receive_file(sock, 256) 表示在套接字sock 将收到一个最大文件长度为 256 字节的文件(对于我的应用程序来说足够了)。这个例程是send_file()的counter部分,保存文件,实现如下:

pair<bool, string> 
receive_file(StreamSocket & sock, size_t max_file_name_len)
{
  stringstream s;
  size_t buf_sz = sizeof(FileDesc) + max_file_name_len;
  unique_ptr<char[]> buffer(new char[buf_sz]);
  int num = sock.receiveBytes(buffer.get(), buf_sz);
  FileDesc * msg = reinterpret_cast<FileDesc*>(buffer.get());
  if (msg->size() > num)
    return make_pair(false, "");

  long chunk_size = msg->get_chunk_size();
  if (chunk_size == 0)
    throw domain_error("chunk size is zero");

  const string file_name = msg->get_file_name();

  ofstream file(file_name, ios::binary);
  if (not file.is_open() or not file.good())
    {
      s << "cannot create file" << file_name;
      throw domain_error(s.str());
    }

  long file_size = msg->get_file_size();
  unique_ptr<char[]> chunk(new char[chunk_size]);

  do
    {   // num_bytes is the quantity that I must receive for this chunk
      auto num_bytes = min(file_size, chunk_size); 
      char * pbuf = chunk.get();
      long n = num_bytes; 
      while (n > 0)
       {
         num = sock.receiveBytes(pbuf, n, 0); // <-- here is potentially the problem
         if (num == 0) // connection closed?
           return make_pair(file_size == 0, file_name);

         pbuf += num;
         n -= num;
       }

      file.write(chunk.get(), num_bytes);
      file_size -= num_bytes;
    }
  while (file_size > 0);

  char ok;
  num = sock.sendBytes(&ok, sizeof(ok)); // notify reception to sender

  return make_pair(true, file_name);
}

receive_file() 返回一个pair&lt;bool,string&gt;,其中first 表示接收已正确完成,second 表示文件名。

问题

由于我无法理解的原因,有时,当num = sock.receiveBytes(pbuf, n, 0) 接收的字节数少于预期的n 并且循环完成读取剩余部分时,仅对应于最后一个发送块的最后一个接收。

奇怪的是,直到现在,我的问题只发生在我使用环回 (127.0.0.1) 时,这对于测试来说非常实用,因为我在我的机器上测试了所有内容。相反,当在两个不同的对等点之间传输时,一切正常,当然并不表明一切都正确。

所以,如果有人能帮助我批评我的实现并最终指出问题出在哪里,我将不胜感激

【问题讨论】:

    标签: c++ sockets tcp poco-libraries


    【解决方案1】:

    如果你不希望 I/O 阻塞,你应该将 socket 设置为non-blocking mode

    但是,看起来一件简单的事情过于复杂了;这是一个简单的例子:

    $ fallocate -l 1G test.in
    $ ls -al test.in
    -rw-r--r-- 1 alex alex 1073741824 Oct 15 23:31 test.in
    $ ls -al test.out
    ls: cannot access test.out: No such file or directory
    $./download
    $ ls -al test.out 
    -rw-rw-r-- 1 alex alex 1073741824 Oct 15 23:32 test.out
    $ diff test.out test.in 
    $
    

    所以,我们很好 - 文件是 1G 并且相同(尽管很无聊)。

    客户端代码:

    #include "Poco/StreamCopier.h"
    #include "Poco/FileStream.h"
    #include "Poco/Net/SocketAddress.h"
    #include "Poco/Net/ServerSocket.h"
    #include "Poco/Net/StreamSocket.h"
    #include "Poco/Net/SocketStream.h"
    
    using namespace Poco;
    using namespace Poco::Net;
    
    int main()
    {
            StreamSocket ss;
            ss.connect(SocketAddress("localhost", 9999));
            SocketStream istr(ss);
    
            std::string file("test.out");
            Poco::FileOutputStream ostr(file, std::ios::binary);
            StreamCopier::copyStream(istr, ostr);
    
            return 0;
    }
    

    这是服务器:

    #include "Poco/Net/SocketAddress.h"
    #include "Poco/Net/Socket.h"
    #include "Poco/Net/StreamSocket.h"
    #include "Poco/Net/ServerSocket.h"
    #include "Poco/Net/SocketStream.h"
    #include "Poco/StreamCopier.h"
    #include "Poco/Timespan.h"
    #include "Poco/FileStream.h"
    
    using namespace Poco;
    using namespace Poco::Net;
    
    int main()
    {
            ServerSocket srvs(SocketAddress("localhost", 9999));
            Timespan span(250000);
            while (true)
            {
                    if (srvs.poll(span, Socket::SELECT_READ))
                    {
                            StreamSocket strs = srvs.acceptConnection();
                            SocketStream ostr(strs);
                            std::string file("test.in");
                            Poco::FileInputStream istr(file, std::ios::binary);
                            StreamCopier::copyStream(istr, ostr);
                    }
            }
            return 0;
    }
    

    享受POCO

    【讨论】:

    • OMG,非常令人印象深刻和简洁!我开始认为 poco 真的是一个非常好的图书馆。我改变了一点你的sn-p;基本上我将FileInputStream 放在客户端部分,将FileOutputStream 放在服务器部分。也许有些事情我不明白,但效果很好。如果我错了,请纠正我。无论如何,我会将您的答案标记为已接受,非常感谢!
    猜你喜欢
    • 2016-04-14
    • 2011-02-24
    • 2019-04-21
    • 2011-04-11
    • 2012-07-12
    • 2015-08-28
    • 2021-07-06
    相关资源
    最近更新 更多