Come across the following codes (from user368831) which is what I am looking for. I have modified a little to make it a threaded TCP session that listen and read for connection and data while the main loop can do other tasks.
class CSession
{
public:
CSession(boost::asio::io_service& io_service) : m_Socket(io_service)
{}
tcp::socket& socket() return m_Socket;
void start()
{
boost::asio::async_read_until(m_Socket, m_Buffer, " ",
boost::bind(&CSession::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
ostringstream ss;
ss << &m_Buffer;
m_RecvMsg = ss.str();
std::cout << "handle_read():" << m_RecvMsg << std::endl;
}
else
delete this;
}
private:
boost::asio::streambuf m_Buffer;
tcp::socket m_Socket;
string m_RecvMsg;
};
class CTcpServer
{
public:
CTcpServer(short port)
: m_Acceptor(m_IOService, tcp::endpoint(tcp::v4(), port)),
m_Thread(boost::bind(&boost::asio::io_service::run, &m_IOService))
{
CSession* new_session = new CSession(m_IOService);
m_Acceptor.async_accept(new_session->socket(),
boost::bind(&CTcpServer::handle_accept, this, new_session,
boost::asio::placeholders::error));
};
void handle_accept(CSession* new_session, const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
new_session = new CSession(m_IOService);
m_Acceptor.async_accept(new_session->socket(),
boost::bind(&CTcpServer::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
else
delete new_session;
}
private:
boost::asio::io_service m_IOService;
tcp::acceptor m_Acceptor;
boost::thread m_Thread;
};
void main()
{
:
CTcpServer *server = new CTcpServer(6002); // tcp port 6002
/* How to get the incoming data sent from the client here?? */
// string message;
// while(1)
// {
// if ( server->incomingData(message) )
// {
// std::cout << "Data recv: " << message.data() << std::endl;
// }
// :
// : // other tasks
// :
// }
}
However, how do I code incomingData() in the main loop such that it will monitor the data from the client and return true whenever handle_read() is called?
Can use Boost::signals library in this case?
This code is frankly horrible. There are memory leaks galore because of the way you are using raw pointers. Asio works best with shared_ptrs, it needs guarantees about the lifetimes of objects. I suggest you throw this code away, and start by looking at asio's simple enough to follow examples.
As for the method you want to code - that's not the way it works, you should put that logic in handle_read. handle_read will get called when you have a full message according to your protocol, you should put the logic you want to happen in this method - not in the main while loop. In your main thread, you should simply call io_service::run().
Related
I want my TCP client to connect to multiple servers(each server has a separate IP and port).
I am using async_connect. I can successfully connect to different servers but the read/write fails since the server's corresponding tcp::socket object is not available.
Can you please suggest how I could store each server's socket in some data structure? I tried saving the IP, socket to a std::map, but the first server's socket object is not available in memory and the app crashes. I tried making the socket static, but it does not help either.
Please help me!!
Also, I hope I am logically correct in making a single TCP client connect to 2 different servers.
I am sharing below the simplified header & cpp file.
class TCPClient: public Socket
{
public:
TCPClient(boost::asio::io_service& io_service,
boost::asio::ip::tcp::endpoint ep);
virtual ~TCPClient();
void Connect(boost::asio::ip::tcp::endpoint ep, boost::asio::io_service &ioService, void (Comm::*SaveClientDetails)(std::string,void*),
void *pClassInstance);
void TransmitData(const INT8 *pi8Buffer);
void HandleWrite(const boost::system::error_code& err,
size_t szBytesTransferred);
void HandleConnect(const boost::system::error_code &err,
void (Comm::*SaveClientDetails)(std::string,void*),
void *pClassInstance, std::string sIPAddr);
static tcp::socket* CreateSocket(boost::asio::io_service &ioService)
{ return new tcp::socket(ioService); }
static tcp::socket *mSocket;
private:
std::string sMsgRead;
INT8 i8Data[MAX_BUFFER_LENGTH];
std::string sMsg;
boost::asio::deadline_timer mTimer;
};
tcp::socket* TCPClient::mSocket = NULL;
TCPClient::TCPClient(boost::asio::io_service &ioService,
boost::asio::ip::tcp::endpoint ep) :
mTimer(ioService)
{
}
void TCPClient::Connect(boost::asio::ip::tcp::endpoint ep,
boost::asio::io_service &ioService,
void (Comm::*SaveServerDetails)(std::string,void*),
void *pClassInstance)
{
mSocket = CreateSocket(ioService);
std::string sIPAddr = ep.address().to_string();
/* To send connection request to server*/
mSocket->async_connect(ep,boost::bind(&TCPClient::HandleConnect, this,
boost::asio::placeholders::error, SaveServerDetails,
pClassInstance, sIPAddr));
}
void TCPClient::HandleConnect(const boost::system::error_code &err,
void (Comm::*SaveServerDetails)(std::string,void*),
void *pClassInstance, std::string sIPAddr)
{
if (!err)
{
Comm* pInstance = (Comm*) pClassInstance;
if (NULL == pInstance)
{
break;
}
(pInstance->*SaveServerDetails)(sIPAddr,(void*)(mSocket));
}
else
{
break;
}
}
void TCPClient::TransmitData(const INT8 *pi8Buffer)
{
sMsg = pi8Buffer;
if (sMsg.empty())
{
break;
}
mSocket->async_write_some(boost::asio::buffer(sMsg, MAX_BUFFER_LENGTH),
boost::bind(&TCPClient::HandleWrite, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void TCPClient::HandleWrite(const boost::system::error_code &err,
size_t szBytesTransferred)
{
if (!err)
{
std::cout<< "Data written to TCP Client port! ";
}
else
{
break;
}
}
You seem to know your problem: the socket object is unavailable. That's 100% by choice. You chose to make it static, of course there will be only one instance.
Also, I hope I am logically correct in making a single TCP client connect to 2 different servers.
It sounds wrong to me. You can redefine "client" to mean something having multiple TCP connections. In that case at the very minimum you expect a container of tcp::socket objects to hold those (or, you know, a Connection object that contains the tcp::socket.
BONUS: Demo
For fun and glory, here's what I think you should be looking for.
Notes:
no more new, delete
no more void*, reinterpret casts (!!!)
less manual buffer sizing/handling
no more bind
buffer lifetimes are guaranteed for the corresponding async operations
message queues per connection
connections are on a strand for proper synchronized access to shared state in multi-threading environments
I added in a connection max idle time timeout; it also limits the time taken for any async operation (connect/write). I assumed you wanted something like this because (a) it's common (b) there was an unused deadline_timer in your question code
Note the technique of using shared pointers to have Comm manage its own lifetime. Note also that _socket and _outbox are owned by the individual Comm instance.
Live On Coliru
#include <boost/asio.hpp>
#include <deque>
#include <iostream>
using INT8 = char;
using boost::asio::ip::tcp;
using boost::system::error_code;
//using SaveFunc = std::function<void(std::string, void*)>; // TODO abolish void*
using namespace std::chrono_literals;
using duration = std::chrono::high_resolution_clock::duration;
static inline constexpr size_t MAX_BUFFER_LENGTH = 1024;
using Handle = std::weak_ptr<class Comm>;
class Comm : public std::enable_shared_from_this<Comm> {
public:
template <typename Executor>
explicit Comm(Executor ex, tcp::endpoint ep, // ex assumed to be strand
duration max_idle)
: _ep(ep)
, _max_idle(max_idle)
, _socket{ex}
, _timer{_socket.get_executor()}
{
}
~Comm() { std::cerr << "Comm closed (" << _ep << ")\n"; }
void Start() {
post(_socket.get_executor(), [this, self = shared_from_this()] {
_socket.async_connect(
_ep, [this, self = shared_from_this()](error_code ec) {
std::cerr << "Connect: " << ec.message() << std::endl;
if (!ec)
DoIdle();
else
_timer.cancel();
});
DoIdle();
});
}
void Stop() {
post(_socket.get_executor(), [this, self = shared_from_this()] {
if (not _outbox.empty())
std::cerr << "Warning: some messages may be undelivered ("
<< _ep << ")" << std::endl;
_socket.cancel();
_timer.cancel();
});
}
void TransmitData(std::string_view msg) {
post(_socket.get_executor(),
[this, self = shared_from_this(), msg = std::string(msg.substr(0, MAX_BUFFER_LENGTH))] {
_outbox.emplace_back(std::move(msg));
if (_outbox.size() == 1) { // no send loop already active?
DoSendLoop();
}
});
}
private:
// The DoXXXX functions are assumed to be on the strand
void DoSendLoop() {
DoIdle(); // restart max_idle even after last successful send
if (_outbox.empty())
return;
boost::asio::async_write(
_socket, boost::asio::buffer(_outbox.front()),
[this, self = shared_from_this()](error_code ec, size_t xfr) {
std::cerr << "Write " << xfr << " bytes to " << _ep << " " << ec.message() << std::endl;
if (!ec) {
_outbox.pop_front();
DoSendLoop();
} else
_timer.cancel(); // causes Comm shutdown
});
}
void DoIdle() {
_timer.expires_from_now(_max_idle); // cancels any pending wait
_timer.async_wait([this, self = shared_from_this()](error_code ec) {
if (!ec) {
std::cerr << "Timeout" << std::endl;
_socket.cancel();
}
});
}
tcp::endpoint _ep;
duration _max_idle;
tcp::socket _socket;
boost::asio::high_resolution_timer _timer;
std::deque<std::string> _outbox;
};
class TCPClient {
boost::asio::any_io_executor _ex;
std::deque<Handle> _comms;
public:
TCPClient(boost::asio::any_io_executor ex) : _ex(ex) {}
void Add(tcp::endpoint ep, duration max_idle = 3s)
{
auto pcomm = std::make_shared<Comm>(make_strand(_ex), ep, max_idle);
pcomm->Start();
_comms.push_back(pcomm);
// optionally garbage collect expired handles:
std::erase_if(_comms, std::mem_fn(&Handle::expired));
}
void TransmitData(std::string_view msg) {
for (auto& handle : _comms)
if (auto pcomm = handle.lock())
pcomm->TransmitData(msg);
}
void Stop() {
for (auto& handle : _comms)
if (auto pcomm = handle.lock())
pcomm->Stop();
}
};
int main() {
using std::this_thread::sleep_for;
boost::asio::thread_pool ctx(1);
TCPClient c(ctx.get_executor());
c.Add({{}, 8989});
c.Add({{}, 8990}, 1s); // shorter timeout for demo
c.TransmitData("Hello world\n");
c.Add({{}, 8991});
sleep_for(2s); // times out second connection
c.TransmitData("Three is a crowd\n"); // only delivered to 8989 and 8991
sleep_for(1s); // allow for delivery
c.Stop();
ctx.join();
}
Prints (on Coliru):
for p in {8989..8991}; do netcat -t -l -p $p& done
sleep .5; ./a.out
Hello world
Connect: Success
Connect: Success
Hello world
Connect: Success
Write 12 bytes to 0.0.0.0:8989 Success
Write 12 bytes to 0.0.0.0:8990 Success
Timeout
Comm closed (0.0.0.0:8990)
Write Three is a crowd
17Three is a crowd
bytes to 0.0.0.0:8989 Success
Write 17 bytes to 0.0.0.0:8991 Success
Comm closed (0.0.0.0:8989)
Comm closed (0.0.0.0:8991)
The output is a little out of sequence there. Live local demo:
I want to make this simple server that listens to incoming connection requests, makes connections and sends some data. When I start this acceptor looks like it's working fine, it waits for those incoming connection requests, but when my client tries to connect to this acceptor it automatically crushes. I cant even catch any exceptions with catch(...)
When I start this program it looks like this in a terminal
But when I try to connect
Client application received this kind of error code
Is there something fundamentally wrong with my my_acceptor class?
class my_acceptor{
public:
my_acceptor(asio::io_context& ios, unsigned short port_num) :
m_ios(ios),
port{port_num},
m_acceptor{ios}{}
//start accepting incoming connection requests
void Start()
{
std::cout << "Acceptor Start" << std::endl;
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), port);
m_acceptor.open(endpoint.protocol());
m_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
m_acceptor.bind(endpoint);
m_acceptor.listen();
InitAccept();
}
void Stop(){}
private:
void InitAccept()
{
std::cout << "Acceptor InitAccept" << std::endl;
std::shared_ptr<asio::ip::tcp::socket> sock{new asio::ip::tcp::socket(m_ios)};
m_acceptor.async_accept(*sock.get(),
[this, sock](const boost::system::error_code& error)
{
onAccept(error, sock);
});
}
void onAccept(const boost::system::error_code& ec, std::shared_ptr<asio::ip::tcp::socket> sock)
{
std::cout << "Acceptor onAccept" << std::endl;
}
private:
unsigned short port;
asio::io_context& m_ios;
asio::ip::tcp::acceptor m_acceptor;
};
Just in case this is the Server code that wraps my_acceptor
class Server{
public:
Server(){}
//start the server
void Start(unsigned short port_num, unsigned int thread_pool_size)
{
assert(thread_pool_size > 0);
//create specified number of threads and add them to the pool
for(unsigned int i = 0; i < thread_pool_size; ++i)
{
std::unique_ptr<std::thread> th(
new std::thread([this]()
{
m_ios.run();
}));
m_thread_pool.push_back(std::move(th));
}
//create and start acceptor
acc.reset(new my_acceptor(m_ios, port_num));
acc->Start();
}
//stop the server
void Stop()
{
work_guard.reset();
acc->Stop();
m_ios.stop();
for(auto& th : m_thread_pool)
{
th->join();
}
}
private:
asio::io_context m_ios;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work_guard = boost::asio::make_work_guard(m_ios);
std::unique_ptr<my_acceptor> acc;
std::vector<std::unique_ptr<std::thread>> m_thread_pool;
};
There's a threading bug, at least. tcp::acceptor is not thread-safe and you (potentially) run multiple threads. So you will need to make the acceptor access be done from a strand.
my_acceptor(asio::io_context& ios, unsigned short port_num) :
m_ios(ios),
port{port_num},
m_acceptor{make_strand(ios)}{}
And then any operation involving it must be on that strand. E.g., the missing Stop() code should look like:
void Stop(){
post(m_acceptor.get_executor(), [this] { m_acceptor.cancel(); });
}
I leave the initial accept as-is because at that point there aren't multiple threads involved.
Likewise in Start() and Stop() you should check whether acc is null, because acc->Stop() would throw and just replacing a running acc would cause Undefined Behaviour due to deleting the instance that is still having async operations in flight.
In a sidenote, m_ios.stop() should not be necessary if you stop the running acceptor. In the future you might have to signal any client connections to stop, in order for the threads to naturally join.
Here's how I'd complete the accept loop:
void onAccept(error_code ec, std::shared_ptr<tcp::socket> sock)
{
std::cout << "Acceptor onAccept " << ec.message() << " " << sock.get() << std::endl;
if (!ec) {
InitAccept();
}
}
Note how unless the socket is canceled (or otherwise in error), we keep accepting.
I think the threading issue was likely your big problem. The result after my suggestions works:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
#include <memory>
#include <thread>
using namespace std::chrono_literals;
namespace asio = boost::asio;
using boost::system::error_code;
using asio::ip::tcp;
class my_acceptor {
public:
my_acceptor(asio::io_context& ios, unsigned short port_num) :
m_ios(ios),
port{port_num},
m_acceptor{make_strand(ios)}{}
//start accepting incoming connection requests
void Start()
{
std::cout << "Acceptor Start" << std::endl;
tcp::endpoint endpoint(tcp::v4(), port);
m_acceptor.open(endpoint.protocol());
m_acceptor.set_option(tcp::acceptor::reuse_address(true));
m_acceptor.bind(endpoint);
m_acceptor.listen();
InitAccept();
}
void Stop(){
post(m_acceptor.get_executor(), [this] { m_acceptor.cancel(); });
}
private:
void InitAccept()
{
std::cout << "Acceptor InitAccept" << std::endl;
auto sock = std::make_shared<tcp::socket>(m_ios);
m_acceptor.async_accept(*sock,
[this, sock](error_code error) { onAccept(error, sock); });
}
void onAccept(error_code ec, const std::shared_ptr<tcp::socket>& sock)
{
std::cout << "Acceptor onAccept " << ec.message() << " " << sock.get() << std::endl;
if (!ec) {
InitAccept();
}
}
private:
asio::io_context& m_ios;
unsigned short port;
tcp::acceptor m_acceptor;
};
class Server{
public:
Server() = default;
//start the server
void Start(unsigned short port_num, unsigned int thread_pool_size)
{
assert(!acc); // otherwise UB results
assert(thread_pool_size > 0);
//create specified number of threads and add them to the pool
for(unsigned int i = 0; i < thread_pool_size; ++i)
{
std::unique_ptr<std::thread> th(
new std::thread([this]() { m_ios.run(); }));
m_thread_pool.push_back(std::move(th));
}
//create and start acceptor
acc = std::make_unique<my_acceptor>(m_ios, port_num);
acc->Start();
}
//stop the server
void Stop()
{
work_guard.reset();
if (acc) {
acc->Stop();
}
//m_ios.stop();
for(auto& th : m_thread_pool) {
th->join();
}
acc.reset();
}
private:
asio::io_context m_ios;
asio::executor_work_guard<asio::io_context::executor_type>
work_guard = make_work_guard(m_ios);
std::unique_ptr<my_acceptor> acc;
std::vector<std::unique_ptr<std::thread>> m_thread_pool;
};
int main() {
Server s;
s.Start(6868, 1);
std::this_thread::sleep_for(10s);
s.Stop();
}
Testing with netcat as client:
for msg in one two three; do
sleep 1
nc 127.0.0.1 6868 <<< "$msg"
done
Prints
Acceptor Start
Acceptor InitAccept
Acceptor onAccept Success 0x1f26960
Acceptor InitAccept
Acceptor onAccept Success 0x7f59f80009d0
Acceptor InitAccept
Acceptor onAccept Success 0x7f59f8000a50
Acceptor InitAccept
Acceptor onAccept Operation canceled 0x7f59f80009d0
I have next snippet:
void TcpConnection::Send(const std::vector<uint8_t>& buffer) {
std::shared_ptr<std::vector<uint8_t>> bufferCopy = std::make_shared<std::vector<uint8_t>>(buffer);
auto socket = m_socket;
m_socket->async_send(asio::buffer(bufferCopy->data(), bufferCopy->size()), [socket, bufferCopy](const boost::system::error_code& err, size_t bytesSent)
{
if (err)
{
logwarning << "clientcomms_t::sendNext encountered error: " << err.message();
// Assume that the communications path is no longer
// valid.
socket->close();
}
});
}
This code leads to memory leak. if m_socket->async_send call is commented then there is not memeory leak. I can not understand why bufferCopy is not freed after callback is dispatched. What I am doing wrong?
Windows is used.
Since you don't show any relevant code, and the code shown does not contain a strict problem, I'm going to assume from the code smells.
The smell is that you have a TcpConnection class that is not enable_shared_from_this<TcpConnection> derived. This leads me to suspect you didn't plan ahead, because there's no possible reasonable way to continue using the instance after the completion of any asynchronous operation (like the async_send).
This leads me to suspect you have a crucially simple problem, which is that your completion handler never runs. There's only one situation that could explain this, and that leads me to assume you never run() the ios_service instance
Here's the situation live:
Live On Coliru
#include <boost/asio.hpp>
namespace asio = boost::asio;
using asio::ip::tcp;
#include <iostream>
auto& logwarning = std::clog;
struct TcpConnection {
using Buffer = std::vector<uint8_t>;
void Send(Buffer const &);
TcpConnection(asio::io_service& svc) : m_socket(std::make_shared<tcp::socket>(svc)) {}
tcp::socket& socket() const { return *m_socket; }
private:
std::shared_ptr<tcp::socket> m_socket;
};
void TcpConnection::Send(Buffer const &buffer) {
auto bufferCopy = std::make_shared<Buffer>(buffer);
auto socket = m_socket;
m_socket->async_send(asio::buffer(bufferCopy->data(), bufferCopy->size()),
[socket, bufferCopy](const boost::system::error_code &err, size_t /*bytesSent*/) {
if (err) {
logwarning << "clientcomms_t::sendNext encountered error: " << err.message();
// Assume that the communications path is no longer
// valid.
socket->close();
}
});
}
int main() {
asio::io_service svc;
tcp::acceptor a(svc, tcp::v4());
a.bind({{}, 6767});
a.listen();
boost::system::error_code ec;
do {
TcpConnection conn(svc);
a.accept(conn.socket(), ec);
char const* greeting = "whale hello there!\n";
conn.Send({greeting, greeting+strlen(greeting)});
} while (!ec);
}
You'll see that any client, connection e.g. with netcat localhost 6767 will receive the greeting, after which, surprisingly the connection will stay open, instead of being closed.
You'd expect the connection to be closed by the server side either way, either because
a transmission error occurred in async_send
or because after the completion handler is run, it is destroyed and hence the captured shared-pointers are destructed. Not only would that free the copied buffer, but also would it run the destructor of socket which would close the connection.
This clearly confirms that the completion handler never runs. The fix is "easy", find a place to run the service:
int main() {
asio::io_service svc;
tcp::acceptor a(svc, tcp::v4());
a.set_option(tcp::acceptor::reuse_address());
a.bind({{}, 6767});
a.listen();
std::thread th;
{
asio::io_service::work keep(svc); // prevent service running out of work early
th = std::thread([&svc] { svc.run(); });
boost::system::error_code ec;
for (int i = 0; i < 11 && !ec; ++i) {
TcpConnection conn(svc);
a.accept(conn.socket(), ec);
char const* greeting = "whale hello there!\n";
conn.Send({greeting, greeting+strlen(greeting)});
}
}
th.join();
}
This runs 11 connections and exits leak-free.
Better:
It becomes a lot cleaner when the accept loop is also async, and the TcpConnection is properly shared as hinted above:
Live On Coliru
#include <boost/asio.hpp>
namespace asio = boost::asio;
using asio::ip::tcp;
#include <memory>
#include <thread>
#include <iostream>
auto& logwarning = std::clog;
struct TcpConnection : std::enable_shared_from_this<TcpConnection> {
using Buffer = std::vector<uint8_t>;
TcpConnection(asio::io_service& svc) : m_socket(svc) {}
void start() {
char const* greeting = "whale hello there!\n";
Send({greeting, greeting+strlen(greeting)});
}
void Send(Buffer);
private:
friend struct Server;
Buffer m_output;
tcp::socket m_socket;
};
struct Server {
Server(unsigned short port) {
_acceptor.set_option(tcp::acceptor::reuse_address());
_acceptor.bind({{}, port});
_acceptor.listen();
do_accept();
}
~Server() {
keep.reset();
_svc.post([this] { _acceptor.cancel(); });
if (th.joinable())
th.join();
}
private:
void do_accept() {
auto conn = std::make_shared<TcpConnection>(_svc);
_acceptor.async_accept(conn->m_socket, [this,conn](boost::system::error_code ec) {
if (ec)
logwarning << "accept failed: " << ec.message() << "\n";
else {
conn->start();
do_accept();
}
});
}
asio::io_service _svc;
// prevent service running out of work early:
std::unique_ptr<asio::io_service::work> keep{std::make_unique<asio::io_service::work>(_svc)};
std::thread th{[this]{_svc.run();}}; // TODO handle handler exceptions
tcp::acceptor _acceptor{_svc, tcp::v4()};
};
void TcpConnection::Send(Buffer buffer) {
m_output = std::move(buffer);
auto self = shared_from_this();
m_socket.async_send(asio::buffer(m_output),
[self](const boost::system::error_code &err, size_t /*bytesSent*/) {
if (err) {
logwarning << "clientcomms_t::sendNext encountered error: " << err.message() << "\n";
// not holding on to `self` means the socket gets closed
}
// do more with `self` which points to the TcpConnection instance...
});
}
int main() {
Server server(6868);
std::this_thread::sleep_for(std::chrono::seconds(3));
}
I'm developing a 3d (first/third person) game and I'm trying to make it multiplayer using TCP sockets. I'm using the boost asio library for this, and I'm in a little over my head. I've played with the tutorials and examples a bit on the boost asio doc page and they compiled/ran/worked just fine, I'm just a little confused as to how everything works.
Right now I'm just trying to make the server accept messages from the client, and print the message after receiving it. When I execute the code below (it compiles/links/runs fine), nothing happens. More specifically, the client appears to successfully send the message, and the server never seems to receive the message.
Client code:
ClientFramework::ClientFramework() :
mResolver(mIOService)
{
}
bool ClientFramework::Initialize()
{
try
{
tcp::resolver::query query("localhost", "daytime");
tcp::resolver::iterator it = mResolver.resolve(query);
tcp::socket socket(mIOService);
boost::asio::connect(socket, it);
std::string s = "hello world";
boost::system::error_code e;
socket.write_some(boost::asio::buffer(s.c_str(), s.size()), e);
if (e)
{
throw boost::system::system_error(e);
}
} catch (std::exception& e)
{
gLog << LOG_ERROR << e.what() << "\n";
}
return true;
}
Server code:
ServerFramework::ServerFramework() :
mAcceptor(mIOService, tcp::endpoint(tcp::v4(), 13))
{
}
bool ServerFramework::Initialize()
{
mIOService.run();
StartAccept();
return true;
}
void ServerFramework::StartAccept()
{
Connection::ptr conn =
Connection::create(mAcceptor.get_io_service());
mAcceptor.async_accept(conn->Socket(),
boost::bind(&ServerFramework::HandleAccept, this, conn,
boost::asio::placeholders::error));
}
void ServerFramework::HandleAccept(Connection::ptr conn,
const boost::system::error_code& error)
{
if (!error)
{
conn->Initialize();
}
StartAccept();
}
Connection::ptr Connection::create(boost::asio::io_service& io_service)
{
return ptr(new Connection(io_service));
}
tcp::socket& Connection::Socket()
{
return mSocket;
}
void Connection::Initialize()
{
boost::asio::async_read(mSocket, boost::asio::buffer(buf, BUFFER_SIZE),
boost::bind(&Connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
Connection::Connection(boost::asio::io_service& io_service) :
mSocket(io_service)
{
}
void Connection::handle_read(const boost::system::error_code& e, size_t size)
{
std::string s(buf, size);
gLog << LOG_INFO << s << "\n";
boost::asio::async_read(mSocket, boost::asio::buffer(buf, BUFFER_SIZE),
boost::bind(&Connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
It does not look like your io_service has any work to do when you invoke run().
bool ServerFramework::Initialize()
{
mIOService.run(); // <-- you don't check the return value here
StartAccept();
return true;
}
it will return the number of handlers executed, I suspect it is zero. Try invoking it after async_accept()
bool ServerFramework::Initialize()
{
StartAccept();
mIOService.run();
return true;
}
Though, it isn't entirely obvious by your limited code snippets where you invoke ServerFramework::Initialize(). I suggest editing your question with a short, self contained, correct example that we can compile with little to no effort. Your current code will not compile without additional boilerplate stuff, like main().
Hey all, I'm new to asio and boost, I've been trying to implement a TCP Server & Client so that I could transmit an std::vector - but I've failed so far. I'm finding the boost documentation of Asio lacking (to say the least) and hard to understand (english is not my primary language).
In any case, I've been looking at the iostreams examples and I've been trying to implement an object oriented solution - but I've failed.
The server that I'm trying to implement should be able to accept connections from multiple clients (How do I do that ?)
The server should receive the std::vector, /* Do something */ and then return it to the client so that the client can tell that the server received the data intact.
*.h file
class TCP_Server : private boost::noncopyable
{
typedef boost::shared_ptr<TCP_Connection> tcp_conn_pointer;
public :
TCP_Server(ba::io_service &io_service, int port);
virtual ~TCP_Server() {}
virtual void Start_Accept();
private:
virtual void Handle_Accept(const boost::system::error_code& e);
private :
int m_port;
ba::io_service& m_io_service; // IO Service
bi::tcp::acceptor m_acceptor; // TCP Connections acceptor
tcp_conn_pointer m_new_tcp_connection; // New connection pointer
};
*.cpp file
TCP_Server::TCP_Server(boost::asio::io_service &io_service, int port) :
m_io_service(io_service),
m_acceptor(io_service, bi::tcp::endpoint(bi::tcp::v4(), port)),
m_new_tcp_connection(TCP_Connection::Create(io_service))
{
m_port = port;
Start_Accept();
}
void TCP_Server::Start_Accept()
{
std::cout << "[TCP_Server][Start_Accept] => Listening on port : " << m_port << std::endl;
//m_acceptor.async_accept(m_new_tcp_connection->Socket(),
// boost::bind(&TCP_Server::Handle_Accept, this,
// ba::placeholders::error));
m_acceptor.async_accept(*m_stream.rdbuf(),
boost::bind(&TCP_Server::Handle_Accept,
this,
ba::placeholders::error));
}
void TCP_Server::Handle_Accept(const boost::system::error_code &e)
{
if(!e)
{
/*boost::thread T(boost::bind(&TCP_Connection::Run, m_new_tcp_connection));
std::cout << "[TCP_Server][Handle_Accept] => Accepting incoming connection. Launching Thread " << std::endl;
m_new_tcp_connection = TCP_Connection::Create(m_io_service);
m_acceptor.async_accept(m_new_tcp_connection->Socket(),
boost::bind(&TCP_Server::Handle_Accept,
this,
ba::placeholders::error));*/
m_stream << "Server Response..." << std::endl;
}
}
How should the client look ?
How do I keep the connection alive while both apps "talk" ?
AFAIK ASIO iostreams are only for synchronous I/O. But your example gives me a hint that you want to use asynchronous I/O.
Here is a small example of a server which uses async I/O to read a request comprising of an array of integers preceded by 4 byte count of the integers in the request.
So in effect I am serializing a vector of integerss as
count(4 bytes)
int
int
...
etc
if reading the vector of ints is successful, the server will write a 4 byte response code(=1) and then issue a read for a new request from the client. Enough said, Code follows.
#include <iostream>
#include <vector>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/asio.hpp>
using namespace boost::asio;
using boost::asio::ip::tcp;
class Connection
{
public:
Connection(tcp::acceptor& acceptor)
: acceptor_(acceptor), socket_(acceptor.get_io_service(), tcp::v4())
{
}
void start()
{
acceptor_.get_io_service().post(boost::bind(&Connection::start_accept, this));
}
private:
void start_accept()
{
acceptor_.async_accept(socket_,boost::bind(&Connection::handle_accept, this,
placeholders::error));
}
void handle_accept(const boost::system::error_code& err)
{
if (err)
{
//Failed to accept the incoming connection.
disconnect();
}
else
{
count_ = 0;
async_read(socket_, buffer(&count_, sizeof(count_)),
boost::bind(&Connection::handle_read_count,
this, placeholders::error, placeholders::bytes_transferred));
}
}
void handle_read_count(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if (err || (bytes_transferred != sizeof(count_))
{
//Failed to read the element count.
disconnect();
}
else
{
elements_.assign(count_, 0);
async_read(socket_, buffer(elements_),
boost::bind(&Connection::handle_read_elements, this,
placeholders::error, placeholders::bytes_transferred));
}
}
void handle_read_elements(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if (err || (bytes_transferred != count_ * sizeof(int)))
{
//Failed to read the request elements.
disconnect();
}
else
{
response_ = 1;
async_write(socket_, buffer(&response_, sizeof(response_)),
boost::bind(&Connection::handle_write_response, this,
placeholders::error, placeholders::bytes_transferred));
}
}
void handle_write_response(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if (err)
disconnect();
else
{
//Start a fresh read
count_ = 0;
async_read(socket_, buffer(&count_, sizeof(count_)),
boost::bind(&Connection::handle_read_count,
this, placeholders::error, placeholders::bytes_transferred));
}
}
void disconnect()
{
socket_.shutdown(tcp::socket::shutdown_both);
socket_.close();
socket_.open(tcp::v4());
start_accept();
}
tcp::acceptor& acceptor_;
tcp::socket socket_;
std::vector<int> elements_;
long count_;
long response_;
};
class Server : private boost::noncopyable
{
public:
Server(unsigned short port, unsigned short thread_pool_size, unsigned short conn_pool_size)
: acceptor_(io_service_, tcp::endpoint(tcp::v4(), port), true)
{
unsigned short i = 0;
for (i = 0; i < conn_pool_size; ++i)
{
ConnectionPtr conn(new Connection(acceptor_));
conn->start();
conn_pool_.push_back(conn);
}
// Start the pool of threads to run all of the io_services.
for (i = 0; i < thread_pool_size; ++i)
{
thread_pool_.create_thread(boost::bind(&io_service::run, &io_service_));
}
}
~Server()
{
io_service_.stop();
thread_pool_.join_all();
}
private:
io_service io_service_;
tcp::acceptor acceptor_;
typedef boost::shared_ptr<Connection> ConnectionPtr;
std::vector<ConnectionPtr> conn_pool_;
boost::thread_group thread_pool_;
};
boost::function0<void> console_ctrl_function;
BOOL WINAPI console_ctrl_handler(DWORD ctrl_type)
{
switch (ctrl_type)
{
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_SHUTDOWN_EVENT:
console_ctrl_function();
return TRUE;
default:
return FALSE;
}
}
void stop_server(Server* pServer)
{
delete pServer;
pServer = NULL;
}
int main()
{
Server *pServer = new Server(10255, 4, 20);
console_ctrl_function = boost::bind(stop_server, pServer);
SetConsoleCtrlHandler(console_ctrl_handler, TRUE);
while(true)
{
Sleep(10000);
}
}
I believe the code you have posted is a little incomplete/incorrect. Nonetheless, here is some guidance..
1)
Your async_accept() call seems wrong. It should be something like,
m_acceptor.async_accept(m_new_tcp_connection->socket(),...)
2)
Take note that the Handle_Accept() function will be called after the socket is accepted. In other words, when control reaches Handle_Accept(), you simply have to write to the socket. Something like
void TCP_Server::Handle_Accept(const system::error_code& error)
{
if(!error)
{
//send data to the client
string message = "hello there!\n";
//Write data to the socket and then call the handler AFTER that
//Note, you will need to define a Handle_Write() function in your TCP_Connection class.
async_write(m_new_tcp_connection->socket(),buffer(message),bind(&TCP_Connection::Handle_Write, this,placeholders::error,placeholders::bytes_transferred));
//accept the next connection
Start_Accept();
}
}
3)
As for the client, you should take a look here:
http://www.boost.org/doc/libs/1_39_0/doc/html/boost_asio/tutorial/tutdaytime1.html
If your communication on both ends is realized in C++ you can use Boost Serialization library to sezilize the vector into bytes and transfer these to the other machine. On the opposite end you will use boost serialization lib to desirialize the object. I saw at least two approaches doing so.
Advantage of Boost Serialization: this approach works when transferring objects between 32bit and 64bit systems as well.
Below are the links:
code project article
boost mailing list ideas
Regards,
Ovanes