I am working on a chat. For some reason, at the point of distributing user's message between other clients, string put together by Server::msgHandler is butchered by async_write in Connection::write, making it appear as only the part of that string has actually been read. Example:
Constructed message: "Hello people by Jack"
Appears as: "by Jack"
that is the string str=Hello people is not printed out. At first I thought it was to do with the implicit \0 at its end, but that wouldn't make any sense, moreover, as I tried various positions of string in message I noticed that if str is preceded with other text, the text will be shown either emitting str entirely, or placing it in unexpected places. E.g.
writeMsg("It was said: \n"+str+" by \"name\"\n");
will appear as:
It was said
by "name"Hello People
Full, minimal, compilable example:
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <iostream>
#include <vector>
#include <deque>
typedef boost::asio::io_service io_service;
typedef boost::asio::ip::tcp tcp;
class Server;
class Connection : public boost::enable_shared_from_this<Connection> {
io_service::strand strand;
tcp::socket soc;
std::deque<std::string> msgBuff;
boost::asio::streambuf buf;
Server* server;
void(Server::*serverHandler)(std::string);
private:
Connection(io_service& service) :soc(service), strand(service){
}
void writeStranded(std::string msg){
msgBuff.push_back(msg);
if (msgBuff.size() > 1)return;
write();
}
void write(){
std::string& tmpMsg = msgBuff[0];
boost::asio::async_write(
soc,
boost::asio::buffer(tmpMsg.c_str(), tmpMsg.size()),
strand.wrap(
boost::bind(&Connection::handleWrite,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)
)
);
}
void handleWrite(const boost::system::error_code&, size_t s){
msgBuff.pop_front();
if (!msgBuff.empty())write();
}
void handleRead(const boost::system::error_code&, size_t s){
std::istream is(&buf);
std::string tmpMsg;
std::getline(is, tmpMsg);
(server->*serverHandler)(tmpMsg);
readMsg();
}
public:
typedef boost::shared_ptr<Connection> pointer;
static pointer createInstance(io_service& service){
return pointer(new Connection(service));
}
void init(Server* server, void(Server::*serverHandler)(std::string)){
this->server = server;
this->serverHandler = serverHandler;
writeMsg("hello\n");
readMsg();
}
void writeMsg(std::string msg){
strand.dispatch(boost::bind(&Connection::writeStranded, this, msg));
}
void readMsg(){
const char delim = '\n';
boost::asio::async_read_until(soc, buf, delim,
boost::bind(&Connection::handleRead, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
tcp::socket& getSocket(){
return soc;
}
};
class Server{
tcp::acceptor accept;
std::vector<Connection::pointer> connections;
public:
Server(io_service& io_service, int port = 23) :accept(io_service, tcp::endpoint(tcp::v4(), port)){
awaitConnection();
};
private:
void awaitConnection(){
Connection::pointer con = Connection::createInstance(accept .get_io_service());
accept.async_accept(con->getSocket(), boost::bind(&Server::conAcceptor, this, con, boost::asio::placeholders::error));
}
void conAcceptor(Connection::pointer con, const boost::system::error_code& err){
if (err)return;
con->init(this, &Server::msgHandler);
awaitConnection();
connections.push_back(con);
}
void msgHandler(std::string str){
for (Connection::pointer ptr : connections){
ptr->writeMsg(str+" by \"name\"\n");
}
}
};
int main(){
io_service io_service;
Server s(io_service);
io_service.run();
system("pause");
}
Upd
Turns out the async_read was appending the string with carriage return, which was stored as it was added before the delimeter in the name string, and each time I tried make the name appear, everything preceding it would get overwritten by all that followed. Sometimes that carriage return would get wild and skip some characters preceding the name, which further complicated the search of this bug.
I got it running. I had to write a client for it...
Before this goes into production you'll want to look at the lifetime handling. The normal way is that the connection object holds a shared_ptr to itself in its bound handlers.
I have use c++14 lambdas as I find them less onerous that boost::bind.
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <iostream>
#include <vector>
#include <deque>
#include <iterator>
typedef boost::asio::io_service io_service;
typedef boost::asio::ip::tcp tcp;
class Server;
class Connection
: public boost::enable_shared_from_this<Connection>
{
io_service::strand strand;
tcp::socket soc;
// use double-buffering for the message sending
std::deque<std::string> sending, to_send;
boost::asio::streambuf buf;
Server *server;
void (Server::*serverHandler)(std::string);
private:
Connection(io_service& service)
: strand(service)
, soc(service)
{
}
void writeStranded(std::string msg)
{
assert(strand.running_in_this_thread()); // sanity check
to_send.push_back(std::move(msg));
maybe_write();
}
void maybe_write()
{
assert(strand.running_in_this_thread()); // sanity check
if (sending.empty() and not to_send.empty()) {
sending.swap(to_send);
// make a buffer sequence
auto buffers = std::vector<boost::asio::const_buffers_1>();
buffers.reserve(sending.size());
for (auto& data : sending) {
buffers.push_back(boost::asio::buffer(data));
}
boost::asio::async_write(soc, buffers,
strand.wrap([this](auto&& ec, size_t size)
{
this->sending.clear();
if (not ec) maybe_write();
}));
}
}
void handleRead(const boost::system::error_code&, size_t s)
{
std::istream is(&buf);
std::string tmpMsg;
std::getline(is, tmpMsg);
(server->*serverHandler)(tmpMsg);
readMsg();
}
public:
typedef boost::shared_ptr<Connection> pointer;
static pointer createInstance(io_service& service)
{
return pointer(new Connection(service));
}
void init(Server *server, void(Server::*serverHandler)(std::string))
{
this->server = server;
this->serverHandler = serverHandler;
writeMsg("hello\n");
readMsg();
}
void writeMsg(std::string msg)
{
strand.dispatch(boost::bind(&Connection::writeStranded, this, msg));
}
void readMsg()
{
const char delim = '\n';
boost::asio::async_read_until(soc, buf, delim,
boost::bind(&Connection::handleRead, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
tcp::socket& getSocket()
{
return soc;
}
};
class Server
{
tcp::acceptor accept;
std::vector<Connection::pointer> connections;
public:
Server(io_service& io_service, int port = 2333)
: accept(io_service, tcp::endpoint(tcp::v4(), port))
{
awaitConnection();
};
private:
void awaitConnection()
{
Connection::pointer con = Connection::createInstance(accept.get_io_service());
accept.async_accept(con->getSocket(),
boost::bind(&Server::conAcceptor, this, con, boost::asio::placeholders::error));
}
void conAcceptor(Connection::pointer con, const boost::system::error_code& err)
{
if (err)return;
con->init(this, &Server::msgHandler);
awaitConnection();
connections.push_back(con);
}
void msgHandler(std::string str)
{
for (Connection::pointer ptr : connections) {
ptr->writeMsg(str + " by \"name\"\n");
}
}
};
struct Client
{
using protocol = boost::asio::ip::tcp;
Client(boost::asio::io_service& exec)
: executor_(exec) {}
void run(int port)
{
resolver_.async_resolve(protocol::resolver::query("localhost", std::to_string(port)),
strand_.wrap([this](auto&& ec, auto iter)
{
std::cout << "resolve: " << ec.message() << std::endl;
if (not ec) start_connect(iter);
}));
}
void start_connect(protocol::resolver::iterator iter)
{
boost::asio::async_connect(socket_, iter,
strand_.wrap([this](auto&& ec, auto iter)
{
std::cout << "connect: " << ec.message() << std::endl;
if (not ec) {
this->start_reading();
auto data = std::make_shared<std::string>(
"The quick brown fox jumps over the lazy dog\n"
"Farmer bob has a cool tractor\n");
boost::asio::async_write(socket_, boost::asio::buffer(*data),
strand_
.wrap([data](auto&& ec, auto size)
{
std::cout << "written: "
<< size
<< std::endl;
}));
}
}));
}
void start_reading()
{
auto buffer = read_buffer_.prepare(1024);
socket_.async_read_some(read_buffer_.prepare(1024), [this](auto&& ec, auto size)
{
read_buffer_.commit(size);
std::istream is(std::addressof(read_buffer_));
std::string s;
while(std::getline(is, s)) {
std::cout << s << std::endl;
}
start_reading();
});
}
boost::asio::io_service& executor_;
boost::asio::io_service::strand strand_{executor_};
protocol::resolver resolver_{executor_};
protocol::socket socket_{executor_};
boost::asio::streambuf read_buffer_;
};
int main()
{
io_service io_service;
Server s(io_service);
Client c(io_service);
c.run(2333);
io_service.run();
system("pause");
}
output (program does not terminate):
resolve: Undefined error: 0
connect: Undefined error: 0
written: 74
hello
The quick brown fox jumps over the lazy dog by "name"
Farmer bob has a cool tractor by "name"
Notice that in void readMsg() delim is set to be '\n'. As innocuous as it might look it doesn't take into account the fact that in Windows a newline is represented as CR + LF which is: "\r\n". So each time you read something from socket using read_until(delim) everything besides delimeter will stay in the buffer, including \r (carriage return). If string like that is later appended it is expected that everything after \r will overlay the original text.
Related
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I was just trying to accept incoming request with acceptor socket and when it enters the async_accept it throws out an error, I'm not sure what is causing the error. The thing is I'm not even sending the request from the client but still it enters the async_accept handler for some reason, here is the part of the code which is causing the error
main.cpp
#include <iostream>
#include "server.hpp"
#include "connection.hpp"
class Connection;
int main(){
using Server_ = Server<Connection>;
auto server = std::make_unique<Server_>(8989);
server->start();
}
server.hpp
#pragma once
#include <thread>
#include <boost/asio.hpp>
template <typename Connection>
class Server{
using shared_connection = std::shared_ptr<Connection>;
private:
unsigned short port_;
std::thread io_thread_;
boost::asio::io_context ioc_;
boost::asio::io_context::work work_;
boost::asio::ip::tcp::endpoint endpoint_;
boost::asio::ip::tcp::acceptor acceptor_;
void handle_new_request(shared_connection connection, const system::error_code &ec){
if(!ec){
connection->start_operation();
}else{
error::print(ec);
return;
}
}
public:
explicit Server(unsigned short port)
: acceptor_(ioc_)
, work_(ioc_)
, port_(port)
, endpoint_(asio::ip::tcp::v4(), port) {
io_thread_ = std::move(std::thread([&]{ ioc_.run(); }));
io_thread_.join();
}
~Server() {
if(acceptor_.is_open())
acceptor_.close();
io_thread_.join();
}
void start(){
using namespace asio::ip;
system::error_code ec;
// creates an actual operating system socket
acceptor_.open(endpoint_.protocol(),ec);
acceptor_.set_option(tcp::acceptor::reuse_address(true),ec);
// binds to the endpoint
acceptor_.bind(endpoint_,ec);
if(!ec){
std::cout << "Listening for requests from port " << port_ << std::endl;
acceptor_.listen();
}else{
error::print(ec);
return;
}
shared_connection connection = std::make_shared<Connection>(ioc_);
acceptor_.async_accept(connection->sock_,[=](system::error_code ec){
handle_new_request(connection,ec);
});
}
};
connection.hpp
#pragma once
#include <memory>
#include <boost/asio.hpp>
class Connection : public std::enable_shared_from_this<Connection> {
using shared_connection = std::shared_ptr<Connection>;
std::vector<char> buffer_space_;
private:
boost::asio::mutable_buffers_1 buffer_;
boost::asio::io_context& ioc_;
public:
boost::asio::ip::tcp::socket sock_;
explicit Connection(boost::asio::io_context &context,const int &size = 1024)
: ioc_(context)
, sock_(context)
, buffer_space_(size)
, buffer_(buffer_space_.data(),size){}
~Connection(){ if(sock_.is_open()) sock_.close(); }
void start_operation(){
if(sock_.is_open()){
sock_.async_read_some(buffer_,[me = shared_from_this()](const system::error_code &ec, std::size_t bytes){
if(!ec){
for(int i=0;i<bytes;++i)
std::cout << me->buffer_space_[i];
std::cout << std::endl;
me->start_operation();
}else{
error::print(ec);
return;
}
});
}
}
};
error.hpp
#pragma once
#include <iostream>
#include <boost/system/error_code.hpp>
namespace error {
inline void print(const boost::system::error_code &ec){
std::cerr << "Error Code : " << ec.value() << ", Message : " << ec.message() << std::endl;
}
}
Any help on this would be appreciated. Thanks!
The error lies in having the io_thread_.run() in the destructor which would destroy the socket object by then
int main(){
using Server_ = Server<Connection>;
auto server = std::make_unique<Server_>(8989);
server->start();
}
server will call the deleter, which destroys Server_ on exiting main. You Would like to join any threads or await the shutdown of the server before exiting main.
You would in your case join the iothread, like you TRIED to do.
However, you do it in the server constructor:
explicit Server(unsigned short port)
: acceptor_(ioc_)
, work_(ioc_)
, port_(port)
, endpoint_(asio::ip::tcp::v4(), port)
{
io_thread_ = std::move(std::thread([&] { ioc_.run(); }));
io_thread_.join();
}
I can't really figure out how this would not hang indefinitely due to the work_. On tangentially related observation is that work_ is NOT initilaized before ioc_ (or thread_) because initialization happens in order of member declaration instead of order in which the initializers appear. You will want to fix the declaration order regardless:
boost::asio::io_context ioc_;
boost::asio::ip::tcp::acceptor acceptor_;
boost::asio::io_context::work work_;
unsigned short port_;
boost::asio::ip::tcp::endpoint endpoint_;
std::thread io_thread_;
Similarly in Connection:
private:
boost::asio::io_context& ioc_;
public:
boost::asio::ip::tcp::socket sock_;
private:
std::vector<char> buffer_space_;
boost::asio::mutable_buffers_1 buffer_;
Fixed Demo
Live On Coliru
#include <iostream>
#include <boost/system/error_code.hpp>
namespace error {
inline void print(const boost::system::error_code& ec)
{
std::cerr << "Error Code : " << ec.value()
<< ", Message : " << ec.message() << std::endl;
}
}
#include <memory>
#include <boost/asio.hpp>
class Connection : public std::enable_shared_from_this<Connection> {
using shared_connection = std::shared_ptr<Connection>;
private:
boost::asio::io_context& ioc_;
public:
boost::asio::ip::tcp::socket sock_;
private:
std::vector<char> buffer_space_;
boost::asio::mutable_buffers_1 buffer_;
public:
explicit Connection(
boost::asio::io_context& context, const int& size = 1024)
: ioc_(context)
, sock_(context)
, buffer_space_(size)
, buffer_(buffer_space_.data(), size)
{
}
void start_operation()
{
if (sock_.is_open()) {
sock_.async_read_some(buffer_,
[me = shared_from_this()](
const boost::system::error_code& ec, std::size_t bytes) {
if (!ec) {
for (size_t i = 0; i < bytes; ++i) {
std::cout << me->buffer_space_[i];
}
std::cout << std::endl;
me->start_operation();
} else {
error::print(ec);
return;
}
});
}
}
};
#include <thread>
#include <boost/asio.hpp>
template <typename Connection> class Server {
using shared_connection = std::shared_ptr<Connection>;
private:
boost::asio::io_context ioc_;
boost::asio::ip::tcp::acceptor acceptor_;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type>
work_ {ioc_.get_executor()};
uint16_t port_;
boost::asio::ip::tcp::endpoint endpoint_;
std::thread io_thread_;
void handle_new_request(
shared_connection connection, const boost::system::error_code& ec)
{
if (!ec) {
connection->start_operation();
} else {
error::print(ec);
return;
}
}
public:
explicit Server(uint16_t port)
: acceptor_(ioc_)
, port_(port)
, endpoint_(boost::asio::ip::tcp::v4(), port)
, io_thread_([&] { ioc_.run(); })
{ ; }
~Server()
{
if (acceptor_.is_open()) {
boost::system::error_code ec;
acceptor_.cancel(ec);
//acceptor_.close(ec);
}
work_.reset();
io_thread_.join();
}
void start()
{
using boost::asio::ip::tcp;
boost::system::error_code ec;
// creates an actual operating system socket
acceptor_.open(endpoint_.protocol(), ec);
acceptor_.set_option(tcp::acceptor::reuse_address(true), ec);
// binds to the endpoint
acceptor_.bind(endpoint_, ec);
if (!ec) {
std::cout << "Listening for requests from port " << port_
<< std::endl;
acceptor_.listen();
} else {
error::print(ec);
return;
}
shared_connection connection = std::make_shared<Connection>(ioc_);
acceptor_.async_accept(
connection->sock_, [=, this](boost::system::error_code ec) {
handle_new_request(connection, ec);
});
}
};
#include <iostream>
//#include "server.hpp"
//#include "connection.hpp"
using namespace std::chrono_literals;
class Connection;
int main()
{
using Server_ = Server<Connection>;
auto server = std::make_unique<Server_>(8989);
server->start();
std::this_thread::sleep_for(4s);
// destructor joins
}
This would give 4s for the first client to connect, and will shut down as soon as all connections are done.
I'm currently trying to get a chatroom type program working with boost::asio. In the current state, the server is able to accept connections from clients, and then the clients are able to send messages to the server (at which point, the server formats the message a little bit and then sends it to every client currently connected).
The problem I am having is as follows:
server starts
client 0 connects
client 0 sends a message
(the message is received by the server and then sent back to client 0 who receives it correctly)
client 1 connects
client 1 sends a message
(the message is received by the server and then sent back to client 0 and client 1 who both receive it correctly)
client 0 tries to send a message again
(the message is received by the server and the server processes the header then attempts to call async_read again to read the body of the message, however the socket member variable for client 0 no longer exists and I get a segfault)
I find this really strange because the server still has a valid socket object for client 0 (otherwise it wouldn't be able to send client 1's messages to client 0).
Here is the relevant code:
tcp_connection class (where the segfault occurs)
#include <deque>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
using boost::asio::ip::tcp;
class tcp_connection {
public:
tcp_connection(tcp::socket socket_, int id, std::function<void (std::size_t, char*, std::size_t)> read_handler)
: socket_(std::move(socket)), id_(id), read_handler_(read_handler) {
}
void start() {
char first_message[] = "server: connected";
net_message msg(first_message, strlen(first_message));
send(msg);
read_header();
}
void send(net_message msg) {
bool write_in_progress = !write_messages_.empty();
write_messages_.push_back(msg);
if (!write_in_progress) {
do_write();
}
}
int get_id() { return id_; }
private:
void read_header() {
boost::asio::async_read(socket_, boost::asio::buffer(read_message_.get_data(), net_message::header_length),
boost::bind(&tcp_connection::handle_read_header, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read_header(const boost::system::error_code e, std::size_t bytes_transferred) {
read_message_.decode_header();
read_body();
}
void read_body() {
/*
######################
THIS IS WHERE THE SEGFAULT OCCURS.
socket_ is no longer valid for some reason
despite socket_ still being valid for any async_write
operations that need to be handled by the do_write() function
######################
*/
boost::asio::async_read(socket_, boost::asio::buffer(read_message_.get_data() + net_message::header_length, read_message_.get_body_length()),
boost::bind(&tcp_connection::handle_read_body, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read_body(const boost::system::error_code e, std::size_t bytes_transferred) {
char body[read_message_.get_body_length()];
memcpy(body, read_message_.get_body(), read_message_.get_body_length());
// call the read_handler from the net_server object
read_handler_(id_, body, read_message_.get_body_length());
read_header();
}
void handle_write(const boost::system::error_code e, std::size_t bytes_transferred) {
}
void do_write() {
boost::asio::async_write(socket_, boost::asio::buffer(write_messages_.front().get_data(),
write_messages_.front().get_body_length() + net_message::header_length),
[this] (boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
write_messages_.pop_front();
if (!write_messages_.empty()) {
do_write();
}
} else {
std::cerr << "error with writing to client " << id_ << " with error code: " << ec << std::endl;
}
});
}
tcp::socket socket_;
std::function<void (std::size_t, char*, std::size_t)> read_handler_;
net_message read_message_;
std::deque<net_message> write_messages_;
int id_;
};
net_server class
class net_server {
public:
net_server(boost::asio::io_context& io_context, std::size_t port,
std::function<void (std::size_t)> accept_handler,
std::function<void (std::size_t, char*, std::size_t)> read_handler)
: io_context_(io_context), acceptor_(io_context, tcp::endpoint(tcp::v4(), 1234)),
accept_handler_(accept_handler), read_handler_(read_handler) {
start_accept();
}
void send_to(std::size_t id, const char* body, std::size_t length) {
net_message msg(body, length);
connections_[id].send(msg);
}
void send_to_all(const char* body, std::size_t length) {
net_message msg(body, length);
for (int i = 0; i < connections_.size(); i++) {
connections_[i].send(msg);
}
}
void send_to_all_except(std::size_t id, const char* body, std::size_t length) {
net_message msg(body, length);
for (int i = 0; i < connections_.size(); i++) {
if (i == id) continue;
connections_[i].send(msg);
}
}
private:
void start_accept() {
acceptor_.async_accept(
[this](boost::system::error_code ec, tcp::socket socket) {
if (!ec) {
std::unique_lock lock(connections_mutex_);
std::size_t index = connections_.size();
connections_.emplace_back(std::move(socket), connections_.size(), read_handler_);
lock.unlock();
connections_[index].start();
accept_handler_(index);
}
start_accept();
});
}
boost::asio::io_context& io_context_;
tcp::acceptor acceptor_;
std::vector<tcp_connection> connections_;
std::mutex connections_mutex_;
std::function<void (std::size_t)> accept_handler_;
std::function<void (std::size_t, char*, std::size_t)> read_handler_;
};
main cpp program that sets up the server
#include <iostream>
class client {
public:
client()
: valid_(false)
{}
client(int id)
: id_(id), valid_(true)
{}
const char * get_name() const {
std::string str("Client ");
str += std::to_string(id_);
return str.c_str();
}
private:
int id_;
bool valid_;
};
class chat_server {
public:
chat_server(boost::asio::io_context& io_context, std::size_t port)
: server_(io_context, port, std::bind(&chat_server::handle_accept, this, std::placeholders::_1),
std::bind(&chat_server::handle_read, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))
{}
void handle_accept(std::size_t client_index) {
std::scoped_lock lock(clients_mutex_);
if (clients_.size() != client_index) {
std::cerr << "New client connecting at index " << client_index <<
" however, clients_ vector currently has size " << clients_.size() << std::endl;
if (clients_.size() < client_index) {
clients_.resize(client_index);
clients_.emplace_back(client_index);
} else {
clients_[client_index] = client(client_index);
}
} else {
clients_.emplace_back(client_index);
}
std::cout << "New client with id: " << client_index << std::endl;
}
void handle_read(std::size_t sender, char* body, std::size_t length) {
// whenever the server receives a message, this function will be called
// where clients[sender] will be the connection that sent the message
// body will be a pointer to the start of the body of the message
// and length will be the length of the body
// we will process the message here and decide if / what to send in response
// (for example, in a chat server, we'd want to forward the message to every client
// with the name of the sender attached to it so that clients can update the chat dialogue)
std::size_t sender_name_len = strlen(clients_[sender].get_name());
std::size_t new_message_length = sender_name_len + length + 3;
char new_message[new_message_length];
sprintf(new_message, "%s: ", clients_[sender].get_name());
memcpy(new_message + sender_name_len + 2, body, length);
new_message[new_message_length - 1] = '\0';
std::cout << new_message << std::endl;
server_.send_to_all(new_message, new_message_length-1);
}
private:
net_server server_;
std::vector<client> clients_;
std::mutex clients_mutex_;
};
int main() {
try {
boost::asio::io_context io_context;
chat_server serv(io_context, 1234);
io_context.run();
} catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
What I want is for my server class to maintain a list of tcp_connections which each represent a client that has connected to the server. When the server accepts a connection, a tcp_connection object is created for that connection and then that tcp_connection object starts an infinite asynchronous "read_header -> read_body -> repeat" loop. Whenever the server receives a message from any of the clients, it should format the message and then send it to every tcp_connection in the list.
Your connections_ member variable is being reallocated when you add new elements to it. In your various handlers in tcp_connection you are capturing this, when the vector is reallocated the value of this will change and your handlers will then try to operate on the old copy of the object causing undefined behaviour.
The simple solution is to make your connections_ vector a vector of std::shared_ptr.
It is also best practice to capture your object's shared_ptr in your handlers so that the object can't go out of scope before your callbacks execute. e.g.:
void do_write() {
auto self = shared_from_this();
boost::asio::async_write(socket_, boost::asio::buffer(write_messages_.front().get_data(),
write_messages_.front().get_body_length() + net_message::header_length),
[self, this] (boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
write_messages_.pop_front();
if (!write_messages_.empty()) {
do_write();
}
} else {
std::cerr << "error with writing to client " << id_ << " with error code: " << ec << std::endl;
}
});
}
You'll need to derive tcp_connection from std::shared_from_this<tcp_connection> and make sure that you have created the shared_ptr before setting up any handlers (e.g. don't create handlers in the constructor).
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 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 am following the example from http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/example/serialization/connection.hpp. I have modified the connection.hpp header to insert to more operations called aynchronous_read and asynchronous_write.The code is attached here...
//
// connection.hpp
// ~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2012 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)
//
#ifndef SERIALIZATION_CONNECTION_HPP
#define SERIALIZATION_CONNECTION_HPP
#include <boost/asio.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/tuple/tuple.hpp>
#include <iomanip>
#include <string>
#include <sstream>
#include <vector>
namespace s11n_example {
/// The connection class provides serialization primitives on top of a socket.
/**
* Each message sent using this class consists of:
* #li An 8-byte header containing the length of the serialized data in
* hexadecimal.
* #li The serialized data.
*/
class connection
{
public:
/// Constructor.
connection(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
/// Get the underlying socket. Used for making a connection or for accepting
/// an incoming connection.
boost::asio::ip::tcp::socket& socket()
{
return socket_;
}
/// Asynchronously write a data structure to the socket.
template <typename T, typename Handler>
void async_write(const T& t, Handler handler)
{
// Serialize the data first so we know how large it is.
std::ostringstream archive_stream;
boost::archive::text_oarchive archive(archive_stream);
archive << t;
outbound_data_ = archive_stream.str();
// Format the header.
std::ostringstream header_stream;
header_stream << std::setw(header_length)
<< std::hex << outbound_data_.size();
if (!header_stream || header_stream.str().size() != header_length)
{
// Something went wrong, inform the caller.
boost::system::error_code error(boost::asio::error::invalid_argument);
socket_.get_io_service().post(boost::bind(handler, error));
return;
}
outbound_header_ = header_stream.str();
// Write the serialized data to the socket. We use "gather-write" to send
// both the header and the data in a single write operation.
std::vector<boost::asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(outbound_header_));
buffers.push_back(boost::asio::buffer(outbound_data_));
boost::asio::async_write(socket_, buffers, handler);
}
///Asynchronously write a string to the socket.
template <typename T, typename Handler>
void asyncronous_write(const T& t, Handler handler)
{
std::ostringstream archive_stream;
boost::archive::text_oarchive archive(archive_stream);
archive << t;
outbound_str_ = archive_stream.str();
//boost::asio::buffer buffer1(outbound_str_);
boost::asio::async_write(socket_,boost::asio::buffer(outbound_str_), handler);
}
/// Asynchronously read a string from the socket.
template <typename T, typename Handler>
void asyncronous_read(T& t, Handler handler)
{
// Issue a read operation to read exactly the number of bytes in a header.
void (connection::*f)(
const boost::system::error_code&,
T&, boost::tuple<Handler>)
= &connection::handle_read_str<T, Handler>;
boost::asio::async_read(socket_, boost::asio::buffer(inbound_str_),
boost::bind(f,
this, boost::asio::placeholders::error, boost::ref(t),
boost::make_tuple(handler)));
}
/// Handle a completed read of message data.
template <typename T, typename Handler>
void handle_read_str(const boost::system::error_code& e,
T& t, boost::tuple<Handler> handler)
{
if (e)
{
boost::get<0>(handler)(e);
}
else
{
// Extract the data structure from the data just received.
try
{
std::string archive_data(&inbound_str_[0], inbound_str_.size());
std::istringstream archive_stream(archive_data);
boost::archive::text_iarchive archive(archive_stream);
archive >> t;
}
catch (std::exception& e)
{
// Unable to decode data.
boost::system::error_code error(boost::asio::error::invalid_argument);
boost::get<0>(handler)(error);
return;
}
// Inform caller that data has been received ok.
boost::get<0>(handler)(e);
}
}
/// Asynchronously read a data structure from the socket.
template <typename T, typename Handler>
void async_read(T& t, Handler handler)
{
// Issue a read operation to read exactly the number of bytes in a header.
void (connection::*f)(
const boost::system::error_code&,
T&, boost::tuple<Handler>)
= &connection::handle_read_header<T, Handler>;
boost::asio::async_read(socket_, boost::asio::buffer(inbound_header_),
boost::bind(f,
this, boost::asio::placeholders::error, boost::ref(t),
boost::make_tuple(handler)));
}
/// Handle a completed read of a message header. The handler is passed using
/// a tuple since boost::bind seems to have trouble binding a function object
/// created using boost::bind as a parameter.
template <typename T, typename Handler>
void handle_read_header(const boost::system::error_code& e,
T& t, boost::tuple<Handler> handler)
{
if (e)
{
boost::get<0>(handler)(e);
}
else
{
// Determine the length of the serialized data.
std::istringstream is(std::string(inbound_header_, header_length));
std::size_t inbound_data_size = 0;
if (!(is >> std::hex >> inbound_data_size))
{
// Header doesn't seem to be valid. Inform the caller.
boost::system::error_code error(boost::asio::error::invalid_argument);
boost::get<0>(handler)(error);
return;
}
// Start an asynchronous call to receive the data.
inbound_data_.resize(inbound_data_size);
void (connection::*f)(
const boost::system::error_code&,
T&, boost::tuple<Handler>)
= &connection::handle_read_data<T, Handler>;
boost::asio::async_read(socket_, boost::asio::buffer(inbound_data_),
boost::bind(f, this,
boost::asio::placeholders::error, boost::ref(t), handler));
}
}
/// Handle a completed read of message data.
template <typename T, typename Handler>
void handle_read_data(const boost::system::error_code& e,
T& t, boost::tuple<Handler> handler)
{
if (e)
{
boost::get<0>(handler)(e);
}
else
{
// Extract the data structure from the data just received.
try
{
std::string archive_data(&inbound_data_[0], inbound_data_.size());
std::istringstream archive_stream(archive_data);
boost::archive::text_iarchive archive(archive_stream);
archive >> t;
}
catch (std::exception& e)
{
// Unable to decode data.
boost::system::error_code error(boost::asio::error::invalid_argument);
boost::get<0>(handler)(error);
return;
}
// Inform caller that data has been received ok.
boost::get<0>(handler)(e);
}
}
private:
/// The underlying socket.
boost::asio::ip::tcp::socket socket_;
/// The size of a fixed length header.
enum { header_length = 8 };
/// Holds an outbound header.
std::string outbound_header_;
/// Holds the outbound data.
std::string outbound_data_;
std::string outbound_str_;
/// Holds an inbound header.
char inbound_header_[header_length];
/// Holds the inbound data.
std::vector<char> inbound_data_;
std::vector<char>inbound_str_;
//char* inbound_str_=new char[1024];
};
typedef boost::shared_ptr<connection> connection_ptr;
} // namespace s11n_example
#endif // SERIALIZATION_CONNECTION_HPP
The server and client codes are ---
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <fstream>
#include <vector>
#include "connection.hpp" // Must come before boost/serialization headers.
#include <boost/serialization/vector.hpp>
#include "structsample.hpp"
namespace s11n_example {
/// Serves stock quote information to any client that connects to it.
class server
{
public:
/// Constructor opens the acceptor and starts waiting for the first incoming
/// connection.
server(boost::asio::io_service& io_service, unsigned short port)
: acceptor_(io_service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
// Create the data to be sent to each client.
std::string str;
std::ifstream in;
stock s;
in.open("aces.clf");
if(!in)
{
std::cout<<"Error in Opening a file"<<std::endl;
exit(1);
}
for(int i=0;i<13;i++)
{
getline(in,str);
}
while(!in.eof())
{
getline(in,str);
s.data.append(str);
getline(in,str);
s.data.append(str);
s.d_size=s.data.size();
stocks_.push_back(s);
s.data.clear();
}
// Start an accept operation for a new connection.
connection_ptr new_conn(new connection(acceptor_.get_io_service()));
acceptor_.async_accept(new_conn->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error, new_conn));
}
/// Handle completion of a accept operation.
void handle_accept(const boost::system::error_code& e, connection_ptr conn)
{
std::string buf1;
if (!e)
{
// Successfully accepted a new connection. Send the list of stocks to the
// client. The connection::async_write() function will automatically
// serialize the data structure for us.
for(int i=0;i<5;i++)
{
conn->async_write(stocks_[i],
boost::bind(&server::handle_write, this,
boost::asio::placeholders::error, conn));
usleep(500000);
conn->async_read(buf1,
boost::bind(&server::handle_read, this,
boost::asio::placeholders::error, conn));
std::cout<<buf1<<"\n"<<std::endl;
}
// Start an accept operation for a new connection.
connection_ptr new_conn(new connection(acceptor_.get_io_service()));
acceptor_.async_accept(new_conn->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error, new_conn));
}
else
{
// An error occurred. Log it and return. Since we are not starting a new
// accept operation the io_service will run out of work to do and the
// server will exit.
std::cerr << e.message() << std::endl;
}
}
/// Handle completion of a write operation.
void handle_write(const boost::system::error_code& e, connection_ptr conn)
{
//conn->async_read(buf1,
//boost::bind(&server::handle_read, this,
// boost::asio::placeholders::error, conn));
// std::cout<<buf1<<"\n"<<std::endl;
}
///handle completion of read operation.
void handle_read(const boost::system::error_code& e,connection_ptr conn)
{
//if(!e)
//{
//std::cout<<"\n"<<stocks_1.data<<std::endl;
//stocks_1.data.clear();
//}
//{
//if (buf1.compare("data received and processed")==0)
//{
// buf1.clear();
//std::cout<<buf1<<"\n"<<std::endl;
//}
//else
//{
//std::cout<<"Error occurred"<<std::endl;
//exit(1);
// }
//}
}
private:
/// The acceptor object used to accept incoming socket connections.
boost::asio::ip::tcp::acceptor acceptor_;
/// The data to be sent to each client.
std::vector<stock> stocks_;
};
} // namespace s11n_example
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 2)
{
std::cerr << "Usage: server <port>" << std::endl;
return 1;
}
unsigned short port = boost::lexical_cast<unsigned short>(argv[1]);
boost::asio::io_service io_service;
s11n_example::server server(io_service, port);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <fstream>
#include <vector>
#include "connection.hpp" // Must come before boost/serialization headers.
#include <boost/serialization/vector.hpp>
#include "structsample.hpp"
struct stock1
{
std::string data;
int d_size;
template <typename Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar & data;
ar & d_size;
}
} stocks_1;
namespace s11n_example {
/// Downloads stock quote information from a server.
class client
{
public:
/// Constructor starts the asynchronous connect operation.
client(boost::asio::io_service& io_service,
const std::string& host, const std::string& service)
: connection_(io_service)
{
// Resolve the host name into an IP address.
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query(host, service);
boost::asio::ip::tcp::resolver::iterator endpoint_iterator =
resolver.resolve(query);
boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
// Start an asynchronous connect operation.
connection_.socket().async_connect(endpoint,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error, ++endpoint_iterator));
}
/// Handle completion of a connect operation.
void handle_connect(const boost::system::error_code& e,
boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
if (!e)
{
// Successfully established connection. Start operation to read the list
// of stocks. The connection::async_read() function will automatically
// decode the data that is read from the underlying socket.
connection_.async_read(stocks_1,
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error));
}
else if (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator())
{
// Try the next endpoint.
connection_.socket().close();
boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
connection_.socket().async_connect(endpoint,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error, ++endpoint_iterator));
}
else
{
// An error occurred. Log it and return. Since we are not starting a new
// operation the io_service will run out of work to do and the client will
// exit.
std::cerr << e.message() << std::endl;
}
}
/// Handle completion of a read operation.
void handle_read(const boost::system::error_code& e)
{
std::string buf2;
if (!e)
{
std::cout << " data: " << stocks_1.data << "\n";
std::cout << " size: " << stocks_1.d_size << "\n";
usleep(500000);
if(!e)
{
//stocks_1.data="data received and processed";
//tocks_1.d_size=stocks_1.data.size();
connection_.async_write(buf2,
boost::bind(&client::handle_write, this,
boost::asio::placeholders::error));
std::cout<<buf2<<std::endl;
//buf2.clear();
// connection_.async_write(stocks_1,
//boost::bind(&client::handle_write, this,
//boost::asio::placeholders::error));
//stocks_1.data.clear();
}
}
else
{
// An error occurred.
std::cerr << e.message() << std::endl;
}
// Since we are not starting a new operation the io_service will run out of
// work to do and the client will exit.
}
void handle_write(const boost::system::error_code& e)
{
if(!e)
{
connection_.async_read(stocks_1,
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error));
}
else
//std::cout<<"Error is there"<<std::endl;
exit(1);
}
private:
/// The connection to the server.
connection connection_;
/// The data received from the server.
std::vector<stock> stocks_;
};
} // namespace s11n_example
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 3)
{
std::cerr << "Usage: client <host> <port>" << std::endl;
return 1;
}
boost::asio::io_service io_service;
s11n_example::client client(io_service, argv[1], argv[2]);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
structsample.hpp----
#ifndef SERIALIZATION_STOCK_HPP
#define SERIALIZATION_STOCK_HPP
#include <string>
namespace s11n_example {
/// Structure to hold information about a single stock.
struct stock
{
public:
std::string data;
int d_size;
template <typename Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar & data;
ar & d_size;
}
};
} // namespace s11n_example
#endif // SERIALIZATION_STOCK_HPP
Here the data is reaching from server to client but the response from client is not sent back.
What ASIO really transfers is bytes of data. A string is not a suitable container for storing arbitrary data (which may contain null characters and will cause confusing results if contained in the string).
To transfer a string using boost::asio, it is better to use a buffer based on std::vector or std::array, and then analyze the data in this buffer to obtain your string.