I am using FlatBuffers & Boost ASIO
Here is how I form up my message and sent it over from the client to the server:
size_t const header_length = 8;
size_t const body_size_b = 8;
std::string data;
ServerOpcode opc;
opc = ServerOpcode::SMSG_LOGIN_REQUEST_RESPONSE_TEST;
flatbuffers::FlatBufferBuilder builder;
auto name = builder.CreateString("Orc Monster");
auto accountRole = Vibranium::CreateAccountRole_Packet(builder,1,name);
std::ostringstream header_stream;
header_stream << std::setw(header_length) << std::hex << opc;
std::ostringstream body_size_stream;
body_size_stream << std::setw(body_size_b) << std::hex << builder.GetSize();
data += header_stream.str();
data += body_size_stream.str();
boost::asio::streambuf b;
std::ostream os(&b);
size_t request_length = data.length();
boost::asio::write(s, boost::asio::buffer(data,request_length));
boost::asio::write(s, boost::asio::buffer(&accountRole,builder.GetSize()));
Here is how I read it on server side:
void Vibranium::Client::read_header() {
auto self(shared_from_this());
_packet.header_.resize(Packet::header_size_in_bytes);
boost::asio::async_read(socket,
boost::asio::buffer(_packet.header_, Packet::header_size_in_bytes),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec))
{
Disconnect();
}
else
{
std::ostringstream header_stream;
header_stream << std::setw(Packet::header_size_in_bytes) << std::hex << _packet.header_;
std::cout << "Header: " << std::endl;
std::cout << std::hex << header_stream.str() << std::endl;
read_body_size();
}
});
}
void Vibranium::Client::read_body_size() {
auto self(shared_from_this());
_packet.body_size_.resize(Packet::body_size_in_bytes);
socket.async_read_some(boost::asio::buffer(_packet.body_size_, Packet::body_size_in_bytes),
[this, self](boost::system::error_code ec, std::size_t length)
{
if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec))
{
Disconnect();
}
else
{
std::cout << "Body Size: " << std::endl;
std::cout << _packet.body_size_ << std::endl;
std::stringstream ss;
ss << std::hex << _packet.body_size_;
ss >> _packet.body_in_bytes;
read_body();
}
});
}
void Vibranium::Client::read_body() {
auto self(shared_from_this());
_packet.body_.resize(_packet.body_in_bytes);
socket.async_read_some(boost::asio::buffer(_packet.body_, _packet.body_in_bytes),
[this, self](boost::system::error_code ec, std::size_t length)
{
if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec))
{
Disconnect();
}
else
{
try
{
auto ac = GetAccountRole_Packet(&_packet.body_);
std::cout << ac->name()->str() << std::endl;
}
catch (std::exception& e)
{
std::cout << "Error bace!" << std::endl;
// Unable to decode data.
boost::system::error_code error(boost::asio::error::invalid_argument);
return;
}
std::cout << "Body: " << std::endl;
std::cout << _packet.body_ << std::endl;
//Send(ServerOpcode::SMSG_AUTH_CONNECTION_RESPONSE,"How are you, mate?");
read_header();
}
});
}
void Vibranium::Client::Disconnect() const {
Logger::Log("Disconnected ID: " + std::__cxx11::to_string(this->connectionId), Logger::Error, true);
for (int i = 0; i < Vibranium::Server::Clients.size(); ++i) {
if(Vibranium::Server::Clients[i]->connectionId == this->connectionId)
Vibranium::Server::Clients.erase(Vibranium::Server::Clients.begin() + i);
}
}
When message is received on the server side I get the following output:
Header:
a99
Body Size:
24
Segmentation fault (core dumped)
Then if I try to see more details about the seg fault with gdb I see the following output:
Core was generated by `./AuthServer'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00007f0ad833c202 in flatbuffers::ReadScalar<int> (p=0x30bd6e0) at /usr/local/include/flatbuffers/base.h:392
392 return EndianScalar(*reinterpret_cast<const T *>(p));
Why is the server crashing? What am I doing wrong and how can I fix it?
P.S.
I am pretty sure this line std::cout << ac->name()->str() << std::endl; is causing the crash, but I cant understand why.
Like I said in one of the other many other questions you opened on this topic, you first need to read the tutorial on FlatBuffers:
https://google.github.io/flatbuffers/flatbuffers_guide_tutorial.html
Some hints:
You're not finishing the buffer.
You're not actually using the buffer anywhere in your writing code, only getting its size.
You seem to freely mixing binary and strings in your reading/writing. FlatBuffers need to be written as binary only. Stuff like std::hex is for text output.
Tip: just putting things together without understanding them may work in e.g. Python, but it leads to crashes in C++. You need to actually learn the language and APIs and how they work.
Related
I have a client which read file and sends the data to server line by line. Server must count the number of lines sent. I'm using boost::asio::async_read_until to reach this result. But I'm getting the garbage (like this: Line: �68�) when trying to read data from buffer. Client sends the data only in ASCII encoding.
Client code fragment:
std::ifstream infile(argv[1]);
std::string line;
while (std::getline(infile, line)) {
boost::asio::write(s, boost::asio::buffer(line + '\n'));
std::cout << line << std::endl;
Server read function:
void handle_read(const boost::system::error_code& error, size_t bytes_trasferred, boost::asio::streambuf& buf)
{
if (!error)
{
if (!bytes_trasferred)
{
std::cout << "0 bytes trasferred\n";
return;
}
std::string data = boost::asio::buffer_cast<const char*>(buf.data());
std::istringstream is(data);
std::string line;
std::getline(is, line);
std::cout << "Line: " << line << std::endl;
}
else
std::cerr << "Error: " << error.message() << std::endl;
}
void do_read()
{
std::cout << "do_read\n";
auto self(shared_from_this());
boost::asio::streambuf buf;
buf.prepare(1048576);
std::cout << "ASYNC\n";
boost::asio::async_read_until(socket_, buf, "\n",
boost::bind(&TcpConnection::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, boost::ref(buf)));
}
How to resolve it? Help me, please!
I've tried to use 'self' 'this' instead. It follows to memory leak.
I've tried to add null terminator after getting data in handler function. ChatGPT said me about this way, but behavior still same.
Here:
std::string data = boost::asio::buffer_cast<const char*>(buf.data());
This is Undefined Behaviour as your are assuming that buf.data() points to a character sequence that is properly terminated with a NUL character, where you have absolutely no reason to assume this.
Besides you have UB because you pass a reference to buf, a local variable, which will by definition no longer be valid after do_read returns.
Thirdly, as Alan pointed out, you failed to copy the shared pointer (self) into the bound handler.
You're mixing many different ways to attack the problem of dealing with line-wise input. By far the easiest approach is to
use a dynamic buffer (like indeed, streambuf)
make it a member, because that's why you have shared_from_this in the first place
do not use prepare() because async_read_until knows how to do that (just like it does commit() for you)
do use consume() instead of doing getline /again/, even though async_read_until already tells you where the newline is.
Combining that:
struct TcpConnection : std::enable_shared_from_this<TcpConnection> {
TcpConnection(tcp::socket s) : socket_(std::move(s)) {}
void run() {
do_read();
}
private:
tcp::socket socket_;
asio::streambuf buf;
void handle_read(error_code ec, size_t n) {
std::cerr << "handle_read: " << ec.message() << std::endl;
if (!ec) {
std::string const line(boost::asio::buffer_cast<char const*>(buf.data()), n);
buf.consume(n);
std::cout << "Line: " << quoted(line) << std::endl;
do_read();
}
}
void do_read() {
async_read_until( //
socket_, buf, "\n",
boost::bind(&TcpConnection::handle_read, shared_from_this(), asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
};
I'd probably simplify, avoiding copying and potentially allocations:
struct TcpConnection : std::enable_shared_from_this<TcpConnection> {
TcpConnection(tcp::socket s) : socket_(std::move(s)) {}
void run() {
buf_.reserve(8192); // optional, tune to taste
do_read();
}
private:
tcp::socket socket_;
std::string buf_;
void handle_read(error_code ec, size_t n) {
std::cerr << "handle_read: " << ec.message() << std::endl;
if (!ec) {
auto line = std::string_view(buf_).substr(0, n);
std::cout << "Line: " << quoted(line) << std::endl;
buf_.erase(0, n);
do_read();
}
}
void do_read() {
async_read_until( //
socket_, asio::dynamic_buffer(buf_), "\n",
boost::bind(&TcpConnection::handle_read, shared_from_this(), ph::error,
ph::bytes_transferred));
}
};
This still uses a dynamic buffer, but you don't have to do the extra hoops with istream which you apparently really don't need. For illustration purposes only, you could make the dynamic_string_buffer explicit:
void run() {
backing_.reserve(8192); // optional, tune to taste
do_read();
}
private:
tcp::socket socket_;
std::string backing_;
asio::dynamic_string_buffer<char, std::char_traits<char>, std::allocator<char>> buf_{backing_};
void handle_read(error_code ec, size_t n) {
std::cerr << "handle_read: " << ec.message() << std::endl;
if (!ec) {
auto line = std::string_view(backing_).substr(0, n);
std::cout << "Line: " << quoted(line) << std::endl;
buf_.consume(n); // look mom, it's a DynamiceBuffer all the same!
do_read();
}
}
void do_read() {
async_read_until( //
socket_, buf_, "\n",
boost::bind(&TcpConnection::handle_read, shared_from_this(), ph::error,
ph::bytes_transferred));
}
Note, I wouldn't do that, but it demonstrates you how both approaches use the same DynamicBuffer concept as you did with streambuf.
Live Demo
Live On Coliru
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
namespace ph = asio::placeholders;
using asio::ip::tcp;
using boost::system::error_code;
struct TcpConnection : std::enable_shared_from_this<TcpConnection> {
TcpConnection(tcp::socket s) : socket_(std::move(s)) {}
void run() {
buf_.reserve(8192); // optional, tune to taste
do_read();
}
private:
tcp::socket socket_;
std::string buf_;
void handle_read(error_code ec, size_t n) {
if (n) {
auto line = std::string_view(buf_).substr(0, n - 1); // exclude '\n'
std::cout << socket_.remote_endpoint() << " Line: " << quoted(line) << std::endl;
buf_.erase(0, n);
}
if (!ec)
do_read();
else
std::cerr << "handle_read: n:" << n << " " << ec.message() << std::endl;
}
void do_read() {
async_read_until( //
socket_, asio::dynamic_buffer(buf_), "\n",
boost::bind(&TcpConnection::handle_read, shared_from_this(), ph::error,
ph::bytes_transferred));
}
};
struct Server {
Server(asio::any_io_executor ex) : acc(ex, {{}, 7878}) {}
void start() {
do_accept();
}
private:
tcp::acceptor acc;
void do_accept() {
acc.async_accept(make_strand(acc.get_executor()), [this](error_code ec, tcp::socket s) {
if (!ec) {
std::make_shared<TcpConnection>(std::move(s))->run();
do_accept();
}
});
}
};
int main() {
asio::io_context ioc;
Server srv(ioc.get_executor());
srv.start();
ioc.run();
}
With the client emulated by a very similar oneliner:
netcat 127.0.0.1 7878 -w0 < main.cpp
Prints:
127.0.0.1:54860 Line: "#include <boost/asio.hpp>"
127.0.0.1:54860 Line: "#include <boost/bind/bind.hpp>"
127.0.0.1:54860 Line: "#include <iomanip>"
127.0.0.1:54860 Line: "#include <iostream>"
127.0.0.1:54860 Line: "namespace asio = boost::asio;"
...
...
127.0.0.1:54860 Line: "int main() {"
127.0.0.1:54860 Line: " asio::io_context ioc;"
127.0.0.1:54860 Line: ""
127.0.0.1:54860 Line: " Server srv(ioc.get_executor());"
127.0.0.1:54860 Line: " srv.start();"
127.0.0.1:54860 Line: ""
127.0.0.1:54860 Line: " ioc.run();"
127.0.0.1:54860 Line: "}"
handle_read: n:0 End of file
Local demo showing multiple concurrent clients:
Boost.process allows the usage of Boost.asio in order to perform asynchronous read.
From what I understand, this is useful to read output while process is running without having to wait for the process to terminate.
But to access this output, is it necessary to wait for the process to terminate, or is it possible to access it while process is running, and how?
Actually my need is to access the beginning of a process output (to check that it started as expected) while while keeping it running.
To detail the context, I run a process which I want to keep until the end of the execution:
boost::asio::io_service ios;
std::vector<char> buf;
bp::child c("process_that_needs_to_keep_running", args,
bp::std_out > boost::asio::buffer(buf), ios);
ios.run();
// I DON'T WANT WAIT FOR c TO TERMINATE
// but I want to check that buf contains some text that ensures me it started correctly
// the issue I have here is that I don't know how to read from buf, since its size and content might not be consistent
// is it possible to take a snapshot for instance?
check_started_correctly(buf);
Here the issue is that the producer creates output which I don't control, I just issues output.
If you use bp::std_out > some_kind_of_buffer_or_future you will usually get the result only at exit.
However, you can use an async_pipe:
bp::async_pipe pipe(io);
bp::child c( //
"/bin/bash",
std::vector<std::string>{
"-c",
"for a in {1..20}; do sleep 1; echo message $a; done",
}, //
bp::std_out > pipe, //
bp::on_exit(on_exit), //
io);
Now, you have to explicitly do the IO on that pipe:
boost::asio::streambuf sb;
async_read_until( //
pipe, sb, "message 5\n", //
[&](error_code ec, size_t) { //
std::cout << "Got message 5 (" << ec.message() << ")" << std::endl;
});
This works:
Live On Coliru
#include <boost/process.hpp>
#include <boost/process/async.hpp>
#include <boost/asio.hpp>
#include <iostream>
namespace bp = boost::process;
using boost::system::error_code;
namespace /*file-static*/ {
using namespace std::chrono_literals;
static auto now = std::chrono::steady_clock::now;
static const auto t0 = now();
static auto timestamp() {
return std::to_string((now() - t0) / 1.s) + "s ";
}
} // namespace
int main() {
boost::asio::io_context io;
bp::async_pipe pipe(io);
auto on_exit = [](int code, std::error_code ec) {
std::cout << timestamp() << "on_exit: " << ec.message() << " code "
<< code << std::endl;
};
bp::child c( //
"/bin/bash",
std::vector<std::string>{
"-c",
"for a in {1..20}; do sleep 1; echo message $a; done",
}, //
bp::std_out > pipe, //
bp::on_exit(on_exit), //
io);
boost::asio::streambuf sb;
async_read_until( //
pipe, sb, "message 5\n", //
[&](error_code ec, size_t) { //
std::cout << timestamp() << "Got message 5 (" << ec.message() << ")"
<< std::endl;
});
io.run();
}
Prints
5.025400s Got message 5 (Success)
20.100547s on_exit: Success code 0
So you can respond to content you're looking for when it happens. Keep in mind OS and shells do stream buffering on pipes, but the default is line-buffering so, you can expect to receive input as soon as a newline is printed.
Large Buffers?
The above kinda assumes that you can buffer the entire output up to the interesting message. What if that is gigabytes? As long as your pattern isn't gigabytes, you can keep reading until the criteria is matched.
Let's morph our example into an async grep that looks for the regex class\s*\w+_heap in all of the boost headers. Of course, this is many megabytes of data, but we use only a 10Kb buffer:
std::string text;
auto buf = boost::asio::dynamic_buffer(text, 10 * 1024); // max 10 kilobyte
size_t total_received =0;
boost::regex const re(R"(class\s*\w+_heap)");
Now we make a read loop that reads until match or when the buffer is full:
std::function<void()> wait_for_message;
wait_for_message = [&] {
async_read_until( //
pipe, buf, re, //
[&](error_code ec, size_t received) { //
std::cerr << '\x0d' << timestamp() << "Checking for message ("
<< ec.message() << ", total " << total_received
<< ") ";
if (received || ec != boost::asio::error::not_found) {
total_received += received;
buf.consume(received);
boost::smatch m;
if (regex_search(text, m, re)) {
std::cout << "\n" << timestamp()
<< "Found: " << std::quoted(m.str()) << " at "
<< (total_received - m.size()) << " bytes"
<< std::endl;
}
} else {
// discard 90% of buffer capacity
auto discard =
std::min(buf.max_size() / 10 * 9, buf.size());
total_received += discard;
buf.consume(discard);
}
if (!ec | (ec == boost::asio::error::not_found))
wait_for_message();
else
std::cout << "\n" << timestamp() << ec.message() << std::endl;
});
};
Of course, this system might miss matches if the match exceeds 10% of the buffer size (because we only keep 10% of the previous buffer contents to allow for matches overlapping read boundaries).
Again, see it Live On Coliru
#include <boost/process.hpp>
#include <boost/process/async.hpp>
#include <boost/asio.hpp>
#include <boost/regex.hpp>
#include <iostream>
#include <iomanip>
namespace bp = boost::process;
using boost::system::error_code;
namespace /*file-static*/ {
using namespace std::chrono_literals;
static auto now = std::chrono::steady_clock::now;
static const auto t0 = now();
static auto timestamp() {
return std::to_string((now() - t0) / 1.s) + "s ";
}
} // namespace
int main() {
boost::asio::io_context io;
bp::async_pipe pipe(io);
auto on_exit = [](int code, std::error_code ec) {
std::cout << timestamp() << "on_exit: " << ec.message() << " code "
<< code << std::endl;
};
bp::child c( //
"/usr/bin/find",
std::vector<std::string>{"/usr/local/include/boost", "-name",
"*.hpp", "-exec", "cat", "{}", "+"},
bp::std_out > pipe, //
bp::on_exit(on_exit), //
io);
std::string text;
auto buf = boost::asio::dynamic_buffer(text, 10 * 1024); // max 10 kilobyte
size_t total_received =0;
boost::regex const re(R"(class\s*\w+_heap)");
std::function<void()> wait_for_message;
wait_for_message = [&] {
async_read_until( //
pipe, buf, re, //
[&](error_code ec, size_t received) { //
std::cerr << '\x0d' << timestamp() << "Checking for message ("
<< ec.message() << ", total " << total_received
<< ") ";
if (received || ec != boost::asio::error::not_found) {
total_received += received;
buf.consume(received);
boost::smatch m;
if (regex_search(text, m, re)) {
std::cout << "\n" << timestamp()
<< "Found: " << std::quoted(m.str()) << " at "
<< (total_received - m.size()) << " bytes"
<< std::endl;
}
} else {
// discard 90% of buffer capacity
auto discard =
std::min(buf.max_size() / 10 * 9, buf.size());
total_received += discard;
buf.consume(discard);
}
if (!ec | (ec == boost::asio::error::not_found))
wait_for_message();
else
std::cout << "\n" << timestamp() << ec.message() << std::endl;
});
};
wait_for_message();
io.run();
std::cout << timestamp() << " - Done, total_received: " << total_received << "\n";
}
Which prints
2.033324s Found: "class d_ary_heap" at 6747512 bytes
2.065290s Found: "class pairing_heap" at 6831390 bytes
2.071888s Found: "class binomial_heap" at 6860833 bytes
2.072715s Found: "class skew_heap" at 6895677 bytes
2.073348s Found: "class fibonacci_heap" at 6921559 bytes
34.729355s End of file
34.730515s on_exit: Success code 0
34.730593s - Done, total_received: 154746011
Or live from my machine:
I am building an networking application, and being a newbie to Boost asio and networking as a whole had this doubt which might be trivial. I have this application which reads from a file and calls apis accordingly. I am reading json (example):
test.json
{
"commands":
[
{
"type":"login",
"Username": 0,
"Password": "kk"
}
]
}
My main program looks like this :
int main() {
ba::io_service ios;
tcp::socket s(ios);
s.connect({{},8080});
IO io;
io.start_read(s);
io.interact(s);
ios.run();
}
void start_read(tcp::socket& socket) {
char buffer_[MAX_LEN];
socket.async_receive(boost::asio::null_buffers(),
[&](const boost::system::error_code& ec, std::size_t bytes_read) {
(void)bytes_read;
if (likely(!ec)) {
boost::system::error_code errc;
int br = 0;
do {
br = socket.receive(boost::asio::buffer(buffer_, MAX_LEN), 0, errc);
if (unlikely(errc)) {
if (unlikely(errc != boost::asio::error::would_block)) {
if (errc != boost::asio::error::eof)
std::cerr << "asio async_receive: error " << errc.value() << " ("
<< errc.message() << ")" << std::endl;
interpret_read(socket,nullptr, -1);
//close(as);
return;
}
break; // EAGAIN
}
if (unlikely(br <= 0)) {
std::cerr << "asio async_receive: error, read " << br << " bytes" << std::endl;
interpret_read(socket,nullptr, br);
//close(as);
return;
}
interpret_read(socket,buffer_, br);
} while (br == (int)MAX_LEN);
} else {
if (socket.is_open())
std::cerr << "asio async_receive: error " << ec.value() << " (" << ec.message() << ")"
<< std::endl;
interpret_read(socket,nullptr, -1);
//close(as);
return;
}
start_read(socket);
});
}
void interpret_read(tcp::socket& s,const char* buf, int len) {
if(len<0)
{
std::cout<<"some error occured in reading"<<"\n";
}
const MessageHeaderOutComp *obj = reinterpret_cast<const MessageHeaderOutComp *>(buf);
int tempId = obj->TemplateID;
//std::cout<<tempId<<"\n";
switch(tempId)
{
case 10019: //login
{
//const UserLoginResponse *obj = reinterpret_cast<const UserLoginResponse *>(buf);
std::cout<<"*********[SERVER]: LOGIN ACKNOWLEDGEMENT RECEIVED************* "<<"\n";
break;
}
}
std::cout << "RX: " << len << " bytes\n";
if(this->input_type==2)
interact(s);
}
void interact(tcp::socket& s)
{
if(this->input_type == -1){
std::cout<<"what type of input you want ? option 1 : test.json / option 2 : manually through command line :";
int temp;
std::cin>>temp;
this->input_type = temp;
}
if(this->input_type==1)
{
//std::cout<<"reading from file\n";
std::ifstream input_file("test.json");
Json::Reader reader;
Json::Value input;
reader.parse(input_file, input);
for(auto i: input["commands"])
{
std::string str = i["type"].asString();
if(str=="login")
this->login_request(s,i);
}
std::cout<<"File read completely!! \n Do you want to continue or exit?: ";
}
}
The sending works fine, the message is sent and the server responds in a correct manner, but what I need to understand is why is the control not going to on_send_completed (which prints sent x bytes). Neither it prints the message [SERVER]: LOGIN ACKNOWLEDGEMENT RECEIVED, I know I am missing something basic or am doing something wrong, please correct me.
login_request function:
void login_request(tcp::socket& socket,Json::Value o) {
/*Some buffer being filled*/
async_write(socket, boost::asio::buffer(&info, sizeof(info)), on_send_completed);
}
Thanks in advance!!
From a cursory scan it looks like you redefined buffer_ that was already a class member (of IO, presumably).
It's hidden by the local in start_read, which is both UB (because the lifetime ends before the async read operation completes) and also makes it so the member _buffer isn't used.
I see a LOT of confusing code though. Why are you doing synchronous reads from within completion handlers?
I think you might be looking for the composed-ooperation reads (boost::asio::async_read and boost::asio::async_until)
I'm encapsulating the boost-asio socket, but I got an issue with it, but neither async_read nor async_write calls their callback function and I don't understand why.
I've tried using async_read_some but had the same issue.
Here's the code I've written so far
#include <iostream>
#include "socket.hpp"
Socket::Socket()
{
boost::asio::ip::tcp::endpoint ep_tmp(boost::asio::ip::tcp::v4(), 4242);
endpoint = ep_tmp;
acceptor = new boost::asio::ip::tcp::acceptor(ios, endpoint);
tcp_socket = new boost::asio::ip::tcp::socket(ios);
acceptor->listen();
}
Socket::~Socket()
{
delete(acceptor);
delete(tcp_socket);
}
void Socket::get_connection()
{
acceptor->async_accept(*tcp_socket, [](const boost::system::error_code &ec)
{
std::cout << "Connection received." << std::endl;
if (ec)
std::cout << "Error " << ec << std::endl;
});
this->exec();
}
void Socket::send(std::string &message)
{
async_write(*tcp_socket, boost::asio::buffer(message),
[](const boost::system::error_code &ec,
std::size_t bytes_transferred)
{
std::cout << "Sending datas." << std::endl;
if (ec)
std::cout << "Error " << ec << std::endl;
else
std::cout << bytes_transferred << " bytes transferred." << std::endl;
});
}
void Socket::receive(void)
{
char *buf;
buf = (char *)malloc(sizeof(char) * 50);
buf = (char *)memset(buf, 0, 50);
async_read(*tcp_socket, boost::asio::buffer(buf, 50),
[](const boost::system::error_code &ec,
std::size_t bytes_transferred)
{
std::cout << "Receiving datas." << std::endl;
if (ec)
std::cout << "Error " << ec << std::endl;
else
std::cout << bytes_transferred
<< " bytes transferred." << std::endl;
});
}
void Socket::exec(void)
{
ios.run();
}
int main()
{
Socket serv;
std::string data_test;
data_test = "Test\n";
serv.get_connection();
serv.send(data_test);
serv.exec();
serv.receive();
serv.exec();
return (0);
}
The malloc bit is temporary until I find a way to do it without using C.
I'd be really thankful if someone could enlighten me on that issue
You have to call io_service::reset before second and later calls to io_service::run. And you probably want to use synchronous API instead, as your current approach absolutely defeats the purpose of asynchronicity.
I'm with yuri: prefer non-async unless you know what you're doing.
It could look like this: http://coliru.stacked-crooked.com/a/523a7828a9aee4b2
#include <boost/asio.hpp>
#include <iostream>
namespace ba = boost::asio;
using ba::ip::tcp;
class Socket {
public:
Socket() { acceptor.listen(); }
void get_connection();
void exec();
void send(std::string const &message);
void receive(void);
private:
ba::io_service ios;
tcp::endpoint endpoint{ tcp::v4(), 4242 };
tcp::acceptor acceptor{ ios, endpoint };
tcp::socket tcp_socket{ ios };
};
void Socket::get_connection() {
acceptor.accept(tcp_socket);
std::cout << "Connection received.\n";
}
void Socket::send(std::string const &message) {
std::cout << "Sending datas.\n";
auto bytes_transferred = ba::write(tcp_socket, ba::buffer(message));
std::cout << bytes_transferred << " bytes transferred.\n";
}
void Socket::receive(void) {
std::cout << "Receiving datas.\n";
char buf[50] = { 0 };
auto bytes_transferred = ba::read(tcp_socket, ba::buffer(buf));
std::cout << bytes_transferred << " bytes transferred.\n";
}
int main() {
Socket serv;
serv.get_connection();
serv.send("Test\n");
serv.receive();
}
If you want async behaviour, you have to manage the lifetimes of each buffer/connection-specific resource. There are many examples of that, e.g. in the docs or here: http://coliru.stacked-crooked.com/a/95e2000e49b4db1d
On the perils of buffer lifetime: client server simple example nonblocking
I have the following code, trying to code an asynchronous client.
The problem is that in main(), the Client gets deleted in the try-catch block, because execution leaves the scope.
I've tried to come up with a solution to this problem, like adding a while(true), but I don't like this approach. Also, I don't prefer a getchar().
Due to the asynchronous nature of the calls, both connect() and loop() returns immediately.
How can I fix this?
#include <iostream>
#include <thread>
#include <string>
#include <boost\asio.hpp>
#include <Windows.h>
#define DELIM "\r\n"
using namespace boost;
class Client {
public:
Client(const std::string& raw_ip_address, unsigned short port_num) :
m_ep(asio::ip::address::from_string(raw_ip_address), port_num), m_sock(m_ios)
{
m_work.reset(new asio::io_service::work(m_ios));
m_thread.reset(new std::thread([this]() {
m_ios.run();
}));
m_sock.open(m_ep.protocol());
}
void connect()
{
m_sock.async_connect(m_ep, [this](const system::error_code& ec)
{
if (ec != 0) {
std::cout << "async_connect() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
std::cout << "Connection to server has been established." << std::endl;
});
}
void loop()
{
std::thread t = std::thread([this]()
{
recv();
});
t.join();
}
void recv()
{
asio::async_read_until(m_sock, buf, DELIM, [this](const system::error_code& ec, std::size_t bytes_transferred)
{
if (ec != 0) {
std::cout << "async_read_until() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
std::istream is(&buf);
std::string req;
std::getline(is, req, '\r');
is.get(); // discard newline
std::cout << "Received: " << req << std::endl;
if (req == "alive") {
recv();
}
else if (req == "close") {
close();
return;
}
else {
send(req + DELIM);
}
});
}
void send(std::string resp)
{
auto s = std::make_shared<std::string>(resp);
asio::async_write(m_sock, asio::buffer(*s), [this, s](const system::error_code& ec, std::size_t bytes_transferred)
{
if (ec != 0) {
std::cout << "async_write() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
else {
recv();
}
});
}
void close()
{
m_sock.close();
m_work.reset();
m_thread->join();
}
private:
asio::io_service m_ios;
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
std::unique_ptr<asio::io_service::work> m_work;
std::unique_ptr<std::thread> m_thread;
asio::streambuf buf;
};
int main()
{
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 8001;
try {
Client client(raw_ip_address, port_num);
client.connect();
client.loop();
}
catch (system::system_error &err) {
std::cout << "main() error: " << err.what() << " (" << err.code() << ") " << std::endl;
return err.code().value();
}
return 0;
}
You've not really understood how asio works. Typically in the main thread(s) you will call io_service::run() (which will handle all the asynchronous events.)
To ensure the lifetime of the Client, use a shared_ptr<> and ensure this shared pointer is used in the handlers. For example..
io_service service;
{
// Create the client - outside of this scope, asio will manage
// the life time of the client
auto client = make_shared<Client>(service);
client->connect(); // setup the connect operation..
}
// Now run the io service event loop - this will block until there are no more
// events to handle
service.run();
Now you need to refactor your Client code:
class Client : public std::enable_shared_from_this<Client> {
Client(io_service& service): socket_(service) ...
{ }
void connect() {
// By copying the shared ptr to the lambda, the life time of
// Client is guaranteed
socket_.async_connect(endpoint_, [self = this->shared_from_this()](auto ec)
{
if (ec) {
return;
}
// Read
self->read(self);
});
}
void read(shared_ptr<Client> self) {
// By copying the shared ptr to the lambda, the life time of
// Client is guaranteed
asio::async_read_until(socket_, buffer_, DELIM, [self](auto ec, auto size)
{
if (ec) {
return;
}
// Handle the data
// Setup the next read operation
self->read(self)
});
}
};
You have a thread for the read operation - which is not necessary. That will register one async read operation and return immediately. You need to register a new read operation to continue reading the socket (as I've sketched out..)
You can post any function to io_service via post(Handler)
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/io_service/post.html
Then in the main() do something like:
while (!exit) {
io_service.run_one();
}
Or call io_service::run_one or io_service::run in the main()