Reputation: 885
I have been trying to start from http://think-async.com/asio/boost_asio_1_3_0/doc/html/boost_asio/example/http/server/connection.cpp and modify it to my needs. I want to accept multiple clients one after another and eventually read data over TCP. My server works fine if I run it and then in another shell use
nc localhost 3731
then hit control-C. The first client is properly disconnected. I then enter the same command again. The trouble comes on the next client; it accepts and prints out the start of reading fine but then when I hit control-C on the second client it does not seem to get the disconnect message. I can't figure out why. Any help with boost::asio async_read_some?
Here's the output debug messages showing the first client working then successive clients not being disconnected properly:
./a.out
Made connection at address 80x2240370
Added connection to ConnectionManager at 80x2240370
Starting read at address 80x2240370
Made connection at address 80x22449f0
Did read of 0 with error code asio.misc:2
Accepting bytes.
Removing connection to ConnectionManager at 80x2240370
Destroying connection at address 80x2240370
Added connection to ConnectionManager at 80x22449f0
Starting read at address 80x22449f0
Made connection at address 80x2248af0
Here's the full source code edited for brevity but still showing the problem:
// compile with: g++ asiohelp.cpp -lboost_system -lpthread -std=c++11
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/array.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <set>
#include <vector>
#include <string>
#define CONTROL_PORT "3731"
#define CONTROL_BIND_ADDRESS "127.0.0.1"
namespace hsd {
namespace net {
class HSDConnectionManager;
class HSDConnection: public boost::enable_shared_from_this<HSDConnection> {
boost::asio::ip::tcp::socket socket_;
boost::array<char, 16384> readBuffer_;
HSDConnectionManager& connectionManager_;
public:
explicit HSDConnection(boost::asio::io_service& io_service,
HSDConnectionManager& manager);
~HSDConnection(void);
boost::asio::ip::tcp::socket& socket(void);
/// Start the first asynchronous operation for the connection.
void startRead(void);
void handleRead(const boost::system::error_code& e,
std::size_t bytesTransferred);
void stop(void);
private:
void continueRead(void);
};
typedef boost::shared_ptr<HSDConnection> ConnectionPtr;
}
}
namespace hsd {
namespace net {
class HSDConnectionManager: private boost::noncopyable {
public:
void start(ConnectionPtr c);
void stop(ConnectionPtr c);
void stop_all();
private:
std::set<ConnectionPtr> connections_;
};
}
}
namespace hsd {
namespace net {
class ControlServer {
public:
// Construct the server to listen on the specified TCP address and port
explicit ControlServer(boost::asio::io_service& io_service_,
const std::string& address, const std::string& port);
virtual ~ControlServer(void);
// Run the server's io_service loop.
void run();
// Stop the server.
void stop();
// Handle packet
virtual void packetReceived(std::string result);
private:
/// Handle completion of an asynchronous accept operation.
void handleAccept(const boost::system::error_code& e);
/// Handle a request to stop the server.
void handleStop();
/// The io_service used to perform asynchronous operations.
boost::asio::io_service io_service_;
/// Acceptor used to listen for incoming connections.
boost::asio::ip::tcp::acceptor acceptor_;
/// The connection manager which owns all live connections.
HSDConnectionManager connectionManager_;
/// The next connection to be accepted.
ConnectionPtr nextConnection_;
};
}
}
using namespace std;
using namespace boost::system::errc;
namespace hsd {
namespace net {
ControlServer::ControlServer(boost::asio::io_service& io_service_,
const std::string& address, const std::string& port) :
io_service_(), acceptor_(io_service_), connectionManager_(), nextConnection_() {
nextConnection_ = ConnectionPtr(
new HSDConnection(io_service_, connectionManager_));
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(io_service_);
boost::asio::ip::tcp::resolver::query query(address, port);
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
acceptor_.open(endpoint.protocol());
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor_.bind(endpoint);
acceptor_.listen();
acceptor_.async_accept(nextConnection_->socket(),
boost::bind(&ControlServer::handleAccept, this,
boost::asio::placeholders::error));
}
ControlServer::~ControlServer(void) {
cout << "Destroying ControlServer\n";
}
void ControlServer::handleAccept(const boost::system::error_code& e) {
if (e == success) {
connectionManager_.start(nextConnection_);
nextConnection_.reset(
new HSDConnection(io_service_, connectionManager_));
//nextConnection_ = ConnectionPtr(new HSDConnection(io_service_,
// connectionManager_, *hsPacketReaderListener_s));
acceptor_.async_accept(nextConnection_->socket(),
boost::bind(&ControlServer::handleAccept, this,
boost::asio::placeholders::error));
}
}
void ControlServer::packetReceived(std::string result) {
cout << "Got packet: " << result << "\n";
}
}
}
using namespace std;
namespace hsd {
namespace net {
boost::asio::ip::tcp::socket& HSDConnection::socket(void) {
return socket_;
}
HSDConnection::HSDConnection(boost::asio::io_service& io_service,
HSDConnectionManager& manager) :
socket_(io_service), connectionManager_(manager), readBuffer_()
{
cout << "Made connection at address " << ios::hex << this << "\n";
}
HSDConnection::~HSDConnection(void) {
cout << "Destroying connection at address " << ios::hex << this << "\n";
}
void HSDConnection::handleRead(const boost::system::error_code& e,
std::size_t bytesTransferred) {
cout << "Did read of " << bytesTransferred << " with error code " << e
<< "\n";
std::string byteString(readBuffer_.data(), bytesTransferred);
vector<string> result;
cout << "Accepting bytes.\n";
//hsPacketCore_.acceptBytes(byteString, result);
for (string &packet : result) {
//listener_->packetReceived(packet);
}
if (e == boost::system::errc::success
|| e == boost::asio::error::operation_aborted) {
continueRead();
} else if (bytesTransferred == 0) {
connectionManager_.stop(shared_from_this());
}
}
void HSDConnection::continueRead(void) {
cout << "Continuing read at address " << ios::hex << this << "\n";
socket_.async_read_some(boost::asio::buffer(readBuffer_),
boost::bind(&HSDConnection::handleRead, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void HSDConnection::startRead(void) {
cout << " Starting read at address " << ios::hex << this << "\n";
socket_.async_read_some(boost::asio::buffer(readBuffer_),
boost::bind(&HSDConnection::handleRead, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void HSDConnection::stop(void) {
socket_.close();
}
}
}
namespace hsd {
namespace net {
void HSDConnectionManager::start(ConnectionPtr c) {
connections_.insert(c);
std::cout << "Added connection to ConnectionManager at " << std::ios::hex << c
<< "\n";
c->startRead();
}
void HSDConnectionManager::stop(ConnectionPtr c) {
std::cout << "Removing connection to ConnectionManager at " << std::ios::hex
<< c << "\n";
connections_.erase(c);
c->stop();
}
}
}
using boost::asio::ip::tcp;
using namespace std;
using namespace hsd::net;
int main()
{
try
{
// We need to create a server object to accept incoming client connections.
boost::asio::io_service io_service;
// The io_service object provides I/O services, such as sockets,
// that the server object will use.
ControlServer server(io_service, CONTROL_BIND_ADDRESS, CONTROL_PORT);
// Run the io_service object to perform asynchronous operations.
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
Upvotes: 1
Views: 1429
Reputation: 1993
You have two instances of io_service. The difference in behaviour is from the fact that you call async_accept on sockets initialised from two different io_service instances. The confusion comes from the way that you named the parameter io_service_ the same as the member variable. I managed to fix your code by making the member variable a reference.
/// The io_service used to perform asynchronous operations. boost::asio::io_service& io_service_;
and then disambiguate the variable in the formal parameters of the constructor, and initialise the member variable with this parameter.
ControlServer::ControlServer(boost::asio::io_service& io_service, const std::string& address, const std::string& port) : io_service_(io_service), acceptor_(io_service), connectionManager_(), nextConnection_() {
Another option is to expose the io_service member variable and then run that io_service.
// The io_service object provides I/O services, such as sockets, // that the server object will use. ControlServer server(CONTROL_BIND_ADDRESS, CONTROL_PORT); // Run the io_service object to perform asynchronous operations. server.io_service().run();
I hope that makes sense, I only used boost::asio for the first time last week, doing a writeup of something similar.
Upvotes: 2