通过 poco 流套接字发送文件
Sending files through a poco stream socket
我必须说的第一件事是 post 受到 [Remy Lebeau][1]https://whosebug.com/users/65863/remy-lebeau 对这里问题的回答的强烈启发:
Send binary file over TCP/IP connection。
我正在使用 poco 库和 C++。
留言header
目标是通过互联网发送一些大的二进制文件。因此,为了实现这一点,我使用了以下消息 header:
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;
}
};
文件将按固定长度的块发送,可能最后一个除外。这个header是在传输开始时发送的。发送方部分通知接收方部分 file_size
字节的文件将按大小 chunk_size
的块发送,文件名有 file_name_len
个字符。这条消息的末尾是文件名,通过字符数组 file_name
,其长度是可变的,取决于文件名长度。静态方法 allocate
负责为这个 header 分配足够的大小来存储完整的文件名。
发件人例程
这是发件人部分的代码:
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()
的计数器部分,保存文件并实现如下:
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()
returns a pair<bool,string>
其中 first
表示接收正确,second
文件名。
问题
由于我无法理解的原因,有时,当 num = sock.receiveBytes(pbuf, n, 0)
收到的字节数少于预期的 n
并且循环完成读取余数时,只接收到对应于最后发送块。
奇怪的是,直到现在,我的问题只发生在我使用环回 (127.0.0.1) 时,这对于测试来说非常实用,因为我在我的机器上进行了所有测试。相反,当传输在两个不同的peer之间时,一切正常,当然不代表一切正确。
所以,如果有人能帮我批评我的实现并最终指出问题出在哪里,我将不胜感激
如果你不想I/O阻塞,你应该将套接字设置为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!
我必须说的第一件事是 post 受到 [Remy Lebeau][1]https://whosebug.com/users/65863/remy-lebeau 对这里问题的回答的强烈启发:
Send binary file over TCP/IP connection。
我正在使用 poco 库和 C++。
留言header
目标是通过互联网发送一些大的二进制文件。因此,为了实现这一点,我使用了以下消息 header:
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;
}
};
文件将按固定长度的块发送,可能最后一个除外。这个header是在传输开始时发送的。发送方部分通知接收方部分 file_size
字节的文件将按大小 chunk_size
的块发送,文件名有 file_name_len
个字符。这条消息的末尾是文件名,通过字符数组 file_name
,其长度是可变的,取决于文件名长度。静态方法 allocate
负责为这个 header 分配足够的大小来存储完整的文件名。
发件人例程
这是发件人部分的代码:
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()
的计数器部分,保存文件并实现如下:
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()
returns a pair<bool,string>
其中 first
表示接收正确,second
文件名。
问题
由于我无法理解的原因,有时,当 num = sock.receiveBytes(pbuf, n, 0)
收到的字节数少于预期的 n
并且循环完成读取余数时,只接收到对应于最后发送块。
奇怪的是,直到现在,我的问题只发生在我使用环回 (127.0.0.1) 时,这对于测试来说非常实用,因为我在我的机器上进行了所有测试。相反,当传输在两个不同的peer之间时,一切正常,当然不代表一切正确。
所以,如果有人能帮我批评我的实现并最终指出问题出在哪里,我将不胜感激
如果你不想I/O阻塞,你应该将套接字设置为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!