I've been trying to build a messaging application using Boost::asio and for some reason my modified version of chat_client does not recieve any messages. I first thought the io_context running out of handlers but that was not the case. Weirdly, if I eliminate controller.cpp and create a chat_client object in main my problems are solved.
chat_client.cpp(modified from examples)I've hard coded IP of my server and port for testing
#include <string.h>
#include <string>
#include "chat_client.h"
using asio::ip::tcp;
typedef std::deque<chat_message> chat_message_queue;
chat_client::chat_client(std::string ip, std::string port)
: socket_(io_context_)
{
tcp::resolver resolver(io_context_);
endpoints = resolver.resolve(ip, port);
do_connect(endpoints);
t = new std::thread([this](){ io_context_.run(); });
}
chat_client::~chat_client()
{
t->join();
}
void chat_client::write(const chat_message& msg)
{
asio::post(io_context_,
[this, msg]()
{
bool write_in_progress = !write_msgs_.empty();
write_msgs_.push_back(msg);
if (!write_in_progress)
{
do_write();
}
});
}
void chat_client::close()
{
asio::post(io_context_, [this]() { socket_.close(); });
}
void chat_client::do_connect(const tcp::resolver::results_type& endpoints)
{
asio::async_connect(socket_, endpoints,
[this](std::error_code ec, tcp::endpoint)
{
if (!ec)
{
do_read_header();
}
});
}
void chat_client::do_read_header()
{
asio::async_read(socket_,
asio::buffer(read_msg_.data(), chat_message::header_length),
[this](std::error_code ec, std::size_t /*length*/)
{
if (!ec && read_msg_.decode_header())
{
do_read_body();
}
else
{
socket_.close();
}
});
}
void chat_client::do_read_body()
{
asio::async_read(socket_,
asio::buffer(read_msg_.body(), read_msg_.body_length()),
[this](std::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
std::cout.write(read_msg_.body(), read_msg_.body_length());
std::cout << "\n";
do_read_header();
}
else
{
socket_.close();
}
});
}
void chat_client::do_write()
{
asio::async_write(socket_,
asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
[this](std::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
write_msgs_.pop_front();
if (!write_msgs_.empty())
{
do_write();
}
}
else
{
socket_.close();
}
});
}
controller.cpp
#include <stdexcept>
#include "controller.h"
//#include "chat_client.h" inside controller.h
/*Constructor*/
Controller::Controller() {}
void Controller::execute_cmd(int cmd)
{
switch(cmd)
{
case 1: //Create chat_client
{
try
{
c = new chat_client("192.168.0.11", "5000");
chat_message msg;
msg.body_length(5);
std::memcpy(msg.body(), "test", msg.body_length());
msg.encode_header();
c->write(msg);
}
catch(std::runtime_error& e)
{
std::cerr << "Cannot create chat_client" << std::endl;
}
}
case 2: //Close chat_client
{
c->close();
delete c;
}
case 3: //TESTING
{
char line[chat_message::max_body_length + 1];
while (std::cin.getline(line, chat_message::max_body_length + 1))
{
chat_message msg;
msg.body_length(std::strlen(line));
std::memcpy(msg.body(), line, msg.body_length());
msg.encode_header();
c->write(msg);
}
}
}
}
main.cpp used for testing
#include "controller.h"
int main(int argc, char** argv)
{
Controller c;
c.execute_cmd(1); //create chat_client
c.execute_cmd(2); //Start sending messages
return 0;
}
Your switch statement un execute_cmd has not break at end of case. In switch statemte if case not end with break then continué with the next sentence of next case. c.execite_cmd(1] really execute cmd 1, 2 and 3. Remenber that read and write io are async so in cmd 2 tour close connection.
Related
I've created a simple wrapper for boost::asio library. My wrapper consists of 4 main classes: NetServer (server), NetClient (client), NetSession (client/server session) and Network (composition class of these three which also includes all callback methods).
The problem is that the first connection client/server works flawlessly, but when I then stop server, start it again and then try to connect the client, the server just doesn't recognize the client. It seems like the acceptor callback isn't called. And client does connect to server, because first - the connection goes without errors, second - when I close the server's program, the client receives the error message WSAECONNRESET.
I've created test program which emulates the procedure written above. It does following:
Starts the server
Starts the client
Client succesfully connects to server
Stops the server
Client receives the error and disconnects itself
Starts the server again
Client again succesfully connects to server
BUT SERVER DOESN'T CALL THE ACCEPTOR CALLBACK ANYMORE
It means that in point 3 the acceptor succesfully calls the callback function, but in point 7 the acceptor doesn't call the callback.
I think I do something wrong in stop()/start() method of the server, but I can't figure out what's exactly wrong.
The source of the NetServer class:
NetServer::NetServer(Network& netRef) : net{ netRef }
{
acceptor = std::make_unique<boost::asio::ip::tcp::acceptor>(ioc);
}
NetServer::~NetServer(void)
{
ioc.stop();
if (threadStarted)
{
th.join();
threadStarted = false;
}
if (active)
stop();
}
int NetServer::start(void)
{
assert(getAcceptHandler() != nullptr);
assert(getHeaderHandler() != nullptr);
assert(getDataHandler() != nullptr);
assert(getErrorHandler() != nullptr);
closeAll();
try
{
ep = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), srvPort);
acceptor->open(ep.protocol());
acceptor->bind(ep);
acceptor->listen();
initAccept();
}
catch (system::system_error& e)
{
return e.code().value();
}
if (!threadStarted)
{
th = std::thread([this]()
{
ioc.run();
});
threadStarted = true;
}
active = true;
return Network::NET_OK;
}
int NetServer::stop(void)
{
ioc.post(boost::bind(&NetServer::_stop, this));
return Network::NET_OK;
}
void NetServer::_stop(void)
{
boost::system::error_code ec;
acceptor->close(ec);
for (auto& s : sessions)
closeSession(s.get(), false);
active = false;
}
void NetServer::initAccept(void)
{
sock = std::make_shared<asio::ip::tcp::socket>(ioc);
acceptor->async_accept(*sock.get(), [this](const boost::system::error_code& error)
{
onAccept(error, sock);
});
}
void NetServer::onAccept(const boost::system::error_code& ec, SocketSharedPtr sock)
{
if (ec.value() == 0)
{
if (accHandler())
{
addSession(sock);
initAccept();
}
}
else
getErrorHandler()(nullptr, ec);
}
SessionPtr NetServer::addSession(SocketSharedPtr sock)
{
std::lock_guard<std::mutex> guard(mtxSession);
auto session = std::make_shared<NetSession>(sock, *this, true);
sessions.insert(session);
session->start();
return session;
}
SessionPtr NetServer::findSession(const SessionPtr session)
{
for (auto it = std::begin(sessions); it != std::end(sessions); it++)
if (*it == session)
return *it;
return nullptr;
}
bool NetServer::closeSession(const void *session, bool erase /* = true */)
{
std::lock_guard<std::mutex> guard(mtxSession);
for (auto it = std::begin(sessions); it != std::end(sessions); it++)
if (it->get() == session)
{
try
{
it->get()->getSocket()->cancel();
it->get()->getSocket()->shutdown(asio::socket_base::shutdown_send);
it->get()->getSocket()->close();
it->get()->getSocket().reset();
}
catch (system::system_error& e)
{
UNREFERENCED_PARAMETER(e);
}
if (erase)
sessions.erase(*it);
return true;
}
return false;
}
void NetServer::closeAll(void)
{
using namespace boost::placeholders;
std::lock_guard<std::mutex> guard(mtxSession);
std::for_each(sessions.begin(), sessions.end(), boost::bind(&NetSession::stop, _1));
sessions.clear();
}
bool NetServer::write(const SessionPtr session, std::string msg)
{
if (SessionPtr s = findSession(session); s)
{
s->addMessage(msg);
if (s->canWrite())
s->write();
return true;
}
return false;
}
This is the output from the server:
Enter 0 - server, 1 - client: 0
1. Server started
3. Client connected to server
Stopping server....
4. Server stopped
Net error, server, acceptor: ERROR_OPERATION_ABORTED
Net error, server, ERROR_OPERATION_ABORTED
Client session deleted
6. Server started again
(HERE SHOULD BE "8. Client again connected to server", but the server didn't recognize the reconnected client!)
And from the client:
Enter 0 - server, 1 - client: 1
2. Client started and connected to server
Net error, client: ERROR_FILE_NOT_FOUND
5. Client disconnected from server
Waiting 3 sec before reconnect...
Connecting to server...
7. Client started and connected to server
(WHEN I CLOSE THE SERVER WINDOW, I RECVEIVE HERE THE "Net error, client: WSAECONNRESET" MESSAGE - it means client was connected to server anyhow!)
If the code of NetClient, NetSession and Network is necessary, just let me know.
Thanks in advance
Wow. There's a lot to unpack. There is quite a lot of code smell that reminds me of some books on Asio programming that turned out to be... not excellent in my previous experience.
I couldn't give any real advice without grokking your code, which requires me to review in-depth and add missing bits. So let me just provide you with my reviewed/fixed code first, then we'll talk about some of the details.
A few areas where you seemed to have trouble making up your mind:
whether to use a strand or to use mutex locking
whether to use async or sync (e.g. closeSession is completely synchronous and blokcing)
whether to use shared-pointers for lifetime or not: on the one hand you have NetSesion support shared_from_this, but on the other hand you are keeping them alive in a sessions collection.
whether to use smart pointers or raw pointers (sp.get() is a code smell)
whether to use void* pointers or forward declared structs for opaque implementation
whether to use exceptions or to use error codes. Specifically:
return e.code().value();
is a Very Bad Idea. Just return error_code already. Or just propagate the exception.
judging from the use, my best bet is that sessions is std::set<SessionPtr>. Then it's funny that you're doing linear searches. In fact, findSession could be:
SessionPtr findSession(SessionPtr const& session) {
std::lock_guard guard(mtxSessions);
return sessions.contains(session)? session: nullptr;
}
In fact, given some natural invariants, it could just be
auto findSession(SessionPtr s) { return std::move(s); }
Note as well, you had forgotten to lock the mutex in findSession
closeSession completely violates Law Of Demeter, 6*3 times over if you will. In my example I make it so SessHandle is a weak pointer to NetSession and you can just write:
for (auto& handle : sessions)
if (auto sess = handle.lock())
sess->close();
Of course, sess->close() should not block
Also, it should correctly synchronize on the session e.g. using the sessions strand:
void close() {
return post(sock_.get_executor(), [this, self = shared_from_this()] {
error_code ec;
if (!ec) sock_.cancel(ec);
if (!ec) sock_.shutdown(tcp::socket::shutdown_send, ec);
if (!ec) sock_.close(ec);
});
}
If you insist, you can make it so the caller can still await the result and receive any exceptions:
std::future<void> close() {
return post(
sock_.get_executor(),
std::packaged_task<void()>{[this, self = shared_from_this()] {
sock_.cancel();
sock_.shutdown(tcp::socket::shutdown_send);
sock_.close();
}});
}
Honestly, that seems overkill since you never look at the return value anyways.
In general, I recommend leaving socket::close() to the destructor. It avoids a specific class of race-conditions on socket handles.
Don't use boolean flags (isThreadActive is better replaced with th.joinable())
apparently you had NetSession::stop which I imagine did largely the same as closeSession but in the right place? I replaced it with the new NetSession::close
subtly when accHandler returned false, you would exit the accept loop alltogether. I doubt that was on purpose
try to minimize time under locks:
std::future<void> close() {
return post(
sock_.get_executor(),
std::packaged_task<void()>{[this, self = shared_from_this()] {
sock_.cancel();
sock_.shutdown(tcp::socket::shutdown_send);
sock_.close();
}});
}
I will show you how to do without the lock entirely instead.
Demo Listing
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <deque>
#include <iostream>
#include <iomanip>
#include <set>
using namespace std::chrono_literals;
using namespace std::placeholders;
namespace asio = boost::asio;
using asio::ip::tcp;
using boost::system::error_code;
static inline std::ostream debug(std::cerr.rdbuf());
struct Network {
static constexpr error_code NET_OK{};
};
struct NetSession; // opaque forward reference
struct NetServer;
using SessHandle = std::weak_ptr<NetSession>; // opaque handle
using Sessions = std::set<SessHandle, std::owner_less<>>;
struct NetSession : std::enable_shared_from_this<NetSession> {
NetSession(tcp::socket&& s, NetServer& srv, bool)
: sock_(std::move(s))
, srv_(srv) {
debug << "New session from " << getPeer() << std::endl;
}
void start() {
post(sock_.get_executor(),
std::bind(&NetSession::do_read, shared_from_this()));
}
tcp::endpoint getPeer() const { return peer_; }
void close() {
return post(sock_.get_executor(), [this, self = shared_from_this()] {
debug << "Closing " << getPeer() << std::endl;
error_code ec;
if (!ec) sock_.cancel(ec);
if (!ec) sock_.shutdown(tcp::socket::shutdown_send, ec);
// if (!ec) sock_.close(ec);
});
}
void addMessage(std::string msg) {
post(sock_.get_executor(),
[this, msg = std::move(msg), self = shared_from_this()] {
outgoing_.push_back(std::move(msg));
if (canWrite())
write_loop();
});
}
private:
// assumed on (logical) strand
bool canWrite() const { // FIXME misnomer: shouldStartWriteLoop()?
return outgoing_.size() == 1;
}
void write_loop() {
if (outgoing_.empty())
return;
async_write(sock_, asio::buffer(outgoing_.front()),
[this, self = shared_from_this()](error_code ec, size_t) {
if (!ec) {
outgoing_.pop_front();
write_loop();
}
});
}
void do_read() {
incoming_.clear();
async_read_until(
sock_, asio::dynamic_buffer(incoming_), "\n",
std::bind(&NetSession::on_read, shared_from_this(), _1, _2));
}
void on_read(error_code ec, size_t);
tcp::socket sock_;
tcp::endpoint peer_ = sock_.remote_endpoint();
NetServer& srv_;
std::string incoming_;
std::deque<std::string> outgoing_;
};
using SessionPtr = std::shared_ptr<NetSession>;
using SocketSharedPtr = std::shared_ptr<tcp::socket>;
struct NetServer {
NetServer(Network& netRef) : net{netRef} {}
~NetServer()
{
if (acceptor.is_open())
acceptor.cancel(); // TODO seems pretty redundant
stop();
if (th.joinable())
th.join();
}
std::function<bool()> accHandler;
std::function<void(SocketSharedPtr, error_code)> errHandler;
// TODO headerHandler
std::function<void(SessionPtr, error_code, std::string)> dataHandler;
error_code start() {
assert(accHandler);
assert(errHandler);
assert(dataHandler);
closeAll(sessions);
error_code ec;
if (!ec) acceptor.open(tcp::v4(), ec);
if (!ec) acceptor.bind({{}, srvPort}, ec);
if (!ec) acceptor.listen(tcp::socket::max_listen_connections, ec);
if (!ec) {
do_accept();
if (!th.joinable()) {
th = std::thread([this] { ioc.run(); }); // TODO exceptions!
}
}
if (ec && acceptor.is_open())
acceptor.close();
return ec;
}
void stop() { //
post(ioc, std::bind(&NetServer::do_stop, this));
}
void closeSession(SessHandle handle, bool erase = true) {
post(acceptor.get_executor(), [=, this] {
if (auto s = handle.lock()) {
s->close();
}
if (erase) {
sessions.erase(handle);
}
});
}
void closeAll() {
post(acceptor.get_executor(), [this] {
closeAll(sessions);
sessions.clear();
});
}
// TODO FIXME is the return value worth it?
bool write(SessionPtr const& session, std::string msg) {
return post(acceptor.get_executor(),
std::packaged_task<bool()>{std::bind(
&NetServer::do_write, this, session, std::move(msg))})
.get();
}
// compare
void writeAll(std::string msg) {
post(acceptor.get_executor(),
std::bind(&NetServer::do_write_all, this, std::move(msg)));
}
private:
Network& net;
asio::io_context ioc;
tcp::acceptor acceptor{ioc}; // active -> acceptor.is_open()
std::thread th; // threadActive -> th.joinable()
Sessions sessions;
std::uint16_t srvPort = 8989;
// std::mutex mtxSessions; // note naming; also replaced by logical strand
// assumed on acceptor logical strand
void do_accept() {
acceptor.async_accept(
make_strand(ioc), [this](error_code ec, tcp::socket sock) {
if (ec.failed()) {
return errHandler(nullptr, ec);
}
if (accHandler()) {
auto s = std::make_shared<NetSession>(std::move(sock),
*this, true);
sessions.insert(s);
s->start();
}
do_accept();
});
}
SessionPtr do_findSession(SessionPtr const& session) {
return sessions.contains(session) ? session : nullptr;
}
bool do_write(SessionPtr session, std::string msg) {
if (auto s = do_findSession(session)) {
s->addMessage(std::move(msg));
return true;
}
return false;
}
void do_write_all(std::string msg) {
for(auto& handle : sessions)
if (auto sess = handle.lock())
do_write(sess, msg);
}
static void closeAll(Sessions const& sessions) {
for (auto& handle : sessions)
if (auto sess = handle.lock())
sess->close();
}
void do_stop()
{
if (acceptor.is_open()) {
error_code ec;
acceptor.close(ec); // TODO error handling?
}
closeAll(sessions); // TODO FIXME why not clear sessions?
}
};
// Implementation must be after NetServer definition:
void NetSession::on_read(error_code ec, size_t) {
if (srv_.dataHandler)
srv_.dataHandler(shared_from_this(), ec, std::move(incoming_));
if (!ec)
do_read();
}
int main() {
Network net;
NetServer srv{net};
srv.accHandler = [] { return true; };
srv.errHandler = [](SocketSharedPtr, error_code ec) {
debug << "errHandler: " << ec.message() << std::endl;
};
srv.dataHandler = [](SessionPtr sess, error_code ec, std::string msg) {
debug << "dataHandler: " << sess->getPeer() << " " << ec.message()
<< " " << std::quoted(msg) << std::endl;
};
srv.start();
std::this_thread::sleep_for(10s);
std::cout << "Shutdown started" << std::endl;
srv.writeAll("We're going to shutdown, take care!\n");
srv.stop();
}
Live Demo:
I try to make TCP asynchronous server, but I'm encountering some difficulties with async_read function. The handler async_read function is never called. I use this command to write on server's socket "nc -C 127.0.0.1 4242". Can you explain to me what I didn't understand or what mistakes I have made? Thanks for your help.
ChatSession.cpp
ChatSession::ChatSession(boost::asio::io_context& ioc)
: __socket(ioc)
{
}
ChatSession::pointer ChatSession::create(boost::asio::io_context& ioc)
{
return pointer(new ChatSession(ioc));
}
tcp::socket& ChatSession::socket()
{
return this->__socket;
}
void ChatSession::handle_start()
{
}
void ChatSession::start()
{
std::string buffer = "Bienvenue\n";
boost::asio::async_write(__socket, boost::asio::buffer(buffer),
boost::bind(&ChatSession::handle_start, shared_from_this()));
readHeader();
}
void ChatSession::handleReadHeader(const boost::system::error_code& error,
std::size_t byte_transferred)
{
if (!error && this->__readMessage.decodeHeader()) {
std::strcpy(__readMessage.getData(), __test.data());
std::cout << __readMessage.getData() << std::endl;
readBody();
} else {
std::cerr << "Erreur" << std::endl;
}
}
void ChatSession::readHeader()
{
__test.resize(6);
boost::asio::async_read(__socket,
boost::asio::buffer(__test),
boost::bind(
&ChatSession::handleReadHeader,
shared_from_this(),
boost::asio::placeholders::error,
}
TcpServer.cpp:
TcpServer::TcpServer(boost::asio::io_context& io_context, int port)
: __acceptor (io_context, tcp::endpoint(tcp::endpoint(tcp::v4(), port)))
{
startAccept();
}
void TcpServer::startAccept()
{
ChatSession::pointer newConnection = ChatSession::create(__io_context);
__acceptor.async_accept(newConnection->socket(),
boost::bind(&TcpServer::handle_accept, this, newConnection,
boost::asio::placeholders::error));
}
void TcpServer::handle_accept(ChatSession::pointer newConnection,
const boost::system::error_code& error)
{
if (!error) {
newConnection->start();
startAccept();
}
}
main.cpp:
int main(int ac, char** av) {
try {
boost::asio::io_context io_context;
TcpServer server(io_context, 4242);
io_context.run();
} catch(const std::exception& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
I'm trying to make a client class from boost TCP client example for my projects, and I've noticed that sometimes handle_connect doesn't get called when connecting to nonexistent host.
I've read similar issues here on stack, where people forgot to run io_service or called it before any tasks were posted, but I don't think that's my case, since I launch io_service.run() thread right after calling async_connect, and successfull connect, network unreachable, and some other cases I've tested work just fine.
Here is the full listing:
tcp_client.hpp
#ifndef TCP_CLIENT_HPP
#define TCP_CLIENT_HPP
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <boost/thread/thread.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <mutex>
#include <iostream>
#include <iomanip>
namespace com {
using boost::asio::ip::tcp;
using namespace std;
class client : public boost::enable_shared_from_this<client> {
private:
std::mutex mx_;
bool stopped_ = 1;
boost::asio::streambuf ibuf_;
boost::shared_ptr<boost::asio::io_service> io_service_;
boost::shared_ptr<boost::asio::ip::tcp::socket> sock_;
boost::shared_ptr<tcp::resolver::iterator> ei_;
std::vector<std::string> inbound_;
std::string host_, port_;
public:
client() {}
void connect( std::string host, std::string port ) {
if (!stopped_) stop();
host_ = host; port_ = port;
io_service_.reset(new boost::asio::io_service);
sock_.reset(new boost::asio::ip::tcp::socket(*io_service_));
ei_.reset(new tcp::resolver::iterator);
tcp::resolver r(*io_service_);
ei_ = boost::make_shared<tcp::resolver::iterator>( r.resolve(tcp::resolver::query(host_, port_)) );
stopped_ = 0;
start_connect();
boost::thread work( boost::bind(&client::work, shared_from_this()) );
return;
}
bool is_running() {
return !stopped_;
}
void stop() {
stopped_ = 1;
sock_->close();
return;
}
void send(std::string str) {
if (stopped_) return;
auto msg = boost::asio::buffer(str, str.size());
boost::asio::async_write( (*sock_), msg, boost::bind(&client::handle_write, shared_from_this(), _1) );
return;
}
std::string pull() {
std::lock_guard<std::mutex> lock(mx_);
std::string msg;
if (inbound_.size()>0) {
msg = inbound_.at(0);
inbound_.erase(inbound_.begin());
}
return msg;
}
int size() {
std::lock_guard<std::mutex> lock(mx_);
return inbound_.size();
}
void clear() {
std::lock_guard<std::mutex> lock(mx_);
inbound_.clear();
return;
}
private:
void work() {
if (stopped_) return;
std::cout<<"work in"<<std::endl;
io_service_->run();
std::cout<<"work out"<<std::endl;
return;
}
void start_connect() {
if ((*ei_) != tcp::resolver::iterator()) {
std::cout<<"Trying "<<(*ei_)->endpoint()<<std::endl;
sock_->async_connect( (*ei_)->endpoint(), boost::bind(&client::handle_connect, shared_from_this(), boost::asio::placeholders::error) );
} else {
stop();
}
return;
}
void handle_connect(const boost::system::error_code& ec) {
if (stopped_) return;
if (!sock_->is_open()) {
std::cout<<"Socket closed"<<std::endl;
(*ei_)++;
start_connect();
} else if (ec) {
std::cout<<"Connect error: "<<ec.message()<<std::endl;
sock_->close();
(*ei_)++;
start_connect();
} else {
std::cout<<"Connected to "<<(*ei_)->endpoint()<<std::endl;
start_read();
}
return;
}
void start_read() {
if (stopped_) return;
boost::asio::async_read_until((*sock_), ibuf_, "", boost::bind(&client::handle_read, shared_from_this(), boost::asio::placeholders::error));
return;
}
void handle_read(const boost::system::error_code& ec) {
std::lock_guard<std::mutex> lock(mx_);
if (stopped_) return;
if (ec) {
std::cout<<"Read error: "<<ec.message()<<std::endl;
stop();
return;
}
std::string line;
std::istream is(&ibuf_);
std::getline(is, line);
if (!line.empty() && inbound_.size()<1000) inbound_.push_back(line);
start_read();
return;
}
private:
void handle_write(const boost::system::error_code& ec) {
if (stopped_) return;
if (ec) {
std::cout<<"Write error: "<<ec.message()<<std::endl;
stop();
return;
}
return;
}
};
};
and tcp_test.cpp
#include "tcp_client.hpp"
int main(int argc, char* argv[]) {
auto tcp_client = boost::shared_ptr<com::client>(new com::client);
try {
tcp_client->connect("192.168.1.15", "50000");
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
tcp_client->connect("192.168.1.20", "50000");
} catch (std::exception& e) {
std::cerr<<"Exception: "<<e.what()<<std::endl;
}
int cnt=0;
while (cnt<5) {
std::cout<<cnt<<std::endl;
cnt++;
tcp_client->send("<test>");
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
}
tcp_client->stop();
while (tcp_client->size()>0) std::cout<<tcp_client->pull()<<std::endl;
return 0;
}
The output I get is when connecting to loopback server:
Trying 192.168.1.15:50000
work in
work out
Trying 192.168.1.20:50000
0
work in
Connected to 192.168.1.20:50000
1
2
3
4
work out
<test>
<test>
<test>
<test>
<test>
The 192.168.1.20 works just as it should, as you see. The 192.168.1.15 doesnt'e exist, but I've expected it to throw some kind of error. Instead io_service.run() returns right away, like async_connect never posted callback task. Maybe it's related to endpoint iterator and not async_connect?
Can anyone please explain why is it happening like this?
Then I've tried to isolate the problem in this code:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <boost/thread/thread.hpp>
boost::asio::io_service io_svc;
boost::asio::ip::tcp::socket sock(io_svc);
boost::asio::ip::tcp::resolver::iterator ei;
void work() {
std::cout<<"work in"<<std::endl;
io_svc.run();
std::cout<<"work out"<<std::endl;
return;
}
void stop() {
sock.close();
return;
}
void start_connect();
void handle_connect(const boost::system::error_code& ec) {
if (!sock.is_open()) {
std::cout<<"Socket closed"<<std::endl;
ei++;
start_connect();
} else if (ec) {
std::cout<<"Connect error: "<<ec.message()<<std::endl;
sock.close();
ei++;
start_connect();
} else {
std::cout<<"Connected to "<<ei->endpoint()<<std::endl;
}
return;
}
void start_connect() {
if (ei != boost::asio::ip::tcp::resolver::iterator()) {
std::cout<<"Trying "<<ei->endpoint()<<std::endl;
sock.async_connect( ei->endpoint(), boost::bind(handle_connect, boost::asio::placeholders::error) );
} else {
stop();
}
return;
}
int main(int argc, char* argv[]) {
std::string host="192.168.1.15", port="50000";
boost::asio::ip::tcp::resolver r(io_svc);
ei = r.resolve(boost::asio::ip::tcp::resolver::query(host, port));
start_connect();
boost::thread* thr = new boost::thread(work);
boost::this_thread::sleep_for(boost::chrono::milliseconds(2000));
return 0;
}
But I've got a totally different result. When I try to connect to a nonexistent host, most of the time it's:
Trying 192.168.1.15:50000
work in
Sometimes it's:
Trying 192.168.1.15:50000
work in
Connect error: Operation canceled
Connect error: Operation canceled
And rarely it's:
Trying 192.168.1.15:50000
work in
Segmentation fault
"work out" is never printed, so I'm guessing io_service in this example is doing something, but how is this different from previous code, and why I get "operation canceled" error only sometimes?
A client running in a background thread should look something like this.
Note that I have note included things like connection timeouts. For that you'd want to have a deadline timer running in parallel with the async_connect. Then you'd have to correctly handle crossing cases (hint: cancel the deadline timer on successful connect and throw away the ensuing error from its async_wait).
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <thread>
#include <functional>
boost::asio::io_service io_svc;
struct client
: std::enable_shared_from_this<client>
{
using protocol = boost::asio::ip::tcp;
using resolver = protocol::resolver;
using socket = protocol::socket;
using error_code = boost::system::error_code;
client(boost::asio::io_service& ios)
: ios_(ios) {}
void start(std::string const& host, std::string const& service)
{
auto presolver = std::make_shared<resolver>(get_io_service());
presolver->async_resolve(protocol::resolver::query(host, service),
strand_.wrap([self = shared_from_this(), presolver](auto&& ec, auto iter)
{
self->handle_resolve(ec, presolver, iter);
}));
}
private:
void
handle_resolve(boost::system::error_code const& ec, std::shared_ptr<resolver> presolver, resolver::iterator iter)
{
if (ec) {
std::cerr << "error resolving: " << ec.message() << std::endl;
}
else {
boost::asio::async_connect(sock, iter, strand_.wrap([self = shared_from_this(),
presolver]
(auto&& ec, auto iter)
{
self->handle_connect(ec, iter);
// note - we're dropping presolver here - we don't need it any more
}));
}
}
void handle_connect(error_code const& ec, resolver::iterator iter)
{
if (ec) {
std::cerr << "failed to connect: " << ec.message() << std::endl;
}
else {
auto payload = std::make_shared<std::string>("Hello");
boost::asio::async_write(sock, boost::asio::buffer(*payload),
strand_.wrap([self = shared_from_this(),
payload] // note! capture the payload so it continues to exist during async send
(auto&& ec, auto size)
{
self->handle_send(ec, size);
}));
}
}
void handle_send(error_code const& ec, std::size_t size)
{
if (ec) {
std::cerr << "send failed after " << size << " butes : " << ec.message() << std::endl;
}
else {
// send something else?
}
}
boost::asio::io_service& get_io_service()
{
return ios_;
}
private:
boost::asio::io_service& ios_;
boost::asio::strand strand_{get_io_service()};
socket sock{get_io_service()};
};
void work()
{
std::cout << "work in" << std::endl;
io_svc.run();
std::cout << "work out" << std::endl;
return;
}
int main(int argc, char *argv[])
{
auto pclient = std::make_shared<client>(io_svc);
std::string host = "192.168.1.15", port = "50000";
pclient->start(host, port);
auto run_thread = std::thread(work);
if (run_thread.joinable())
run_thread.join();
return 0;
}
example output:
work in
<time passes>...
failed to connect: Operation timed out
work out
I'm trying to make a chat with boost asio, having the officals example as reference. But i'm actually having two problems with async_read, first, why do I have an EoF(end of file) and my connexion(and application) close so quickly in the client?
And then I have a problem in the async_read of ChatParticipant : if I pass "Shared_from_this()" as second argument of boost::bind I get and error R6010, but if i pass a simple "this", I don't have it.
Thanks for your help :)
Here is my concerned code :
Chat Server
class ChatServer
{
public:
ChatServer(boost::asio::io_service& io_service, const tcp::endpoint& endpoint): _io_service(io_service), _acceptor(io_service, endpoint)
{
startAccept();
}
void startAccept()
{
std::cout << "accepting a new client" << std::endl;
shared_ptr<ServerParticipant> newParticipant(new ServerParticipant(_io_service, &_room));
_acceptor.async_accept(newParticipant->getSocket(), boost::bind(&ChatServer::handleAccept, this, boost::asio::placeholders::error, newParticipant));
}
void handleAccept(const boost::system::error_code& e, shared_ptr<ServerParticipant> newParticipant)
{
if (!e)
{
std::cout << "accepted a new client" << std::endl;
boost::asio::async_read(newParticipant->getSocket(),
boost::asio::buffer(_readMsgCache.getAllMessage(), ChatMessage::header_length),
boost::bind(&ChatServer::read, this,
boost::asio::placeholders::error));
//newParticipant->start();
startAccept();
}
else
{
std::cerr << e.message() << std::endl;
}
}
void read(const boost::system::error_code& e)
{
if (e && e == boost::asio::error::eof)
{
std::cerr << "closed" << std::endl;
}
if (e)
{
std::cerr << e.message() << std::endl;
}
else
{
std::cout << "Reaaad" << std::endl;
}
}
private:
boost::asio::io_service& _io_service;
tcp::acceptor _acceptor;
ChatMessage _readMsgCache;
}
Chat Participant
class ChatParticipant : public boost::enable_shared_from_this<ChatParticipant>
{
public :
ChatParticipant(boost::asio::io_service& service) : _id(0), _service(service), _socket(service)
{}
virtual void start()
{
startRead();
}
void startRead()
{
std::cout << "Start read" << std::endl;
boost::asio::async_read(_socket,
boost::asio::buffer(_readMsgCache.getAllMessage(), 4),
boost::bind(
&ChatParticipant::readHeader, shared_from_this(),
boost::asio::placeholders::error));
}
// some functions about decoding the message...
protected:
int _id;
boost::asio::io_service& _service;
tcp::socket _socket;
std::deque<ChatMessage> _writeMsgCache;
ChatMessage _readMsgCache;
Chat Client
using boost::asio::ip::tcp;
class ChatClient
{
public:
ChatClient(boost::asio::io_service& service, tcp::endpoint& endpoint) : _service(service), _client(service)
{
_client.getSocket().async_connect(endpoint, boost::bind(&ChatClient::handleConnect, this, boost::asio::placeholders::error));
}
void handleConnect(const boost::system::error_code& err)
{
if (err)
{
std::cerr << err.message();
}
else
{
_client.start();
/*
ChatMessage message;
message.setMessage("hello");
_client.speak(message);
*/
}
}
void read(const boost::system::error_code& e)
{
if (e)
{
std::cerr << e.message() << std::endl;
}
else
{
std::cout << "Reaaad" << std::endl;
}
}
protected :
boost::asio::io_service& _service;
ChatParticipant _client;
};
ChatMessage
#pragma once
#include <stdlib.h>
#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>
using namespace std;
class ChatMessage
{
public :
enum{header_length = 4};
static const size_t headerLength = 4;
static const size_t maxMsgLength = 512;
ChatMessage()
{
clear();
}
void clear()
{
for (size_t i = 0; i <= maxLength() ; ++i)
_allMessage[i] = '\0';
}
char* getAllMessage()
{
return _allMessage;
}
const char* getAllMessage() const
{
return _allMessage;
}
char* getBody()
{
return _allMessage + headerLength;
}
size_t getBodyLength()
{
return _bodyLength;
}
size_t length()
{
return headerLength + _bodyLength;
}
const size_t length() const
{
return headerLength + _bodyLength;
}
size_t maxLength()
{
return headerLength + maxMsgLength;
}
bool setBody(const char* message)
{
_bodyLength = strlen(message);
if (_bodyLength > maxMsgLength)
return false;
memcpy(getBody(), message, _bodyLength);
return true;
}
bool setMessage(const char* message)
{
clear();
if (!setBody(message))
return false;
encodeHeader();
return true;
}
#pragma warning(disable: 4996) /* Disable deprecation */
bool decodeHeader()
{
char header[headerLength + 1] = "";
strncat(header, _allMessage, headerLength);
_bodyLength = atoi(header);
if (_bodyLength > maxMsgLength)
return false;
return true;
}
void encodeHeader()
{
stringstream ss;
ss << setw(headerLength) << _bodyLength;
string s(ss.str());
memcpy(_allMessage,s.c_str(), headerLength);
}
private :
char _allMessage[headerLength + maxMsgLength];
size_t _bodyLength;
};
main
#include "ChatMessage.h"
#define IsServer true
#ifdef IsServer
#include "ChatServer.h"
#else
#include "ChatCLient.h"
#endif
using boost::asio::ip::tcp;
int main()
{
boost::asio::io_service service;
#ifdef IsServer
tcp::endpoint endpoint(tcp::v4(), 13);
std::cout << "Server start" << std::endl;
ChatServer server(service, endpoint);
#else
tcp::endpoint endpoint(boost::asio::ip::address_v4::from_string("127.0.0.1"), 13);
std::cout << "Client start" << std::endl;
ChatClient client(service, endpoint);
#endif
service.run();
return 0;
}
The R6010 error is probably caused by an uncaught exception. shared_from_this() is based on a weak pointer and its operation depends upon the object being the target of a shared pointer.
That is:
shared_ptr<ChatClient> client1 (new ChatClient);
// this client can use shared_from_this()
but
ChatClient client2;
// this client cannot use shared_from_this().
change
ChatParticipant _client;
to
boost::shared_ptr<ChatParticipant> _client;
and initialize the _client pointer in the ctor initialization list. This will allow you to invoke shared_from_this() to get a shared_ptr to _client.
Here's my implementation :
Client A send a message for Client B
Server process the message by async_read the right amount of data and
will wait for new data from Client A (in Order not to block Client A)
Afterwards Server will process the information (probably do a mysql
query) and then send the message to Client B with async_write.
The problem is, if Client A send message really fast, async_writes will interleave before the previous async_write handler is called.
Is there a simple way to avoid this problem ?
EDIT 1 :
If a Client C sends a message to Client B just after Client A, the same issue should appear...
EDIT 2 :
This would work ? because it seems to block, I don't know where...
namespace structure {
class User {
public:
User(boost::asio::io_service& io_service, boost::asio::ssl::context& context) :
m_socket(io_service, context), m_strand(io_service), is_writing(false) {}
ssl_socket& getSocket() {
return m_socket;
}
boost::asio::strand getStrand() {
return m_strand;
}
void push(std::string str) {
m_strand.post(boost::bind(&structure::User::strand_push, this, str));
}
void strand_push(std::string str) {
std::cout << "pushing: " << boost::this_thread::get_id() << std::endl;
m_queue.push(str);
if (!is_writing) {
write();
std::cout << "going to write" << std::endl;
}
std::cout << "Already writing" << std::endl;
}
void write() {
std::cout << "writing" << std::endl;
is_writing = true;
std::string str = m_queue.front();
boost::asio::async_write(m_socket,
boost::asio::buffer(str.c_str(), str.size()),
boost::bind(&structure::User::sent, this)
);
}
void sent() {
std::cout << "sent" << std::endl;
m_queue.pop();
if (!m_queue.empty()) {
write();
return;
}
else
is_writing = false;
std::cout << "done sent" << std::endl;
}
private:
ssl_socket m_socket;
boost::asio::strand m_strand;
std::queue<std::string> m_queue;
bool is_writing;
};
}
#endif
Is there a simple way to avoid this problem ?
Yes, maintain an outgoing queue for each client. Inspect the queue size in the async_write completion handler, if non-zero, start another async_write operation. Here is a sample
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <deque>
#include <iostream>
#include <string>
class Connection
{
public:
Connection(
boost::asio::io_service& io_service
) :
_io_service( io_service ),
_strand( _io_service ),
_socket( _io_service ),
_outbox()
{
}
void write(
const std::string& message
)
{
_strand.post(
boost::bind(
&Connection::writeImpl,
this,
message
)
);
}
private:
void writeImpl(
const std::string& message
)
{
_outbox.push_back( message );
if ( _outbox.size() > 1 ) {
// outstanding async_write
return;
}
this->write();
}
void write()
{
const std::string& message = _outbox[0];
boost::asio::async_write(
_socket,
boost::asio::buffer( message.c_str(), message.size() ),
_strand.wrap(
boost::bind(
&Connection::writeHandler,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
)
);
}
void writeHandler(
const boost::system::error_code& error,
const size_t bytesTransferred
)
{
_outbox.pop_front();
if ( error ) {
std::cerr << "could not write: " << boost::system::system_error(error).what() << std::endl;
return;
}
if ( !_outbox.empty() ) {
// more messages to send
this->write();
}
}
private:
typedef std::deque<std::string> Outbox;
private:
boost::asio::io_service& _io_service;
boost::asio::io_service::strand _strand;
boost::asio::ip::tcp::socket _socket;
Outbox _outbox;
};
int
main()
{
boost::asio::io_service io_service;
Connection foo( io_service );
}
some key points
the boost::asio::io_service::strand protects access to Connection::_outbox
a handler is dispatched from Connection::write() since it is public
it wasn't obvious to me if you were using similar practices in the example in your question since all methods are public.
Just trying to improve Sam's great answer. The improvement points are:
async_write tries hard to send every single byte from the buffer(s) before completing, which means you should supply all the input data that you have to the write operation, otherwise the framing overhead may increase due to TCP packets being smaller than they could have been.
asio::streambuf, while being very convenient to use, is not zero-copy. The example below demonstrates a zero-copy approach: keep the input data chunks where they are and use a scatter/gather overload of async_write that takes in a sequence of input buffers (which are just pointers to the actual input data).
Full source code:
#include <boost/asio.hpp>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_set>
#include <vector>
using namespace std::chrono_literals;
using boost::asio::ip::tcp;
class Server
{
class Connection : public std::enable_shared_from_this<Connection>
{
friend class Server;
void ProcessCommand(const std::string& cmd) {
if (cmd == "stop") {
server_.Stop();
return;
}
if (cmd == "") {
Close();
return;
}
std::thread t([this, self = shared_from_this(), cmd] {
for (int i = 0; i < 30; ++i) {
Write("Hello, " + cmd + " " + std::to_string(i) + "\r\n");
}
server_.io_service_.post([this, self] {
DoReadCmd();
});
});
t.detach();
}
void DoReadCmd() {
read_timer_.expires_from_now(server_.read_timeout_);
read_timer_.async_wait([this](boost::system::error_code ec) {
if (!ec) {
std::cout << "Read timeout\n";
Shutdown();
}
});
boost::asio::async_read_until(socket_, buf_in_, '\n', [this, self = shared_from_this()](boost::system::error_code ec, std::size_t bytes_read) {
read_timer_.cancel();
if (!ec) {
const char* p = boost::asio::buffer_cast<const char*>(buf_in_.data());
std::string cmd(p, bytes_read - (bytes_read > 1 && p[bytes_read - 2] == '\r' ? 2 : 1));
buf_in_.consume(bytes_read);
ProcessCommand(cmd);
}
else {
Close();
}
});
}
void DoWrite() {
active_buffer_ ^= 1; // switch buffers
for (const auto& data : buffers_[active_buffer_]) {
buffer_seq_.push_back(boost::asio::buffer(data));
}
write_timer_.expires_from_now(server_.write_timeout_);
write_timer_.async_wait([this](boost::system::error_code ec) {
if (!ec) {
std::cout << "Write timeout\n";
Shutdown();
}
});
boost::asio::async_write(socket_, buffer_seq_, [this, self = shared_from_this()](const boost::system::error_code& ec, size_t bytes_transferred) {
write_timer_.cancel();
std::lock_guard<std::mutex> lock(buffers_mtx_);
buffers_[active_buffer_].clear();
buffer_seq_.clear();
if (!ec) {
std::cout << "Wrote " << bytes_transferred << " bytes\n";
if (!buffers_[active_buffer_ ^ 1].empty()) // have more work
DoWrite();
}
else {
Close();
}
});
}
bool Writing() const { return !buffer_seq_.empty(); }
Server& server_;
boost::asio::streambuf buf_in_;
std::mutex buffers_mtx_;
std::vector<std::string> buffers_[2]; // a double buffer
std::vector<boost::asio::const_buffer> buffer_seq_;
int active_buffer_ = 0;
bool closing_ = false;
bool closed_ = false;
boost::asio::deadline_timer read_timer_, write_timer_;
tcp::socket socket_;
public:
Connection(Server& server) : server_(server), read_timer_(server.io_service_), write_timer_(server.io_service_), socket_(server.io_service_) {
}
void Start() {
socket_.set_option(tcp::no_delay(true));
DoReadCmd();
}
void Close() {
closing_ = true;
if (!Writing())
Shutdown();
}
void Shutdown() {
if (!closed_) {
closing_ = closed_ = true;
boost::system::error_code ec;
socket_.shutdown(tcp::socket::shutdown_both, ec);
socket_.close();
server_.active_connections_.erase(shared_from_this());
}
}
void Write(std::string&& data) {
std::lock_guard<std::mutex> lock(buffers_mtx_);
buffers_[active_buffer_ ^ 1].push_back(std::move(data)); // move input data to the inactive buffer
if (!Writing())
DoWrite();
}
};
void DoAccept() {
if (acceptor_.is_open()) {
auto session = std::make_shared<Connection>(*this);
acceptor_.async_accept(session->socket_, [this, session](boost::system::error_code ec) {
if (!ec) {
active_connections_.insert(session);
session->Start();
}
DoAccept();
});
}
}
boost::asio::io_service io_service_;
tcp::acceptor acceptor_;
std::unordered_set<std::shared_ptr<Connection>> active_connections_;
const boost::posix_time::time_duration read_timeout_ = boost::posix_time::seconds(30);
const boost::posix_time::time_duration write_timeout_ = boost::posix_time::seconds(30);
public:
Server(int port) : acceptor_(io_service_, tcp::endpoint(tcp::v6(), port), false) { }
void Run() {
std::cout << "Listening on " << acceptor_.local_endpoint() << "\n";
DoAccept();
io_service_.run();
}
void Stop() {
acceptor_.close();
{
std::vector<std::shared_ptr<Connection>> sessionsToClose;
copy(active_connections_.begin(), active_connections_.end(), back_inserter(sessionsToClose));
for (auto& s : sessionsToClose)
s->Shutdown();
}
active_connections_.clear();
io_service_.stop();
}
};
int main() {
try {
Server srv(8888);
srv.Run();
}
catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
}
}