The following code use to get http response message:
boost::beast::tcp_stream stream_;
boost::beast::flat_buffer buffer;
boost::beast::http::response<boost::beast::http::dynamic_body> res;
boost::beast::http::read(stream_, buffer, res);
However, In some cases, based on the preceding request, I can expect that the response message body will include large binary file.
Therefore, I’d like to read it directly to the filesystem and not through buffer variable to avoid excessive use of process memory. How can it be done ?
in Objective-c framework NSUrlSession there's an easy way to do it using NSURLSessionDownloadTask instead of NSURLSessionDataTask, so I wonder if it's also exist in boost.
Thanks !
In general, you can use the http::buffer_body to handle arbitrarily large request/response messages.
If you specifically want to read/write from a filesystem file, you can have the http::file_body instead.
Full Demo buffer_body
The documentation sample for buffer_body is here https://www.boost.org/doc/libs/1_77_0/libs/beast/doc/html/beast/using_http/parser_stream_operations/incremental_read.html.
Using it to write to std::cout: Live On Coliru
#include <boost/asio.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast.hpp>
#include <boost/beast/websocket.hpp>
#include <iostream>
namespace net = boost::asio;
namespace beast = boost::beast;
namespace http = beast::http;
using tcp = net::ip::tcp;
using socket_t = tcp::socket;
/* This function reads a message using a fixed size buffer to hold
portions of the body, and prints the body contents to a `std::ostream`.
*/
template<
bool isRequest,
class SyncReadStream,
class DynamicBuffer>
void
read_and_print_body(
std::ostream& os,
SyncReadStream& stream,
DynamicBuffer& buffer,
beast::error_code& ec)
{
http::parser<isRequest, http::buffer_body> p;
http::read_header(stream, buffer, p, ec);
if(ec)
return;
while(! p.is_done())
{
char buf[512];
p.get().body().data = buf;
p.get().body().size = sizeof(buf);
http::read(stream, buffer, p, ec);
if(ec == http::error::need_buffer)
ec = {};
if(ec)
return;
os.write(buf, sizeof(buf) - p.get().body().size);
}
}
int main() {
std::string host = "173.203.57.63"; // COLIRU 20210901
auto const port = "80";
net::io_context ioc;
tcp::resolver resolver{ioc};
socket_t s{ioc};
net::connect(s, resolver.resolve(host, port));
write(s, http::request<http::empty_body>{http::verb::get, "/", 11});
beast::error_code ec;
beast::flat_buffer buf;
read_and_print_body<false>(std::cout, s, buf, ec);
}
Full file_body example
This is much shorter, writing to body.html:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast.hpp>
#include <boost/beast/websocket.hpp>
#include <iostream>
namespace net = boost::asio;
namespace beast = boost::beast;
namespace http = beast::http;
using tcp = net::ip::tcp;
using socket_t = tcp::socket;
int main() {
std::string host = "173.203.57.63"; // COLIRU 20210901
auto const port = "80";
net::io_context ioc;
tcp::resolver resolver{ioc};
socket_t s{ioc};
net::connect(s, resolver.resolve(host, port));
write(s, http::request<http::empty_body>{http::verb::get, "/", 11});
beast::error_code ec;
beast::flat_buffer buf;
http::response<http::file_body> res;
res.body().open("body.html", beast::file_mode::write_new, ec);
if (!ec.failed())
{
read(s, buf, res, ec);
}
std::cout << "Wrote 'body.html' (" << ec.message() << ")\n";
std::cout << "Headers " << res.base() << "\n";
}
Prints
Wrote 'body.html' (Success)
Headers HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 8616
Server: WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) OpenSSL/1.0.2g
Date: Wed, 01 Sep 2021 19:52:20 GMT
Connection: Keep-Alive
With file body.html; wc body.html showing:
body.html: HTML document, ASCII text, with very long lines
185 644 8616 body.html
Beyond: streaming to child processes and streaming processing
I have an advanced example of that here: How to read data from Internet using muli-threading with connecting only once?.
Related
The next code contains a tcp client class which should be created one or more times defined in a file (it is hard-coded for the example) and emplaced into a std::vector object, and then connected to its corresponding server socket.
Godbolt link: https://godbolt.org/z/hzK9jhzjc
#include <chrono>
#include <thread>
#include <fstream>
#include <boost/asio.hpp>
namespace tcpsocket
{
using boost::asio::ip::tcp;
class client
{
public:
void connect(const std::string& host, const std::string& port)
{
if (host.empty() || port.empty()) return;
tcp::resolver resolver{ io_context };
tcp::resolver::results_type endpoints = resolver.resolve(host, port);
boost::asio::async_connect(socket, endpoints, [this](const boost::system::error_code& error, const tcp::endpoint /*endpoint*/)
{
if (!error)
read();
});
}
void read()
{
socket.async_read_some(boost::asio::buffer(data, max_length), [this](const boost::system::error_code& error, std::size_t bytes)
{
if (error) return socket.close();
bytes_received = bytes;
read();
});
}
void write(const std::string& msg)
{
boost::system::error_code error;
size_t bytes = socket.write_some(boost::asio::buffer(msg), error);
if (error) return socket.close();
}
void poll()
{
io_context.poll();
}
private:
std::string host;
std::string port;
size_t bytes_received{};
enum { max_length = 512 };
unsigned char data[max_length];
boost::asio::io_context io_context;
tcp::socket socket{io_context};
};
}//namespace tcpsocket
struct Cfg
{
unsigned id{};
std::string host;
std::string port;
};
struct Client
{
unsigned id{};
tcpsocket::client sck;
};
int main()
{
std::vector<Client> clients;
std::vector<Cfg> config{ {125u, "127.0.0.1", "30000"}, {137u, "127.0.0.1", "30001"} };//In real life, this would come from configuration file
for (auto&[id, host, port] : config)
{
//auto& client = clients.push_back(Client{id, {}});//This is failing (typo error with return value detected by Sehe!!!)
auto& client = clients.emplace_back(id, {});//This is failing
client.sck.connect(host, port);
}
while (true)
{
for (auto&[id, client] : clients)
client.poll();
using namespace std::chrono_literals;
std::this_thread::sleep_for(100ms);
}
}
The program is not compiling, due to an error with copying io_context/socket under my understanding, but I may be wrong in this point.
How can I fix this? And therefore, is there any better alternative to which I am doing? For example, it should be a better approach to make some tcp socket pool into the client class and use the same io_context for all them?
push_back doesn't return a value (return type is void). If you have c++17, emplace_back can be used like that:
auto& client = clients.emplace_back(Client{id, {}});
But vector can reallocate, which necessitates moving or copying all elements. Since client isn't copyable nor movable, that can't work. And that's only good, because otherwise the async_ operations would run into UB when the vector was reallocated.
Consider deque or list which afford reference stability (meaning elements don't reallocate, or in fewer cases). std::list is the safer of the two here:
std::list<Client> clients;
This gets you somewhere. However I'd note a few things:
it's not efficient to create separate IO services for each client
manually polling them is not typical
you had host and port members that were never used
bytes_received was being overwritten
write_some doesn't guarantee the whole buffer will be written
you're mixing async and sync operations (async_read vs write_some). This is not always a good idea. I think for tcp::socket this will be fine in the given use case, but don't expect IO objects to support this in general
There's no reason to supply the array length for boost::asio::buffer - it will be deduced. Even better to use std::array instead of C style array
I see your #include <thread>; if you intend to run on multiple threads, be aware of strands: Why do I need strand per connection when using boost::asio?
Here's a simplified, fixed version with the above:
Live On Coliru
#include <boost/asio.hpp>
#include <chrono>
#include <fstream>
#include <iostream>
#include <list>
using namespace std::chrono_literals;
namespace tcpsocket {
using boost::asio::ip::tcp;
using boost::system::error_code;
class client {
public:
client(boost::asio::any_io_executor ex) : socket_(ex) {}
size_t bytes_received() const { return bytes_received_; }
void connect(const std::string& host, const std::string& port) {
post(socket_.get_executor(), [=, this] { do_connect(host, port); });
}
void write(std::string msg) {
post(socket_.get_executor(), [=, this] { do_write(msg); });
}
void read() {
post(socket_.get_executor(), [=, this] { do_read(); });
}
private:
void do_connect(const std::string& host, const std::string& port) {
if (host.empty() || port.empty())
return;
tcp::resolver resolver{socket_.get_executor()};
async_connect(socket_, resolver.resolve(host, port),
[this](error_code ec, tcp::endpoint /*endpoint*/) {
if (!ec)
do_read();
else
std::cerr << ec.message() << std::endl;
});
}
void do_write(const std::string& msg) {
error_code ec;
boost::asio::write(socket_, boost::asio::buffer(msg), ec);
if (ec) {
std::cerr << "Closing (" << ec.message() << ")" << std::endl;
return socket_.close();
}
}
void do_read() {
socket_.async_read_some( //
boost::asio::buffer(data),
[this](error_code ec, std::size_t bytes) {
if (ec)
return socket_.close();
bytes_received_ += bytes;
do_read();
});
}
std::atomic_size_t bytes_received_{0};
std::array<unsigned char, 512> data;
tcp::socket socket_;
};
} // namespace tcpsocket
struct Cfg {
unsigned id{};
std::string host;
std::string port;
};
struct Client {
Client(unsigned id, boost::asio::any_io_executor ex) : id_(id), impl_(ex) {}
unsigned id_;
tcpsocket::client impl_;
};
int main()
{
boost::asio::io_context ioc;
std::list<Client> clients;
std::vector<Cfg> const config{
{125u, "127.0.0.1", "30000"},
{137u, "127.0.0.1", "30001"},
{149u, "127.0.0.1", "30002"},
{161u, "127.0.0.1", "30003"},
{173u, "127.0.0.1", "30004"},
{185u, "127.0.0.1", "30005"},
{197u, "127.0.0.1", "30006"},
{209u, "127.0.0.1", "30007"},
{221u, "127.0.0.1", "30008"},
{233u, "127.0.0.1", "30009"},
{245u, "127.0.0.1", "30010"},
};
for (auto& [id, host, port] : config) {
auto& c = clients.emplace_back(id, make_strand(ioc));
c.impl_.connect(host, port);
c.impl_.write(std::to_string(id) + " connected to " + host + ":" + port + "\n");
}
ioc.run_for(150ms);
for (auto& [id, impl]: clients)
std::cout << id << " received " << impl.bytes_received() << "\n";
}
Prints
(for a in {30000..30010}; do netcat -tlp $a < main.cpp & done)
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp
./a.out
125 connected to 127.0.0.1:30000
149 connected to 127.0.0.1:30002
161 connected to 127.0.0.1:30003
185 connected to 127.0.0.1:30005
197 connected to 127.0.0.1:30006
209 connected to 127.0.0.1:30007
221 connected to 127.0.0.1:30008
233 connected to 127.0.0.1:30009
173 connected to 127.0.0.1:30004
245 connected to 127.0.0.1:30010
137 connected to 127.0.0.1:30001
125 received 3386
137 received 3386
149 received 3386
161 received 3386
173 received 3386
185 received 3386
197 received 3386
209 received 3386
221 received 3386
233 received 3386
245 received 3386
Other Notes
Read operations form a loop (implicit strand).
Note that it is still your responsibility to ensure no write operations overlap. If necessary, introduce a queue so that you can have multiple messages pending. See e.g. How to safely write to a socket from multiple threads?
I wrote a some code that should send GET request and get response.
It works for ip-api.com and returns me json file.
But for api.vk.com it returns html as that:
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>kittenx</center>
</body>
</html>
The most interesting thing is that the program returns the correct link, after opening which the desired GET request will be executed.
main.cpp:
#include <iostream>
#include "client.hpp"
#include "json.hpp"
std::string get_token(const std::string &);
int main()
{
std::string token = get_token("data/token1");
std::string query = "https://api.vk.com/method/groups.getMembers?access_token=" + token + "&v=5.13&group_id=klubauto";
std::cout << query << "\n\n\n";
Client client(url);
client.send_request(query);
std::string response = client.get_response();
std::cout << response << std::endl;
return 0;
}
client.hpp:
#pragma once
#include <string>
#include <boost/beast.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http;
class Client
{
public:
Client();
Client(const std::string &api);
~Client();
void send_request(const std::string &arguments);
std::string get_response();
private:
boost::asio::io_context io;
boost::asio::ip::tcp::resolver resolver;
boost::asio::ip::tcp::socket socket;
std::string url;
};
client.cpp
#include "client.hpp"
/*
* Constructors
*/
Client::Client() : url("google.com"), resolver(io), socket(io)
{
boost::asio::connect(socket, resolver.resolve(url, "80"));
}
Client::Client(const std::string &api) : url(api), resolver(io), socket(io)
{
boost::asio::connect(socket, resolver.resolve(url, "80"));
}
/*
* Destructor
*/
Client::~Client()
{
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
}
/*
* Send request
*/
void Client::send_request(const std::string &arguments)
{
http::request<http::string_body> req(http::verb::get, arguments, 11);
req.set(http::field::host, url);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
http::write(socket, req);
}
/*
* Get response
*/
std::string Client::get_response()
{
std::string response;
{
boost::beast::flat_buffer buffer;
http::response<http::dynamic_body> res;
http::read(socket, buffer, res);
response = boost::beast::buffers_to_string(res.body().data());
}
return response;
}
I would like to receive a json file in the response variable, please tell me how to achieve this?
Like I commented, that's how HTTP works: Servers can redirect to a better/new location.
I assume the prime reason for this is because your connection is not HTTPS, and that's what the end-points require. So, fix that first.
Next, your query includes the base URL, which is another error.
Live Demo
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <string>
namespace http = boost::beast::http;
namespace ssl = boost::asio::ssl;
using boost::asio::ip::tcp;
class Client {
public:
Client(const std::string& host = "google.com") : _host(host) {
_ctx.set_default_verify_paths();
connect(_socket.lowest_layer(),
tcp::resolver{_io}.resolve(_host, "https"));
_socket.handshake(ssl::stream_base::client);
}
void send_request(const std::string& query)
{
http::request<http::string_body> req(http::verb::get, query, 11);
req.set(http::field::host, _host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
http::write(_socket, req);
}
std::string get_response() {
http::response<http::string_body> res;
boost::beast::flat_buffer buffer;
http::read(_socket, buffer, res);
return std::move(res.body());
}
private:
boost::asio::io_context _io;
ssl::context _ctx{ssl::context::sslv23_client};
ssl::stream<tcp::socket> _socket{_io, _ctx};
std::string _host;
};
#include <iostream>
#include <boost/json.hpp>
#include <boost/json/src.hpp> // for COLIRU header-only
namespace json = boost::json;
std::string get_token(const std::string&) { return ""; }
int main()
{
Client client("api.vk.com");
client.send_request("/method/groups.getMembers?access_token=" +
get_token("data/token1") + "&v=5.13&group_id=klubauto");
std::cout << json::parse(client.get_response()) << std::endl;
}
Coliru doesn't allow public network access:
terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
what(): resolve: Service not found
But on my machine it correctly says:
{"error":{"error_code":5,"error_msg":"User authorization failed: no access_token passed.","request_params":[{"key":"v","value":"5.13"},{"key":"group_id","value":"klubauto"},{"key":"method","value":"groups.getMembers"},{"key":"oauth","value":"1"}]}}
Note I included quite a number of simplifications along the way.
Is that possible to construct a boost::beast::http::message (specifically I have to construct a boost::beast::http::response<bb_http::string_body>) from std::string, std::string_view or other raw buffer?
Maybe there is some kind of parser? From what I see in Boost.Beast samples, we can either:
receive a response from boost::beast::read* functions. In that case the first argument should be a SyncReadStream, which has to comply with contracts of SyncReadStream from boost/beast/core/type_traits.hpp:
struct is_sync_read_stream<T, detail::void_t<decltype(
std::declval<std::size_t&>() = std::declval<T>().read_some(
std::declval<detail::MutableBufferSequence>()),
std::declval<std::size_t&>() = std::declval<T>().read_some(
std::declval<detail::MutableBufferSequence>(),
std::declval<boost::system::error_code&>()),
(void)0)>> : std::true_type {};
or construct it by hand like http::request<http::string_body> req{http::verb::get, target, version};
You can manually invoke the parser, e.g. with this simple skeleton function:
http::response<http::string_body> do_parse(std::string_view input)
{
beast::error_code ec;
http::response_parser<http::string_body> p;
// read headers
auto buf = boost::asio::buffer(sample);
auto n = p.put(buf, ec);
assert(p.is_header_done());
// read body
if (!ec) {
buf += n;
n = p.put(buf, ec);
p.put_eof(ec);
}
if (ec)
throw boost::system::system_error(ec);
assert(p.is_done());
return p.release();
}
This assumes that input is a sinlge complete request.
Live Demo
Live On Coliru
#include <boost/beast.hpp>
#include <boost/beast/http.hpp>
#include <string_view>
#include <iostream>
#include <iomanip>
namespace beast = boost::beast;
namespace http = beast::http;
http::response<http::string_body> do_parse(std::string_view input)
{
beast::error_code ec;
http::response_parser<http::string_body> p;
// read headers
auto buf = boost::asio::buffer(input);
auto n = p.put(buf, ec);
assert(p.is_header_done());
// read body
if (!ec) {
buf += n;
n = p.put(buf, ec);
p.put_eof(ec);
}
if (ec)
throw boost::system::system_error(ec);
assert(p.is_done());
return p.release();
}
int main() {
auto res = do_parse(
"HTTP/1.1 200 OK\r\n"
"Date: Sun, 10 Oct 2010 23:26:07 GMT\r\n"
"Server: Apache/2.2.8 (Ubuntu) mod_ssl/2.2.8 OpenSSL/0.9.8g\r\n"
"Last-Modified: Sun, 26 Sep 2010 22:04:35 GMT\r\n"
"ETag: 45b6-834-49130cc1182c0\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: 12\r\n"
"Connection: close\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"Hello world!");
std::cout << res << '\n';
std::cout << "====== body:\n" << std::quoted(res.body()) << "\n";
}
Prints
HTTP/1.1 200 OK
Date: Sun, 10 Oct 2010 23:26:07 GMT
Server: Apache/2.2.8 (Ubuntu) mod_ssl/2.2.8 OpenSSL/0.9.8g
Last-Modified: Sun, 26 Sep 2010 22:04:35 GMT
ETag: 45b6-834-49130cc1182c0
Accept-Ranges: bytes
Content-Length: 12
Connection: close
Content-Type: text/html
Hello world!
====== body:
"Hello world!"
Turns out there already has been a sample snippet for reading a boost::beast::message from std::istream.
https://www.boost.org/doc/libs/1_66_0/libs/beast/doc/html/beast/using_http/buffer_oriented_parsing.html
/** Read a message from a `std::istream`.
This function attempts to parse a complete HTTP/1 message from the stream.
#param is The `std::istream` to read from.
#param buffer The buffer to use.
#param msg The message to store the result.
#param ec Set to the error, if any occurred.
*/
template<
class Allocator,
bool isRequest,
class Body>
void
read_istream(
std::istream& is,
basic_flat_buffer<Allocator>& buffer,
message<isRequest, Body, fields>& msg,
error_code& ec)
{
// Create the message parser
//
// Arguments passed to the parser's constructor are
// forwarded to the message constructor. Here, we use
// a move construction in case the caller has constructed
// their message in a non-default way.
//
parser<isRequest, Body> p{std::move(msg)};
do
{
// Extract whatever characters are presently available in the istream
if(is.rdbuf()->in_avail() > 0)
{
// Get a mutable buffer sequence for writing
auto const b = buffer.prepare(
static_cast<std::size_t>(is.rdbuf()->in_avail()));
// Now get everything we can from the istream
buffer.commit(static_cast<std::size_t>(is.readsome(
reinterpret_cast<char*>(b.data()), b.size())));
}
else if(buffer.size() == 0)
{
// Our buffer is empty and we need more characters,
// see if we've reached the end of file on the istream
if(! is.eof())
{
// Get a mutable buffer sequence for writing
auto const b = buffer.prepare(1024);
// Try to get more from the istream. This might block.
is.read(reinterpret_cast<char*>(b.data()), b.size());
// If an error occurs on the istream then return it to the caller.
if(is.fail() && ! is.eof())
{
// We'll just re-use io_error since std::istream has no error_code interface.
ec = make_error_code(errc::io_error);
return;
}
// Commit the characters we got to the buffer.
buffer.commit(static_cast<std::size_t>(is.gcount()));
}
else
{
// Inform the parser that we've reached the end of the istream.
p.put_eof(ec);
if(ec)
return;
break;
}
}
// Write the data to the parser
auto const bytes_used = p.put(buffer.data(), ec);
// This error means that the parser needs additional octets.
if(ec == error::need_more)
ec = {};
if(ec)
return;
// Consume the buffer octets that were actually parsed.
buffer.consume(bytes_used);
}
while(! p.is_done());
// Transfer ownership of the message container in the parser to the caller.
msg = p.release();
}
I am currently trying to implement a file-transfer app under linux using boost.asio. I am complete new to this topic (general learning cpp), the past days I was trying to figure out how this might work. I am already losing my mind.
I made some progress, but I can't transfer a file completely, instead I am just getting a part of the file. Does anyone knows why the buffer is not red or written completely?
I made It really simple, its just a series of commands, I will implement it object oriented later on.
The secondly I was wondering if there is another way to map the file in memory more efficiency? Say someone want to transfer a 2 tb file?
I am using this binary file for testing: blah.bin
to successfully build it u need:
g++ -std=c++17 -Wall -Wextra -g -Iinclude -Llib src/main.cpp -o bin/main -lboost_system -lpthread
server
//server
#include <boost/asio.hpp>
#include <iostream>
#include <fstream>
using namespace boost::asio;
using ip::tcp;
using std::string;
using std::cout;
using std::endl;
int main() {
boost::asio::io_service io_service;
//listen
tcp::acceptor acceptor_(io_service, tcp::endpoint(tcp::v4(), 3333));
//socket
tcp::socket socket_(io_service);
//waiting
acceptor_.accept(socket_);
//read
boost::asio::streambuf buf;
boost::asio::read_until(socket_, buf, "\nend\n");
auto data = boost::asio::buffer_cast<const char*>(buf.data());
std::ofstream file("transferd.bin");
cout << data;
file << data;
file.close();
//response
boost::asio::write(socket_, boost::asio::buffer("data recived"));
return 0;
}
client
//client
#include <boost/asio.hpp>
#include <iostream>
#include <fstream>
#include <vector>
using namespace boost::asio;
using ip::tcp;
using std::string;
using std::cout;
using std::endl;
using std::vector;
const vector<char> fileVec(const std::string & fileName) {
std::ifstream file(fileName, std::ios::in | std::ios::binary);
vector<char> tempVec ((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
return tempVec;
};
int main() {
boost::asio::io_service io_service;
//socket
tcp::socket socket(io_service);
//connection
socket.connect(tcp::endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 3333));
//write to server
auto vdata = fileVec("example.bin");
vdata.push_back('\n');
vdata.push_back('e');
vdata.push_back('n');
vdata.push_back('d');
vdata.push_back('\n');
boost::system::error_code error;
boost::asio::write(socket, boost::asio::buffer(vdata), error);
//response from server
boost::asio::streambuf receive_buffer;
boost::asio::read(socket, receive_buffer, boost::asio::transfer_all(), error);
const char* response = boost::asio::buffer_cast<const char*>(receive_buffer.data());
cout << response;
return 0;
}
The problem is not in the socket but how you are writing the file in the server.
std::ofstream file("transferd.bin");
cout << data; // you cannot print binary data like this on the standard output!
file << data;
file.close();
The above snippet is wrong because the << operator is used for ASCII not for binary data!
A simple fix would be to replace it with the following snippet:
std::ofstream file("transferd.bin");
file.write(data, buf.size());
The second part of the question is of course more hard and it requires a lot of code changing.
The point is that you cannot transfer all the content at once, But you should split the transfer in small chunks.
One solution can be to send a small header with some information like the total transfer bytes so the server can read chunk by chunk until the whole transfer is complete.
The message has a header file containing the total message size, the number of chunks. Each chunks have a little header indicating the chunk size or for instance the chunk index in case you wanna switch to UDP.
Following the server snippet
#include <array>
#include <boost/asio.hpp>
#include <cstddef>
#include <fstream>
#include <iostream>
#include <vector>
using namespace boost::asio;
using ip::tcp;
using std::cout;
using std::endl;
using std::string;
struct MessageHeader {
int64_t totalSize;
int64_t chunkCount;
};
struct ChunkHeader {
int64_t index;
int64_t size;
};
MessageHeader parseHeader(const char* data) {
MessageHeader header;
memcpy(&header, data, sizeof(MessageHeader));
return header;
}
ChunkHeader parseChunkHeader(const char* data) {
ChunkHeader header;
memcpy(&header, data, sizeof(MessageHeader));
return header;
}
MessageHeader readHeader(tcp::socket& socket) {
std::array<char, sizeof(MessageHeader)> buffer;
boost::asio::read(socket, boost::asio::buffer(buffer));
return parseHeader(buffer.data());
}
ChunkHeader readChunkHeader(tcp::socket& socket) {
std::array<char, sizeof(ChunkHeader)> buffer;
boost::asio::read(socket, boost::asio::buffer(buffer));
return parseChunkHeader(buffer.data());
}
std::vector<char> readChunkMessage(tcp::socket& socket) {
auto chunkHeader = readChunkHeader(socket);
std::vector<char> chunk(chunkHeader.size);
boost::asio::read(socket, boost::asio::buffer(chunk));
return chunk;
}
int main() {
boost::asio::io_service io_service;
// listen
tcp::acceptor acceptor_(io_service, tcp::endpoint(tcp::v4(), 3333));
// socket
tcp::socket socket_(io_service);
// waiting
acceptor_.accept(socket_);
auto messageHeader = readHeader(socket_);
for (auto chunkIndex = 0ll; chunkIndex != messageHeader.chunkCount; ++chunkIndex) {
auto chunk = readChunkMessage(socket_);
// open the file in append mode
std::ofstream file("transferd.bin", std::ofstream::app);
file.write(chunk.data(), chunk.size());
}
// response
boost::asio::write(socket_, boost::asio::buffer("data recived"));
return 0;
}
The above solution has drawbacks because everything is synchronous and if the client quit in the middle of transfer the server will be stuck :D
A better solution is to turn that in async code... but It's too much all at once for a beginner!
I'm using boost::beast in my project. Following code is a modified version of example code. I tried to reuse flat_buffer and http::response in the following code, but the result is wrong. In the second query, the response body is concatenate of two query result. How can I solve this problem?
#include <iostream>
#include <string>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
int main(int argc, char** argv) {
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
auto const host = "www.example.com";
auto const port = "80";
auto const target = "/";
int version = 11;
boost::asio::io_context ioc;
tcp::resolver resolver{ioc};
tcp::socket socket{ioc};
auto const results = resolver.resolve(host, port);
// Make the connection on the IP address we get from a lookup
boost::asio::connect(socket, results.begin(), results.end());
// Set up an HTTP GET request message
http::request<http::string_body> req{http::verb::get, target, version};
req.set(http::field::host, host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// This buffer is used for reading and must be persisted
boost::beast::flat_buffer buffer;
// Declare a container to hold the response
http::response<http::dynamic_body> res;
for (int n = 0; n < 2; n++) {
http::write(socket, req);
http::read(socket, buffer, res);
// Write the message to standard out
std::cout << res << std::endl;
std::cout << "+++++++++++++++++\n";
}
// Gracefully close the socket
boost::system::error_code ec;
socket.shutdown(tcp::socket::shutdown_both, ec);
return EXIT_SUCCESS;
}
The http::read is additive. If you want the buffer empty before the call to read you will need to do it manually.
One way is like this:
buffer.consume(buffer.size());
The beast team are always happy to help on slack if you have access to it:
http://slack.cpp.al
channel #beast