When I use boost::asio::steady_timer, I find something difference between lambda, bind, function pointer.
#include <iostream>
#include <boost/asio.hpp>
void print() { std::cout << "Hello, world!" << std::endl; }
int main()
{
boost::asio::io_context io;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
t.async_wait(&print); // Error
t.async_wait([]{print();}) // Error
t.async_wait(std::bind(print)); // Done
io.run();
return 0;
}
I read asio manual, async_wait handler need const boost::system::error_code& error param. So if I changed print to void print(const boost::system::error_code & /*e*/), all things was right. But asio example of timer4/timer.cc && timeouts/server.cc used handler creating by bind without void print(const boost::system::error_code & /*e*/). When I changed to lambda, compile was wrong. So, what difference of signature been had between bind && lambda.
#include <iostream>
#include <functional>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
class printer
{
public:
printer(boost::asio::io_context &io)
: timer_(io, boost::asio::chrono::seconds(1)), count_(0)
{
timer_.async_wait(std::bind(&printer::print, this));
}
~printer() { std::cout << "Final count is " << count_ << std::endl; }
void print()
{
if (count_ < 5) {
std::cout << count_ << std::endl;
++count_;
timer_.expires_at(timer_.expiry() +
boost::asio::chrono::seconds(1));
timer_.async_wait(boost::bind(&printer::print, this));
// timer_.async_wait([this]{print();}); Error
}
}
private:
boost::asio::steady_timer timer_;
int count_;
};
int main()
{
boost::asio::io_context io;
printer p(io);
io.run();
return 0;
}
The "partial" generated by std::bind detects and ignores arguments provided by the invocation point and not explicitly wired to the bound code.
A minimalistic example (godbolted):
#include <functional>
#include <iostream>
void callme(std::function<void(int, float)> arg) {
arg(42, 4.2);
}
// or like this
// template <typename F> void callme(F&& arg) {
// arg(42, 4.2);
// }
int main()
{
auto fn = std::bind([](){std::cout << "hi there" << std::endl; });
// auto fn = std::bind([](auto&& x){std::cout << "x=" << x << std::endl; }, std::placeholders::_1); <-- this works too and prints 42
// auto fn = std::bind([](auto&& x){std::cout << "x=" << x << std::endl; }, std::placeholders::_2); <-- and works too and prints 4.2
callme(fn);
return 0;
}
Related
I am trying to build a simple IPC protocol using Boost Asio where the server side will be sending a struct that contains a vector<uint8_t> to the client. I was suggested to use a scatter/gather IO approach, but I can't get it working, as it seems the client is only receiving part of the data it is expecting and it keeps waiting indefinitely for the rest of the data to arrive even though it should already be there.
This is what I have right now:
// File: client.cpp
#include <iostream>
#include <vector>
#include <boost/asio.hpp>
#include "ipc_common.hpp"
namespace ba = boost::asio;
using boost::asio::ip::tcp;
int main(int argc, char *argv[])
{
ba::io_context io;
std::vector<std::string> args(argv, argv + argc);
switch (args.size()) {
case 1:
args = {args.at(0), "localhost", "6869"};
break;
case 2:
args = {args.at(0), args.at(1), "6869"};
break;
case 3:
args = {args.at(0), args.at(1), args.at(2)};
break;
default:
std::clog << "usage: " << args.at(0) << " [host = localhost] [port = 6869]" << std::endl;
return 1;
}
try {
propertiesPacket properties;
properties.val1 = 9;
properties.val2 = 45;
tcp::socket socket(io);
tcp::resolver resolver(io);
connect(socket, resolver.resolve(args.at(1), args.at(2)));
write(socket, ba::buffer(&properties, sizeof(properties)));
uint16_t responseSize {};
ba::read(socket, ba::buffer(&responseSize, sizeof(uint16_t)));
std::clog << "client responseSize: " << responseSize << std::endl;
processedData response {};
std::vector<ba::mutable_buffer> responseBuffers {
ba::buffer(&response.size, sizeof(uint16_t)),
ba::buffer(&response.values, responseSize - sizeof(uint8_t))
};
ba::read(socket, responseBuffers);
std::clog << response.serialize();
return 0;
} catch (std::exception &e) {
std::clog << e.what() << std::endl;
return 1;
}
}
// File: server.cpp
#include <vector>
#include <boost/asio.hpp>
#include "ipc_common.hpp"
namespace ba = boost::asio;
using boost::asio::ip::tcp;
using boost::system::error_code;
using TCPSocket = tcp::socket;
class ServerConnection
: public std::enable_shared_from_this<ServerConnection>
{
public:
ServerConnection(TCPSocket socket)
: socket_(std::move(socket))
{ }
void start()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
doRead();
}
private:
void doRead()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
auto self(shared_from_this());
socket_.async_read_some(ba::buffer(&properties_, sizeof(properties_)),
[this, self](error_code ec, std::size_t length)
{
std::clog << "received " << length << std::endl;
if (!ec) {
processData();
std::vector<ba::const_buffer> msg {
ba::buffer(&filePacketSize_, sizeof(uint16_t)),
ba::buffer(&filePacket_.val, sizeof(filePacket_.val)),
ba::buffer(&filePacket_.values, sizeof(filePacket_.values))};
std::clog << "filePacketSize_: " << filePacketSize_ << std::endl;
ba::async_write(socket_, msg,
[this, self = shared_from_this()](error_code ec, std::size_t length)
{
std::clog << "written " << length << std::endl;
if (!ec) doRead();
});
}
});
}
void processData()
{
filePacket_.val = properties_.val1;
// Just for demonstration, we fill the vector with random values
std::random_device rd;
std::mt19937 re(rd()) ;
std::uniform_int_distribution<uint8_t> dist(0, 255);
for (size_t i {}; i < filePacket_.val; ++i) {
processedData.values.push_back(dist(re));
}
}
TCPSocket socket_;
propertiesPacket properties_;
processedData filePacket_;
uint16_t filePacketSize_;
};
class Server
{
public:
using IOContext = ba::io_context;
using TCPAcceptor = tcp::acceptor;
Server(IOContext& io, uint16_t port)
: socket_(io),
acceptor_(io, {tcp::v4(), port})
{
doAccept();
}
private:
void doAccept()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
acceptor_.async_accept(socket_,
[this](error_code ec)
{
if (!ec) {
std::clog << "Accepted " << socket_.remote_endpoint() << std::endl;
std::make_shared<ServerConnection>(std::move(socket_))->start();
doAccept();
}
else {
std::clog << "Accept " << ec.message() << std::endl;
}
});
}
TCPSocket socket_;
TCPAcceptor acceptor_;
};
int main(int argc, char* argv[])
{
std::vector<std::string> args(argv, argv + argc);
switch (args.size()) {
case 1:
args = {args.at(0), "6869"};
break;
case 2:
args = {args.at(0), args.at(1)};
break;
default:
std::clog << "usage: " << args.at(0) << " [port = 6869]" << std::endl;
return 1;
}
try {
ba::io_context io;
Server server(io, std::stoi(args.at(1)));
io.run();
} catch (std::exception &e) {
std::clog << e.what() << std::endl;
return 1;
}
return 0;
}
// File: ipc_common.hpp
#include <cstdint>
#include <vector>
#include <sstream>
#include <string>
struct propertiesPacket
{
uint8_t val1;
uint8_t val2;
};
struct processedData
{
uint8_t val;
std::vector<uint8_t> values;
std::string serialize()
{
std::stringstream sstream;
sstream << "val: " << (unsigned int)val << std::endl;
for (const auto &i : values)
{
sstream << i << " ";
}
sstream << std::endl;
return sstream.str();
}
};
What am I doing wrong?
The sample seems corrupted.
For one, args.at(3) and args.at(4) will by definition always throw, because by definition the switch statement earlier will always exit the client when there are more than 2 command line arguments (default:).
Secondly, the client read uses &response.size but no such member exists at all.
Thirdly, server processData uses a .val property of procesedData which isn't even a member (it's a type, likely should be filePacket_.val instead).
Fourthly, it assigns that from properties_.val; which ALSO doesn't exist at all (there's only val1 and val2).
Next up, rd isn't used to initialize the URBG (random engine, re). Instead it calls an unknown identifier named random_device(). Likely ought to be rd() instead.
Again, where it sais processedData.val you probably meant filePacket_.val
And where you write processedData.push_back(...) you probably meant to say filePacket_.values.push_back(...)...
There's a spurious ; behind void doAccept() in the Server
By contrast, the ; is missing after each struct definition in ipc_common.hpp
The processedData struct defines a serialize() method that is never used. It also uses a C-style cast where static_cast<unsigned>(val) would be safe.
Weirdly, the server "parses" args, and provides an optional default value BUT it never uses that. Instead, it uses argv[1] without checking argc at all. Oops.
That all aside, now comes the confusing part: how did you want the values to be written? This is not correct:
std::vector<ba::const_buffer> msg{
ba::buffer(&filePacketSize_, sizeof(uint16_t)),
ba::buffer(&filePacket_.val, sizeof(filePacket_.val)),
ba::buffer(&filePacket_.values, sizeof(filePacket_.values))};
values is a std::vector<> so you cannot hope to use it in a bitwise way. It'll just invoke Undefined Behaviour.
Besides, it's pretty unclear why filePacketSize_ is being written (it's never even assigned, or even initialized to a determinate value).
On the client side you read a responseSize as if one would be sent... Maybe you want to keep those two in sync.
Suggested Appraoch
I'd do away with the separate size value(s), since a vector already keeps track of that. I'd also make sure your processData doesn't always push_back because the vector would always keep growing.
I'd make a protocol that actually sends the message size before the message itself, and makes sure it's correct.
Let's also make the random data naturally printable (a..z) for simplicity:
void processData()
{
// Just for demonstration, we fill the vector with random characters
std::mt19937 re(std::random_device{}());
std::uniform_int_distribution<uint8_t> dist('a', 'z');
filePacket_.values.clear();
std::generate_n(back_inserter(filePacket_.values), properties_.val1,
[&] { return dist(re); });
}
Then in writing, let's do:
processData();
size_t length[] { filePacket_.values.size() };
std::vector<ba::const_buffer> msg{
ba::buffer(length),
ba::buffer(filePacket_.values)};
Note how, again, we avoid manually specifying any buffer sizes. Also, we let
the library figure out that values is a vector of POD elements and do the
math to convert the calculate the correct start address and buffer size for the
element data.
On the client side, we do the inverse:
size_t length = 0;
ba::read(socket, ba::buffer(&length, sizeof(length)));
response.values.resize(length);
ba::read(socket, ba::buffer(response.values));
(Here we can't avoid writing sizeof(length) without getting more clumsy than I'd like).
Full Demo
File ipc_common.hpp
// File: ipc_common.hpp
#include <cstdint>
#include <sstream>
#include <string>
#include <vector>
struct propertiesPacket {
uint8_t val1;
uint8_t val2;
};
struct processedData {
std::vector<uint8_t> values;
};
File server.cpp
#include <boost/asio.hpp>
#include <vector>
#include <iostream>
#include <random>
#include "ipc_common.hpp"
namespace ba = boost::asio;
using boost::asio::ip::tcp;
using boost::system::error_code;
using TCPSocket = tcp::socket;
class ServerConnection : public std::enable_shared_from_this<ServerConnection> {
public:
ServerConnection(TCPSocket socket) : socket_(std::move(socket))
{
}
void start()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
doRead();
}
private:
void doRead()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
auto self(shared_from_this());
socket_.async_read_some(
ba::buffer(&properties_, sizeof(properties_)),
[this, self](error_code ec, std::size_t length) {
std::clog << "received " << length << std::endl;
if (!ec) {
processData();
size_t length[] { filePacket_.values.size() };
std::vector<ba::const_buffer> msg{
ba::buffer(length), ba::buffer(filePacket_.values)};
ba::async_write(socket_, msg,
[this, self = shared_from_this()](
error_code ec, std::size_t length) {
std::clog << "written " << length
<< std::endl;
if (!ec)
doRead();
});
}
});
}
void processData()
{
// Just for demonstration, we fill the vector with random characters
std::mt19937 re(std::random_device{}());
std::uniform_int_distribution<uint8_t> dist('a', 'z');
filePacket_.values.clear();
std::generate_n(back_inserter(filePacket_.values), properties_.val1,
[&] { return dist(re); });
}
TCPSocket socket_;
propertiesPacket properties_;
processedData filePacket_;
};
class Server {
public:
using IOContext = ba::io_context;
using TCPAcceptor = tcp::acceptor;
Server(IOContext& io, uint16_t port)
: socket_(io)
, acceptor_(io, {tcp::v4(), port})
{
doAccept();
}
private:
void doAccept()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
acceptor_.async_accept(socket_, [this](error_code ec) {
if (!ec) {
std::clog << "Accepted " << socket_.remote_endpoint() << std::endl;
std::make_shared<ServerConnection>(std::move(socket_))->start();
doAccept();
} else {
std::clog << "Accept " << ec.message() << std::endl;
}
});
}
TCPSocket socket_;
TCPAcceptor acceptor_;
};
int main(int argc, char* argv[])
{
std::vector<std::string> args(argv, argv + argc);
switch (args.size()) {
case 1: args.push_back("6869"); break;
case 2: break;
default:
std::clog << "usage: " << args.at(0) << " [port = 6869]" << std::endl;
return 1;
}
try {
ba::io_context io;
Server server(io, std::stoi(args.at(1)));
io.run();
} catch (std::exception const &e) {
std::clog << e.what() << std::endl;
return 1;
}
}
File client.cpp
#include <iostream>
#include <vector>
#include <boost/asio.hpp>
#include "ipc_common.hpp"
namespace ba = boost::asio;
using boost::asio::ip::tcp;
int main(int argc, char *argv[])
{
ba::io_context io;
std::vector<std::string> args(argv, argv + argc);
switch (args.size()) {
case 1: args.push_back("localhost"); [[fallthrough]];
case 2: args.push_back("6869"); [[fallthrough]];
case 3: args.push_back("42"); [[fallthrough]];
case 4: args.push_back("99"); [[fallthrough]];
case 5: break;
default:
std::clog << "usage: " << args.at(0)
<< " [host = localhost] [port = 6869] [val1=42] [val2=99]"
<< std::endl;
return 1;
}
try {
propertiesPacket properties;
properties.val1 = std::stoul(args.at(3));
properties.val2 = std::stoul(args.at(4));
tcp::socket socket(io);
tcp::resolver resolver(io);
connect(socket, resolver.resolve({args.at(1), args.at(2)}));
write(socket, ba::buffer(&properties, sizeof(properties)));
processedData response{};
{
size_t length = 0;
ba::read(socket, ba::buffer(&length, sizeof(length)));
response.values.resize(length);
}
std::clog << "client response size: " << response.values.size() << std::endl;
ba::read(socket, ba::buffer(response.values));
std::clog.write(reinterpret_cast<char const*>(response.values.data()),
response.values.size()) << "\n";
// return 0;
} catch (std::exception &e) {
std::clog << e.what() << std::endl;
return 1;
}
}
Demo output:
Portability
You should probably keep byte ordering in mind as well. You could consider using JSON or another Well Known serialization format.
This is my minimal, reproducible example
#include <memory>
#include <chrono>
#include <thread>
#include <iostream>
#include <functional>
class BaseClass {
public:
void do_func() {
while(true) {
std::cout << "doing stuff" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
};
int main() {
auto obj = std::make_unique<BaseClass>();
std::thread t(&BaseClass::do_func, obj.get());
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "reset called!" << std::endl;
obj.reset();
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "going out of scope" << std::endl;
t.join();
return 0;
}
I was expecting the object to be deleted after reset is called. Even the code cannot exit because the while loop is blocking, which is understandable. I need to delete the object after a particular event, and cannot wait till the unique_ptr goes out of scope. If I change the do_func to
void do_func() {
std::cout << "doing stuff" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(20));
}
then it is the expected behaviour.
Edit:
Based on your comments I have updated my code to
#include <memory>
#include <chrono>
#include <thread>
#include <iostream>
#include <functional>
class BaseClass {
public:
BaseClass() : x(1) {
dummy = std::make_shared<SomeClass>();
}
void do_func() {
while(true) {
std::cout << "doing stuff " << dummy->do_stuff(x) << std::endl;
x++;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
private:
int x;
class SomeClass {
public:
int do_stuff(int x) {
return x * x;
}
};
std::shared_ptr<SomeClass> dummy;
};
int main() {
auto obj = std::make_unique<BaseClass>();
std::thread t(&BaseClass::do_func, obj.get());
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "reset called!" << std::endl;
obj.reset();
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "going out of scope" << std::endl;
t.join();
return 0;
}
And now the function does print garbage values. Does that mean I need to explicitly delete dummy in the destructor?
The simplest way to synchronize these two threads would be to use std::atomic_bool
#include <atomic>
class BaseClass {
public:
std::atomic_bool shouldContinueWork = true;
void do_func() {
while(shouldContinueWork) {
std::cout << "doing stuff" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
};
int main() {
auto obj = std::make_unique<BaseClass>();
std::thread t(&BaseClass::do_func, obj.get());
std::this_thread::sleep_for(std::chrono::seconds(5));
obj->shouldContinueWork = false; //the thread will not do anything more after this, but the sleep will need to end on it's own
std::cout << "stopping work!" << std::endl;
// do not remove the object before join is called - you don't know if it will be still accessed from the other thread or not
// obj.reset();
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "going out of scope" << std::endl;
t.join();
// here it is safe to remove the `obj`, main thread is surely the only thread that accesses it
// (but it goes out of scope anyway)
return 0;
}
This solution doesn't take into account stopping the work midway (i.e. whole loop iteration must always be performed) and is generally prone to having a few more or less iterations of work - it should be precise enough when you have sleep of 1s, but with smaller sleep it won't guarantee any exact number of iterations, take that into account. std::condition_variable can be used for more precise control of thread synchronization.
Thanks for all your quick responses! Let me know if this is a good solution
#include <memory>
#include <chrono>
#include <thread>
#include <iostream>
#include <functional>
class BaseClass {
public:
BaseClass() : x(1) {
dummy = std::make_shared<SomeClass>();
}
virtual ~BaseClass() {
dummy.reset();
}
void do_func() {
while(dummy) {
std::cout << "doing stuff " << dummy->do_stuff(x) << std::endl;
x++;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
private:
int x;
class SomeClass {
public:
int do_stuff(int x) {
return x * x;
}
};
std::shared_ptr<SomeClass> dummy;
};
class DerivedClass : public BaseClass {
};
int main() {
auto obj = std::make_unique<DerivedClass>();
std::thread t(&BaseClass::do_func, obj.get());
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "reset called!" << std::endl;
obj.reset();
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "going out of scope" << std::endl;
t.join();
return 0;
}
The behaviour is now as expected.
i want to make a timer with 10s periodically in the class inside, but it does not work. It will print count in 10s at the first time. But after that, it does not wait another 10s. the specific code.
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
class test {
boost::asio::io_service& a;
boost::asio::deadline_timer t;
int count;
void print(const boost::system::error_code& /*e*/)
{
while(true)
{
std::cout << count << " ";
++(count);
t.expires_at(t.expires_at() + boost::posix_time::seconds(10));
t.async_wait(boost::bind(&test::print, this, boost::asio::placeholders::error));
}
}
public:
test(boost::asio::io_service& io) : a(io), t(io, boost::posix_time::seconds(10)) {
count = 0;
};
void start() {
t.async_wait(boost::bind(&test::print, this, boost::asio::placeholders::error));
}
};
int main()
{
boost::asio::io_service io;
int count = 0;
test b(io);
b.start();
io.run();
return 0;
}
Your print() function has an unnecessary while(true) loop. Once we remove that, we notice that the output doesn't come right away; that's because std::cout is line-buffered and we never write a newline. Here's the full fixed function:
void print(const boost::system::error_code& /*e*/)
{
std::cout << count << " " << std::flush;
++(count);
t.expires_at(t.expires_at() + boost::posix_time::seconds(10));
t.async_wait(boost::bind(&test::print, this, boost::asio::placeholders::error));
}
What I want is when one message queue receives an int N, the handler function will be called after N seconds. below is my code.
It runs OK if the duration seconds of two near message queue is larger than the int N, but the handler will print "Operation canceled" in one handler when the duration seconds between two received message queues are smaller than N, which is not what I want.
I'd appreciate a lot for any help.
#include <boost/asio.hpp>
#include <zmq.h>
#include <boost/thread.hpp>
#include <iostream>
boost::asio::io_service io_service;
void* context = zmq_ctx_new();
void* sock_pull = zmq_socket(context, ZMQ_PULL);
void handler(const boost::system::error_code &ec) {
std::cout << "hello, world" << "\t" << ec.message() << std::endl;
}
void run() {
io_service.run();
}
void thread_listener() {
int nRecv;
boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(0));
while( true ) {
zmq_recv(sock_pull, &nRecv, sizeof(nRecv), 0);
std::cout << nRecv << std::endl;
timer.expires_from_now(boost::posix_time::seconds(nRecv));
timer.async_wait(handler);
}
}
int main(int argc, char* argv[]) {
boost::asio::io_service::work work(io_service);
zmq_bind(sock_pull, "tcp://*:60000");
boost::thread tThread(thread_listener);
boost::thread tThreadRun(run);
tThread.join();
tThreadRun.join();
return 0;
}
When you call
timer.expires_from_now(boost::posix_time::seconds(nRecv));
this, as the documentation states, cancels any async timer pending.
If you want to have overlapping requests in flight at a given time, one timer is clearly not enough. Luckily there is a wellknown pattern around bound shared pointers in Asio that you can use to mimick a "session" per response.
Say you define a session to contain it's own private timer:
struct session : boost::enable_shared_from_this<session> {
session(boost::asio::io_service& svc, int N) :
timer(svc, boost::posix_time::seconds(N))
{
// Note: shared_from_this is not allowed from ctor
}
void start() {
// it's critical that the completion handler is bound to a shared
// pointer so the handler keeps the session alive:
timer.async_wait(boost::bind(&session::handler, shared_from_this(), boost::asio::placeholders::error));
}
private:
void handler(const boost::system::error_code &ec) {
std::cout << "hello, world" << "\t" << ec.message() << std::endl;
}
boost::asio::deadline_timer timer;
};
Now, it's trivial to replace the code that used the hardcoded timer instance:
timer.expires_from_now(boost::posix_time::seconds(nRecv));
timer.async_wait(handler);
with the session start:
boost::make_shared<session>(io_service, nRecv)->start();
A fully working example (with suitably stubbed ZMQ stuff): Live On Coliru
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <iostream>
boost::asio::io_service io_service;
/////////////////////////////////////////////////////////////////////////
// I love stubbing out stuff I don't want to install just to help others
enum { ZMQ_PULL };
static void* zmq_ctx_new() { return nullptr; }
static void* zmq_socket(void*,int) { return nullptr; }
static void zmq_bind(void*,char const*) {}
static void zmq_recv(void*,int*data,size_t,int)
{
boost::this_thread::sleep_for(boost::chrono::milliseconds(rand()%1000));
*data = 2;
}
// End of stubs :)
/////////////////////////////////////////////////////////////////////////
void* context = zmq_ctx_new();
void* sock_pull = zmq_socket(context, ZMQ_PULL);
struct session : boost::enable_shared_from_this<session> {
session(boost::asio::io_service& svc, int N) :
timer(svc, boost::posix_time::seconds(N))
{
// Note: shared_from_this is not allowed from ctor
}
void start() {
// it's critical that the completion handler is bound to a shared
// pointer so the handler keeps the session alive:
timer.async_wait(boost::bind(&session::handler, shared_from_this(), boost::asio::placeholders::error));
}
~session() {
std::cout << "bye (session end)\n";
}
private:
void handler(const boost::system::error_code &ec) {
std::cout << "hello, world" << "\t" << ec.message() << std::endl;
}
boost::asio::deadline_timer timer;
};
void run() {
io_service.run();
}
void thread_listener() {
int nRecv = 0;
for(int n=0; n<4; ++n) {
zmq_recv(sock_pull, &nRecv, sizeof(nRecv), 0);
std::cout << nRecv << std::endl;
boost::make_shared<session>(io_service, nRecv)->start();
}
}
int main() {
auto work = boost::make_shared<boost::asio::io_service::work>(io_service);
zmq_bind(sock_pull, "tcp://*:60000");
boost::thread tThread(thread_listener);
boost::thread tThreadRun(run);
tThread.join();
work.reset();
tThreadRun.join();
}
I'm learning Boost.Asio, but I have a question about boost::asio::deadline_timer async_wai. Here is the code from boost home page:
//
// timer.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
class printer
{
public:
printer(boost::asio::io_service& io)
: timer_(io, boost::posix_time::seconds(1)),
count_(0)
{
timer_.async_wait(boost::bind(&printer::print, this));
}
~printer()
{
std::cout << "Final count is " << count_ << "\n";
}
void print()
{
if (count_ < 5)
{
std::cout << count_ << "\n";
++count_;
timer_.expires_at(timer_.expires_at() + boost::posix_time::seconds(1));
timer_.async_wait(boost::bind(&printer::print, this));
}
}
private:
boost::asio::deadline_timer timer_;
int count_;
};
int main()
{
boost::asio::io_service io;
printer p(io);
io.run();
return 0;
}
async_wait require the function signature like this:
void handler(
const boost::system::error_code& error // Result of operation.
);
but in this domeļ¼ it is timer_.async_wait(boost::bind(&printer::print, this));, the signature is void print(printer*),How it works?
please help me, thank you.
Text from timer3 example
"In this example, the boost::asio::placeholders::error argument to boost::bind() is a named placeholder for the error object passed to the handler. When initiating the asynchronous operation, and if using boost::bind(), you must specify only the arguments that match the handler's parameter list. In tutorial Timer.4 you will see that this placeholder may be elided if the parameter is not needed by the callback handler. "
You can not use boost::asio::placeholders::error or use it in timer4 example.
Example 3 without boost::asio::placeholders::error
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
void print( boost::asio::deadline_timer* t, int* count)
{
if (*count < 5)
{
std::cout << *count << "\n";
++(*count);
t->expires_at(t->expires_at() + boost::posix_time::seconds(1));
t->async_wait(boost::bind(print, t, count));
}
}
int main()
{
boost::asio::io_service io;
int count = 0;
boost::asio::deadline_timer t(io, boost::posix_time::seconds(1));
t.async_wait(boost::bind(print, &t, &count));
io.run();
std::cout << "Final count is " << count << "\n";
return 0;
}
Example 4 with boost::asio::placeholders::error
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
class printer
{
public:
printer(boost::asio::io_service& io)
: timer_(io, boost::posix_time::seconds(1)),
count_(0)
{
timer_.async_wait(boost::bind(&printer::print, this, boost::asio::placeholders::error));
}
~printer()
{
std::cout << "Final count is " << count_ << "\n";
}
void print(const boost::system::error_code &e)
{
if (count_ < 5)
{
std::cout << count_ << "\n";
++count_;
timer_.expires_at(timer_.expires_at() + boost::posix_time::seconds(1));
timer_.async_wait(boost::bind(&printer::print, this, boost::asio::placeholders::error ));
}
}
private:
boost::asio::deadline_timer timer_;
int count_;
};
int main()
{
boost::asio::io_service io;
printer p(io);
io.run();
return 0;
}
Boost.Bind silently ignores extra arguments, take a look at bind documentaion.
EDIT :
A similar question to yours was already asked, take a look at it, there is more detailed answer.