I want to transfer some files via tcp over lan, and so I wrote the following code for my TX-Part:
void send_data(char * filename, char * dest)
{
try
{
boost::asio::io_service io_service;
char dest_t = *dest;
std::string adr = ip_adr_display[0] + ':' + boost::lexical_cast<std::string>(PORTNUM_TCP_IN);
std::cout << "Adress is: " << adr << " and file is: " << filename << '\n';
if(debugmode)
debug_global << adr << '\n';
std::string file = filename;
async_tcp_client client(io_service, adr, file);
io_service.run();
}
catch(std::exception& e)
{
};
};
and RX-Part:
void rec_data(void)
{
try
{
std::cout << "Receiving data...\n";
async_tcp_server *recv_file_tcp_server = new async_tcp_server(PORTNUM_TCP_IN);
if(debugmode)
debug_global << "Receiving...\n";
delete recv_file_tcp_server;
}
catch(std::exception &e)
{
};
};
with the following server and client code:
using boost::asio::ip::tcp;
class async_tcp_client
{
public:
async_tcp_client(boost::asio::io_service& io_service, const std::string& server, const std::string& path):resolver_(io_service), socket_(io_service)
{
size_t pos = server.find(':');
if(pos==std::string::npos)
return;
std::string port_string = server.substr(pos+1);
std::string server_ip_or_host = server.substr(0,pos);
source_file.open(path.c_str(), std::ios_base::binary|std::ios_base::ate);
if(!source_file)
{
std::cout << "Failed to open " << path << std::endl;
return;
}
size_t file_size = source_file.tellg();
source_file.seekg(0);
std::ostream request_stream(&request_);
request_stream << path << "\n" << file_size << "\n\n";
std::cout << "Request size: " << request_.size() << std::endl;
tcp::resolver::query query(server_ip_or_host, port_string);
resolver_.async_resolve(query, boost::bind(&async_tcp_client::handle_resolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator));
};
private:
void handle_resolve(const boost::system::error_code & err, tcp::resolver::iterator endpoint_iterator)
{
if(!err)
{
tcp::endpoint endpoint = *endpoint_iterator;
socket_.async_connect(endpoint, boost::bind(&async_tcp_client::handle_connect, this, boost::asio::placeholders::error, ++endpoint_iterator));
}
else
{
std::cout << "Error: " << err.message() << '\n';
}
};
void handle_connect(const boost::system::error_code &err, tcp::resolver::iterator endpoint_iterator)
{
if(!err)
{
boost::asio::async_write(socket_, request_, boost::bind(&async_tcp_client::handle_write_file, this, boost::asio::placeholders::error));
}
else if(endpoint_iterator != tcp::resolver::iterator())
{
socket_.close();
tcp::endpoint endpoint = *endpoint_iterator;
socket_.async_connect(endpoint, boost::bind(&async_tcp_client::handle_connect, this, boost::asio::placeholders::error, ++endpoint_iterator));
}
else
{
std::cout << "Error: " << err.message() << '\n';
};
}
void handle_write_file(const boost::system::error_code& err)
{
if(!err)
{
if(source_file.eof() == false)
{
source_file.read(buf.c_array(), (std::streamsize)buf.size());
if(source_file.gcount()<= 0)
{
std::cout << "read file error" << std::endl;
return;
};
std::cout << "Send " << source_file.gcount() << "bytes, total: " << source_file.tellg() << " bytes.\n";
boost::asio::async_write(socket_, boost::asio::buffer(buf.c_array(), source_file.gcount()),boost::bind(&async_tcp_client::handle_write_file, this, boost::asio::placeholders::error));
if(err)
{
std::cout << "Send error: " << err << std::endl;
return;
}
}
else
return;
}
else
{
std::cout << "Error: " << err.message() << "\n";
}
};
tcp::resolver resolver_;
tcp::socket socket_;
boost::array<char, 1024> buf;
boost::asio::streambuf request_;
std::ifstream source_file;
};
class async_tcp_connection: public boost::enable_shared_from_this<async_tcp_connection>
{
public:
async_tcp_connection(boost::asio::io_service& io_service):socket_(io_service), file_size(0){}
void start()
{
if(debugmode)
debug_global << __FUNCTION__ << std::endl;
async_read_until(socket_, request_buf, "\n\n", boost::bind(&async_tcp_connection::handle_read_request, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
boost::asio::ip::tcp::socket& socket(){return socket_;}
private:
boost::asio::streambuf request_buf;
size_t file_size;
std::ofstream output_file;
boost::asio::ip::tcp::socket socket_;
boost::array<char, 40960> buf;
void handle_read_request(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if(err)
{
return handle_error(__FUNCTION__, err);
}
if(debugmode)
debug_global << __FUNCTION__ << "(" << bytes_transferred << ")" <<", in_avail = " << request_buf.in_avail() << ", size = " << request_buf.size() << ", max_size = " << request_buf.max_size() << ".\n";
std::istream request_stream(&request_buf);
std::string file_path;
request_stream >> file_path;
request_stream >> file_size;
request_stream.read(buf.c_array(), 2);
if(debugmode)
debug_global << file_path << " size is " << file_size << ", tellg = " << request_stream.tellg() << std::endl;
size_t pos = file_path.find_last_of('\\');
if(pos!= std::string::npos)
file_path = file_path.substr(pos+1);
output_file.open(file_path.c_str(), std::ios_base::binary);
if(!output_file)
{
if(debugmode)
debug_global << "Failed to open: " << file_path << std::endl;
return;
}
do{
request_stream.read(buf.c_array(), (std::streamsize)buf.size());
if(debugmode)
debug_global << __FUNCTION__ << " write " << request_stream.gcount() << " bytes.\n";
output_file.write(buf.c_array(), request_stream.gcount());
}while(request_stream.gcount() > 0);
async_read(socket_, boost::asio::buffer(buf.c_array(), buf.size()),boost::bind(&async_tcp_connection::handle_read_file_content, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void handle_read_file_content(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if (bytes_transferred>0)
{
output_file.write(buf.c_array(), (std::streamsize)bytes_transferred);
if(debugmode)
debug_global << __FUNCTION__ << " recv " << output_file.tellp() << " bytes."<< std::endl;
if (output_file.tellp()>=(std::streamsize)file_size)
{
return;
}
}
if (err)
{
return handle_error(__FUNCTION__, err);
}
async_read(socket_, boost::asio::buffer(buf.c_array(), buf.size()), boost::bind(&async_tcp_connection::handle_read_file_content, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void handle_error(const std::string& function_name, const boost::system::error_code& err)
{
if(debugmode)
debug_global << __FUNCTION__ << " in " << function_name <<" due to " << err <<" " << err.message()<< std::endl;
}
};
class async_tcp_server : private boost::noncopyable
{
public:
typedef boost::shared_ptr<async_tcp_connection> ptr_async_tcp_connection;
async_tcp_server(unsigned short port):acceptor_(io_service_, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port), true)
{
ptr_async_tcp_connection new_connection_(new async_tcp_connection(io_service_));
acceptor_.async_accept(new_connection_->socket(), boost::bind(&async_tcp_server::handle_accept, this,new_connection_, boost::asio::placeholders::error));
io_service_.run();
}
void handle_accept(ptr_async_tcp_connection current_connection, const boost::system::error_code& e)
{
if(debugmode)
debug_global << __FUNCTION__ << " " << e << ", " << e.message()<<std::endl;
if (!e)
{
current_connection->start();
//ptr_async_tcp_connection new_connection_(new async_tcp_connection(io_service_));
//acceptor_.async_accept(new_connection_->socket(),
// boost::bind(&async_tcp_server::handle_accept, this,new_connection_,
// boost::asio::placeholders::error));
}
}
~async_tcp_server()
{
io_service_.stop();
}
private:
boost::asio::io_service io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
};
If I want to transmit a file, I have to enter the absolute path (why?), if I enter the relative path (e.g. "Image.jpg"), I get the error message "failed to open Image.jpg".
After successfull calling the function, I get the following output:
Adress is: <ip>:<port> and file is: <full file path>
Request size: 91
Send 1024 bytes, total: 1024 bytes
Send 1024 bytes, total: 2048 bytes
etc..
Send 1024 bytes, total: 20480 bytes
Send 406 bytes, total: -1 bytes (Why?)
At the receiving side, I get no received data. Why? I do not understand why my code is not working...
Thank you very much!
UPDATE In my answer I casually said
I added postfix .received to the output file name to prevent overwriting the source.
I just realized that this is likely your problem:
If you use your code with the receiver on the same machine as the sender, you will overwrite the source file while you are still sending it... OOPS.
So, I fixed up the code just I could run it.
Here's the test main:
int main()
{
boost::thread_group g;
g.create_thread(rec_data); // get the receiver running
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
g.create_thread([] { send_data("test.cpp"); });
g.join_all();
}
I added postfix .received to the output file name to prevent overwriting the source.
When running this, it appears to work reasonably well:
g++ -std=c++11 -Wall -pedantic -pthread test.cpp -lboost_system -lboost_thread
./a.out
md5sum test.cpp test.cpp.received
We get the output
0dc16e7f0dc23cb9fce100d825852621 test.cpp.received
0dc16e7f0dc23cb9fce100d825852621 test.cpp
I've also tested it with a png and with a 93Mb executable.
Full code (also on Coliru, although Coliru doesn't allow network connections):
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <fstream>
#include <boost/enable_shared_from_this.hpp>
using boost::asio::ip::tcp;
static bool debugmode = true;
static boost::mutex debug_mutex;
static std::ostream debug_global(std::clog.rdbuf());
class async_tcp_client
{
public:
async_tcp_client(boost::asio::io_service& io_service, const std::string& server, const std::string& path)
: resolver_(io_service), socket_(io_service)
{
size_t pos = server.find(':');
if(pos==std::string::npos)
{
return;
}
std::string port_string = server.substr(pos+1);
std::string server_ip_or_host = server.substr(0,pos);
source_file.open(path, std::ios_base::binary|std::ios_base::ate);
if(!source_file)
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << __LINE__ << "Failed to open " << path << std::endl;
return;
}
size_t file_size = source_file.tellg();
source_file.seekg(0);
std::ostream request_stream(&request_);
request_stream << path << "\n" << file_size << "\n\n";
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Request size: " << request_.size() << std::endl;
}
tcp::resolver::query query(server_ip_or_host, port_string);
resolver_.async_resolve(query, boost::bind(&async_tcp_client::handle_resolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator));
};
private:
void handle_resolve(const boost::system::error_code & err, tcp::resolver::iterator endpoint_iterator)
{
if(!err)
{
tcp::endpoint endpoint = *endpoint_iterator;
socket_.async_connect(endpoint, boost::bind(&async_tcp_client::handle_connect, this, boost::asio::placeholders::error, ++endpoint_iterator));
}
else
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Error: " << err.message() << '\n';
}
};
void handle_connect(const boost::system::error_code &err, tcp::resolver::iterator endpoint_iterator)
{
if(!err)
{
boost::asio::async_write(socket_, request_, boost::bind(&async_tcp_client::handle_write_file, this, boost::asio::placeholders::error));
}
else if(endpoint_iterator != tcp::resolver::iterator())
{
socket_.close();
tcp::endpoint endpoint = *endpoint_iterator;
socket_.async_connect(endpoint, boost::bind(&async_tcp_client::handle_connect, this, boost::asio::placeholders::error, ++endpoint_iterator));
}
else
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Error: " << err.message() << '\n';
};
}
void handle_write_file(const boost::system::error_code& err)
{
if(!err)
{
if(source_file)
//if(source_file.eof() == false)
{
source_file.read(buf.c_array(), (std::streamsize)buf.size());
if(source_file.gcount()<= 0)
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "read file error" << std::endl;
return;
};
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Send " << source_file.gcount() << "bytes, total: " << source_file.tellg() << " bytes.\n";
}
boost::asio::async_write(socket_, boost::asio::buffer(buf.c_array(), source_file.gcount()),boost::bind(&async_tcp_client::handle_write_file, this, boost::asio::placeholders::error));
}
else
{
return;
}
}
else
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Error: " << err.message() << "\n";
}
};
tcp::resolver resolver_;
tcp::socket socket_;
boost::array<char, 1024> buf;
boost::asio::streambuf request_;
std::ifstream source_file;
};
class async_tcp_connection: public boost::enable_shared_from_this<async_tcp_connection>
{
public:
async_tcp_connection(boost::asio::io_service& io_service)
: socket_(io_service), file_size(0)
{
}
void start()
{
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __FUNCTION__ << std::endl;
}
async_read_until(socket_, request_buf, "\n\n", boost::bind(&async_tcp_connection::handle_read_request, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
boost::asio::ip::tcp::socket& socket()
{
return socket_;
}
private:
boost::asio::streambuf request_buf;
std::ofstream output_file;
boost::asio::ip::tcp::socket socket_;
size_t file_size;
boost::array<char, 40960> buf;
void handle_read_request(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if(err)
{
return handle_error(__FUNCTION__, err);
}
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __FUNCTION__ << "(" << bytes_transferred << ")" <<", in_avail = " << request_buf.in_avail() << ", size = " << request_buf.size() << ", max_size = " << request_buf.max_size() << ".\n";
}
std::istream request_stream(&request_buf);
std::string file_path;
request_stream >> file_path;
request_stream >> file_size;
request_stream.read(buf.c_array(), 2);
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << file_path << " size is " << file_size << ", tellg = " << request_stream.tellg() << std::endl;
}
size_t pos = file_path.find_last_of('\\');
if(pos!= std::string::npos)
{
file_path = file_path.substr(pos+1);
}
output_file.open(file_path + ".received", std::ios_base::binary);
if(!output_file)
{
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __LINE__ << "Failed to open: " << file_path << std::endl;
}
return;
}
do
{
request_stream.read(buf.c_array(), (std::streamsize)buf.size());
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __FUNCTION__ << " write " << request_stream.gcount() << " bytes.\n";
}
output_file.write(buf.c_array(), request_stream.gcount());
}
while(request_stream.gcount() > 0);
async_read(socket_, boost::asio::buffer(buf.c_array(), buf.size()),boost::bind(&async_tcp_connection::handle_read_file_content, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void handle_read_file_content(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if (bytes_transferred>0)
{
output_file.write(buf.c_array(), (std::streamsize)bytes_transferred);
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __FUNCTION__ << " recv " << output_file.tellp() << " bytes."<< std::endl;
}
if (output_file.tellp()>=(std::streamsize)file_size)
{
return;
}
}
if (err)
{
return handle_error(__FUNCTION__, err);
}
async_read(socket_, boost::asio::buffer(buf.c_array(), buf.size()), boost::bind(&async_tcp_connection::handle_read_file_content, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void handle_error(const std::string& function_name, const boost::system::error_code& err)
{
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __FUNCTION__ << " in " << function_name <<" due to " << err <<" " << err.message()<< std::endl;
}
}
};
class async_tcp_server : private boost::noncopyable
{
public:
typedef boost::shared_ptr<async_tcp_connection> ptr_async_tcp_connection;
async_tcp_server(unsigned short port):acceptor_(io_service_, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port), true)
{
ptr_async_tcp_connection new_connection_(new async_tcp_connection(io_service_));
acceptor_.async_accept(new_connection_->socket(), boost::bind(&async_tcp_server::handle_accept, this,new_connection_, boost::asio::placeholders::error));
io_service_.run();
}
void handle_accept(ptr_async_tcp_connection current_connection, const boost::system::error_code& e)
{
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __FUNCTION__ << " " << e << ", " << e.message()<<std::endl;
}
if (!e)
{
current_connection->start();
}
}
~async_tcp_server()
{
io_service_.stop();
}
private:
boost::asio::io_service io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
};
void send_data(std::string const& filename, std::string const& adr = "localhost:6767")
{
try
{
boost::asio::io_service io_service;
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Adress is: " << adr << " and file is: " << filename << '\n';
}
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << adr << '\n';
}
async_tcp_client client(io_service, adr, filename);
io_service.run();
}
catch(std::exception const& e)
{
std::cerr << "Exception in " << __PRETTY_FUNCTION__ << ": " << e.what() << "\n";
};
};
void rec_data(void)
{
try
{
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Receiving data...\n";
}
async_tcp_server recv_file_tcp_server(6767);
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << "Received\n";
}
}
catch(std::exception const& e)
{
std::cerr << "Exception in " << __PRETTY_FUNCTION__ << ": " << e.what() << "\n";
};
};
int main()
{
boost::thread_group g;
g.create_thread(rec_data); // get the receiver running
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
g.create_thread([] { send_data("main.cpp"); });
g.join_all();
}
Related
I'm trying to use serial_port of asio(standlone) to get data from a device. But I can't read even a byte.
I post a screenshoot of the example usage found from documentation website as below :
enter image description here
enter image description here
https://think-async.com/Asio/asio-1.24.0/doc/asio/reference/basic_serial_port/async_read_some.html
There are 3 question I have:
What is the specific type of the first parameter of async_read_some? std::vector<uint8_t>, for example?
How to bind a class member function as the callback/handler?
Is it right that we just make sure io_context.run() run on a background thread and call async_read_some just once?
I'll appreciate it if you guys could help me check out my code or give some advice.
class NmeaSource {
public:
explicit NmeaSource(std::shared_ptr<asio::io_context> io, const nlohmann::json& settings);
void parse();
void handle(const asio::error_code& error, // Result of operation.
std::size_t bytes_transferred // Number of bytes read.
);
private:
void open();
std::string m_port;
int m_baudrate;
std::shared_ptr<asio::io_context> m_io;
std::shared_ptr<asio::serial_port> m_serial;
unsigned char m_readBuffer[1024];
};
NmeaSource::NmeaSource(std::shared_ptr<asio::io_context> io, const nlohmann::json& settings)
:m_io(io)
{
try {
setPort(settings.at("port"));
setBaudrate(settings.at("baudrate"));
//LOG("");
std::cout << "port: " << m_port << ", baudrate: " << m_baudrate << std::endl;
}
catch (nlohmann::json::basic_json::type_error& e1) {
std::cout << "type is wrong " << e1.what() << std::endl;
throw e1;
}
catch (nlohmann::json::basic_json::out_of_range& e2) {
std::cout << "key is not found " << e2.what() << std::endl;
throw e2;
}
catch (...)
{
std::cout << "unknown error"<< std::endl;
exit(-1);
}
open();
}
void NmeaSource::open()
{
m_serial = std::make_shared<asio::serial_port>(*m_io);
asio::error_code ec;
m_serial->open(m_port, ec);
if (!ec) {
asio::serial_port_base::baud_rate baudrate(m_baudrate);
m_serial->set_option(baudrate);
std::cout << "successfully" << std::endl;
}
else {
std::cout << "failed " << ec.message() <<std::endl;
}
}
void NmeaSource::handle(const asio::error_code& error, // Result of operation.
std::size_t bytes_transferred // Number of bytes read.
)
{
if (!error) {
std::cout << bytes_transferred << " bytes read!" << std::endl;
}
else {
std::cout << error.message() << std::endl;
}
}
void NmeaSource::parse()
{
m_serial->async_read_some(
asio::buffer(m_readBuffer, 1024),
std::bind(&NmeaSource::handle, this,
std::placeholders::_1,
std::placeholders::_2)
);
}
int main()
{
auto io = std::make_shared<asio::io_context>();
//std::thread t(std::bind(static_cast<size_t(asio::io_service::*)()>(&asio::io_service::run), io.get()));
std::thread t([&io]() {io->run(); });
NmeaSource rtk(io, nlohmann::json{ {"port", "COM3"}, {"baudrate", 115200} });
rtk.parse();
for (;;)
{
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
}
The output
There is a simple example with making use of boost::asio::io_context
https://github.com/unegare/boost-ex/blob/500e46f4d3b41e2abe48e2deccfab39d44ae94e0/main.cpp
#include <boost/asio.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <thread>
#include <vector>
#include <memory>
#include <mutex>
#include <chrono>
#include <iostream>
#include <exception>
std::mutex m_stdout;
class MyWorker {
std::shared_ptr<boost::asio::io_context> io_context;
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard;
public:
MyWorker(std::shared_ptr<boost::asio::io_context> &_io_context, std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> &_work_guard):
io_context(_io_context), work_guard(_work_guard) {}
MyWorker(const MyWorker &mw): io_context(mw.io_context), work_guard(mw.work_guard) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker copy constructor" << std::endl;
m_stdout.unlock();
}
MyWorker(MyWorker &&mw): io_context(std::move(mw.io_context)), work_guard(std::move(mw.work_guard)) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker move constructor" << std::endl;
m_stdout.unlock();
}
~MyWorker() {}
void operator() () {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Thread Start" << std::endl;
m_stdout.unlock();
while(true) {
try {
boost::system::error_code ec;
io_context->run(ec);
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker: received an error: " << ec << std::endl;
m_stdout.unlock();
continue;
}
break;
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
}
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Thread Finish" << std::endl;
m_stdout.unlock();
}
};
class Client: public std::enable_shared_from_this<Client> {
std::shared_ptr<boost::asio::io_context> io_context;
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard;
std::shared_ptr<boost::asio::ip::tcp::socket> sock;
std::shared_ptr<std::array<char, 512>> buff;
public:
Client(std::shared_ptr<boost::asio::io_context> &_io_context, std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> &_work_guard, std::shared_ptr<boost::asio::ip::tcp::socket> &_sock):
io_context(_io_context), work_guard(_work_guard), sock(_sock) {
buff = std::make_shared<std::array<char,512>>();
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " with args" << std::endl;
m_stdout.unlock();
}
Client(const Client &cl): io_context(cl.io_context), work_guard(cl.work_guard), sock(cl.sock), buff(cl.buff) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " copy" << std::endl;
m_stdout.unlock();
}
Client(Client &&cl): io_context(std::move(cl.io_context)), work_guard(std::move(cl.work_guard)), sock(std::move(cl.sock)), buff(std::move(cl.buff)) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " move" << std::endl;
m_stdout.unlock();
}
~Client() {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " buff.use_count: " << buff.use_count() << " | sock.use_count: " << sock.use_count() << " | io_context.use_count: " << io_context.use_count() << std::endl;
m_stdout.unlock();
}
void OnConnect(const boost::system::error_code &ec) {
std::cout << __FUNCTION__ << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
// buff = std::make_shared<std::array<char, 512>>();
char req[] = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
memcpy(buff->data(), req, strlen(req));
m_stdout.lock();
std::cout << req << std::endl;
m_stdout.unlock();
sock->async_write_some(boost::asio::buffer(buff->data(), strlen(buff->data())), std::bind(std::mem_fn(&Client::OnSend), this, std::placeholders::_1, std::placeholders::_2));
}
std::cout << __FUNCTION__ << " use_count: " << buff.use_count() << std::endl;
}
void OnSend(const boost::system::error_code &ec, std::size_t bytes_transferred) {
std::cout << __FUNCTION__ << " use_count: " << io_context.use_count() << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
std::cout << __FUNCTION__ << " use_count: " << buff.use_count() << std::endl;
buff->fill(0);
std::cout << __FUNCTION__ << std::endl;
sock->async_read_some(boost::asio::buffer(buff->data(), buff->size()), std::bind(std::mem_fn(&Client::OnRecv), this, std::placeholders::_1, std::placeholders::_2));
}
}
void OnRecv(const boost::system::error_code &ec, std::size_t bytes_transferred) {
std::cout << __FUNCTION__ << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
m_stdout.lock();
std::cout << buff->data() << std::endl;
m_stdout.unlock();
}
}
};
int main () {
std::shared_ptr<boost::asio::io_context> io_context(std::make_shared<boost::asio::io_context>());
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard(
std::make_shared<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> (boost::asio::make_work_guard(*io_context))
);
MyWorker mw(io_context, work_guard);
std::vector<std::thread> vth;
vth.reserve(1);
for (int i = 1; i > 0; --i) {
vth.emplace_back(mw);
}
std::shared_ptr<Client> cl = 0;
try {
boost::asio::ip::tcp::resolver resolver(*io_context);
boost::asio::ip::tcp::resolver::query query("unegare.info", "80");
boost::asio::ip::tcp::endpoint ep = *resolver.resolve(query);
m_stdout.lock();
std::cout << "ep: " << ep << std::endl;
m_stdout.unlock();
std::shared_ptr<boost::asio::ip::tcp::socket> sock(std::make_shared<boost::asio::ip::tcp::socket>(*io_context));
std::shared_ptr<Client> cl2(std::make_shared<Client>(io_context, work_guard, sock));
cl = cl2->shared_from_this();
m_stdout.lock();
std::cout << "HERE: use_count = " << cl.use_count() << std::endl;
m_stdout.unlock();
sock->async_connect(ep, std::bind(std::mem_fn(&Client::OnConnect), *cl2->shared_from_this(), std::placeholders::_1));
std::this_thread::sleep_for(std::chrono::duration<double>(1));
m_stdout.lock();
std::cout << "AFTER CALL" << std::endl;
m_stdout.unlock();
// asm volatile ("");
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Main Thread: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
try {
char t;
std::cin >> t;
work_guard->reset();
// std::this_thread::sleep_for(std::chrono::duration<double>(1));
// std::cout << "Running" << std::endl;
// io_context->run();
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Main Thread: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
std::for_each(vth.begin(), vth.end(), std::mem_fn(&std::thread::join));
return 0;
}
stdout:
[140487203505984] MyWorker copy constructor
[140487203505984] MyWorker move constructor
[140487185372928] Thread Start
ep: 95.165.130.37:80
[140487203505984] Client with args
HERE: use_count = 2
[140487203505984] Client copy
[140487203505984] Client move
[140487203505984] ~Client buff.use_count: 0 | sock.use_count: 0 | io_context.use_count: 0
[140487185372928] Client move
[140487185372928] ~Client buff.use_count: 0 | sock.use_count: 0 | io_context.use_count: 0
OnConnect
GET / HTTP/1.1
Host: unegare.info
OnConnect use_count: 2
[140487185372928] ~Client buff.use_count: 2 | sock.use_count: 3 | io_context.use_count: 5
Segmentation Fault (core dumped)
But there is a little problem with the understanding of the segfault caused by the bad reference to the Client object.
But I do not understand why cl2 becomes destructed after the call of
sock->async_connect(ep, std::bind(std::mem_fn(&Client::OnConnect), *cl2->shared_from_this(), std::placeholders::_1));
at the 162 line.
As well as ... Why was there an invocation of the copy constructor?
as it may be noticed from the stdout above.
It's good that you try to understand. However, start simple!
your copy constructor fails to call the base-class copy constructor (!!) oops
your binds bind to
this (which does NOT keep the shared pointer alive)
*cl2->shared_from_this() - oops this binds to a COPY of *cl2 by value¹. That is obviously the reason why that COPY is destructed when you're done
The invalid reference arose from the combination of 1. and 2.b.
I'd suggest to simplify. A lot!
Use Rule Of Zero (only declare special members when you need. In this, you introduced a bug because you did something wrong manually writing a copy-constructor that you didn't need)
Use non-owning references where appropriate (not everything needs to be a smart pointer)
Prefer std::unique_ptr for things that do not require shared ownership (shared ownership should be really rare)
Prefer std::lock_guard instead of manually locking and unlocking (that's not exception safe)
Many others: work_guard doesn't need to be copied, it has a reset() member already, catch by const-reference, if you're gonna catch, don't need to use error_code on io_context::run, check the end-point iterator of resolve before dereference, use boost::asio::connect instead so you don't have to check and iterate over different endpoints etc.
prefer std::string over fixed-size buffers if you're doing dynamic allocation anyways, use non-implementation defined chrono durations (for example 1.0s, not duration<double>(1)), consider using boost::thread_group instead of vector<std::thread> or at least only joining threads that are joinable() etc
Suggest to make that async_connect part of Client, since then you can simply bind members the same way as all the rest of the binds (using shared_from_this(), instead of writing the bug you had)
If you have time left, consider using Beast for the Http request and response creation/parsing
Just as a reference, this is what MyWorker could be:
class MyWorker {
ba::io_context& io_context;
public:
MyWorker(ba::io_context& _io_context) : io_context(_io_context) {}
//// best is to not mention these at all, because even `default`ing can change what compiler generates
//MyWorker(const MyWorker& mw) = default;
//MyWorker(MyWorker&& mw) = default;
//~MyWorker() = default;
void operator()() {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
}
};
At this point, you could very well just make it a lambda:
auto mw = [&io_context] {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
};
Much simpler.
The Full Code Simplified
Just see e.g. the simplicity of the new connection code:
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
[self=shared_from_this()](error_code ec, tcp::endpoint) { self->OnConnect(ec); });
}
The endpoint is printed when OnConnect is called.
Live On Coliru²
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
namespace ba = boost::asio;
using ba::ip::tcp;
using namespace std::literals;
static std::mutex s_stdout;
#define TRACE(expr) { \
std::lock_guard<std::mutex> lk(s_stdout); \
std::cout << "[" << std::this_thread::get_id() << "] " << expr << std::endl; \
}
class Client : public std::enable_shared_from_this<Client> {
ba::io_context& io_context;
tcp::socket sock;
std::string buf;
public:
Client(ba::io_context& _io_context) : io_context(_io_context), sock{io_context}
{ }
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
std::bind(std::mem_fn(&Client::OnConnect), shared_from_this(), std::placeholders::_1));
}
void OnConnect(const boost::system::error_code& ec) {
TRACE(__FUNCTION__ << " ep:" << sock.remote_endpoint());
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message());
} else {
buf = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
TRACE(std::quoted(buf));
sock.async_write_some(ba::buffer(buf),
std::bind(std::mem_fn(&Client::OnSend), shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
}
void OnSend(const boost::system::error_code& ec, std::size_t bytes_transferred) {
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
TRACE(__FUNCTION__);
buf.assign(512, '\0');
sock.async_read_some(ba::buffer(buf), std::bind(std::mem_fn(&Client::OnRecv), shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
}
void OnRecv(const boost::system::error_code& ec, std::size_t bytes_transferred) {
TRACE(__FUNCTION__);
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
buf.resize(bytes_transferred);
TRACE(std::quoted(buf));
}
}
};
int main() {
ba::io_context io_context;
auto work_guard = make_work_guard(io_context);
boost::thread_group vth;
auto mw = [&io_context] {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
};
vth.create_thread(mw);
try {
std::make_shared<Client>(io_context)->Start();
char t;
std::cin >> t;
work_guard.reset();
} catch (std::exception const& ex) {
TRACE("Main Thread: caught an exception: " << ex.what());
}
vth.join_all();
}
Prints:
[140095938852608] Thread Start
[140095938852608] OnConnect ep:95.165.130.37:80
[140095938852608] "GET / HTTP/1.1
Host: unegare.info
"
[140095938852608] OnSend
[140095938852608] OnRecv
[140095938852608] "HTTP/1.1 200 OK
Date: Sun, 22 Dec 2019 22:26:56 GMT
Server: Apache/2.4.18 (Ubuntu)
Last-Modified: Sun, 10 Mar 2019 10:17:38 GMT
ETag: \"37-583bac3f3c843\"
Accept-Ranges: bytes
Content-Length: 55
Content-Type: text/html
<html>
<head>
</head>
<body>
It works.
</body>
</html>
"
q
[140095938852608] Thread Finish
BONUS
std::bind is obsolete since C++11, consider using lambdas instead. Since Coliru didn't want to co-operate any more, I'll just post the three changed functions in full:
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
[self=shared_from_this()](error_code ec, tcp::endpoint) { self->OnConnect(ec); });
}
void OnConnect(const boost::system::error_code& ec) {
TRACE(__FUNCTION__ << " ep:" << sock.remote_endpoint());
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message());
} else {
buf = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
TRACE(std::quoted(buf));
sock.async_write_some(ba::buffer(buf),
[self=shared_from_this()](error_code ec, size_t bytes_transferred) { self->OnSend(ec, bytes_transferred); });
}
}
void OnSend(const boost::system::error_code& ec, std::size_t bytes_transferred) {
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
TRACE(__FUNCTION__);
buf.assign(512, '\0');
sock.async_read_some(ba::buffer(buf),
[self=shared_from_this()](error_code ec, size_t bytes_transferred) { self->OnRecv(ec, bytes_transferred); });
}
}
¹ Does boost::bind() copy parameters by reference or by value?
² Coliru doesn't allow network access
You don’t track destruction of cl2 with you output: cl2 is std::shared_ptr<Client>. You seem to track construction of Client objects.
You problem is the * in front of cl2->make_shared_from_this(): that will dereference the std::shared_ptr<Client>. The bind() expression sees a Client& and captures a Client by copy. Removing the * should fix the problem. I haven’t fully understood was code is trying to do (I’m reading it on a phone) but I guess you actually want to capture the std::shared_ptr<Client> rather than the Client.
Also, as cl2 is already a std::shared_ptr<Client> there is no point in calling make_shared_from_this() on the pointed to object. It just recreates an unnecessary st::shared_ptr<Client>.
I need to write a class that handle ssl connection (read/write char array) to a black box server. I need to implement disconnect/connect function. But it ain't work as expected.
Use case:
User connect to server (worked: can send and receive messages)
User disconnect to server, wait for a while then reconnect again. (Failed: it only works when the duration is short: like 10 secs. If longer than that handle_connect() return error connection timed out)
Here is the source code to the class and how I use it:
boost::asio::io_service &mioService;
SSLHandler* mpSSLConnection;
void Connector::setupConnection()
{
try{
std::string port = std::to_string(mPort);
boost::asio::ip::tcp::resolver resolver(mioService);
boost::asio::ip::tcp::resolver::query query(mHost, port);
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::ssl::context context(boost::asio::ssl::context::sslv23);
// context.load_verify_file("key.pem");
if(mpSSLConnection == nullptr)
mpSSLConnection = new SSLHandler(mioService,context,iterator,this);
}catch (std::exception& e){
std::cerr << "Exception: " << e.what() << "\n";
}
// actually this line is called from outside the func
mpSSLConnection->connectToServer();
}
and disconnect like this
void Connector::disconnect()
{
isSetDisconnectedToSrv = true;
mpSSLConnection->setIsDestructing(true);
QThread::msleep(500);
delete mpSSLConnection;
mpSSLConnection = nullptr;
// setupConnection();
isConnectedToServer =false; // we did delete the object handle the ssl connection so...
mpHandler->onServerDisconnected(); // just report to upper layer
}
Finally, the class source code:
#ifndef SSLHANDLER_H
#define SSLHANDLER_H
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <istream>
#include <ostream>
#include <string>
#include <queue>
#include <boost/lockfree/spsc_queue.hpp>
class Connector;
const int READ_SIZE =0;
const int READ_MSG=1;
class SSLHandler
{
public:
SSLHandler(boost::asio::io_service& io_service, boost::asio::ssl::context& context, boost::asio::ip::tcp::resolver::iterator endpoint_iterator, Connector* pConnector)
: socket_(io_service, context) , mEndpointIterator (endpoint_iterator) , mpConnector (pConnector),
timer_{ io_service},
isConnectionOk {false}
{
LOG_TRACE << "creating new sslhandler";
socket_.set_verify_mode(boost::asio::ssl::context::verify_none);
socket_.set_verify_callback(boost::bind(&SSLHandler::verify_certificate, this, _1, _2));
mode = READ_SIZE;
}
~SSLHandler();
bool verify_certificate(bool preverified, boost::asio::ssl::verify_context& ctx);
void handle_connect(const boost::system::error_code& error);
void handle_handshake(const boost::system::error_code& error);
void handle_write(const boost::system::error_code& error, size_t bytes_transferred);
void handle_write_auth(const boost::system::error_code& error, size_t bytes_transferred);
void handle_read_msgsize(const boost::system::error_code& error, size_t bytes_transferred);
void handle_read_message(const boost::system::error_code& error, size_t bytes_transferred);
void connectToServer();
void do_reconnect();
void handle_reconnect_timer(boost::system::error_code ec);
void writeMessage(std::vector<char> &array);
void setRequestMsg(std::vector<char> &&array);
void setIsDestructing(bool value);
private:
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
boost::asio::ip::tcp::resolver::iterator mEndpointIterator;
boost::asio::deadline_timer timer_;
char reply_[0x1 << 16]; //=65356 bytes
int mode;
uint32_t size;
std::vector<char> requestMsg;
std::vector<char> replyMsg;
Connector* mpConnector; // ptr to object compose message
std::queue<std::vector < char> > mQueueMsg;
bool isConnectionOk;
bool isDestructing =false;
private:
void writeMessageWithQueue(std::vector<char> &array);
};
#endif // SSLHANDLER_H
#include "sslhandler.h"
#include "connector.h"
#include "BoostLogger.h"
#include <QThread>
#include "boost/enable_shared_from_this.hpp"
SSLHandler::~SSLHandler()
{
LOG_FATAL << "ssl handler shutdown";
if(isConnectionOk){
socket_.lowest_layer().close();
boost::system::error_code ec;
socket_.shutdown(ec);
if(ec){
LOG_FATAL << "ssl handler socket shutdown with err: " << ec.message();
}
LOG_TRACE << "ssl handler shutdown complete";
}
}
bool SSLHandler::verify_certificate(bool preverified, boost::asio::ssl::verify_context &ctx)
{
char subject_name[256];
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying:\n" << subject_name << std::endl;
return preverified;
}
void SSLHandler::handle_connect(const boost::system::error_code &error)
{
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
LOG_TRACE << "get past destructing";
if(!error){
isConnectionOk = true;
LOG_TRACE << "Connection OK!" << std::endl;
socket_.async_handshake(boost::asio::ssl::stream_base::client, boost::bind(&SSLHandler::handle_handshake, this, boost::asio::placeholders::error));
}else{
LOG_FATAL << "Connect failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_handshake(const boost::system::error_code &error)
{
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
if(!error){
std::cout << "Sending request: " << std::endl;
boost::asio::async_write(socket_,
boost::asio::buffer(requestMsg.data(), requestMsg.size()),
boost::bind(&SSLHandler::handle_write_auth, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}else{
LOG_FATAL << "Handshake failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_write(const boost::system::error_code &error, size_t bytes_transferred)
{
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
if (error) {
LOG_FATAL << "Write failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
Q_UNUSED(bytes_transferred);
usleep(1e4);
if (!mQueueMsg.empty()) {
mQueueMsg.pop();
if(!mQueueMsg.empty()){
auto msg = mQueueMsg.front();
writeMessageWithQueue(msg);
}
}
else{
LOG_ERROR << "Empty queue messages!";
}
}
void SSLHandler::handle_write_auth(const boost::system::error_code &error, size_t bytes_transferred)
{
usleep(1e5);
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
if (!error){
if(mode==READ_SIZE){
mode = READ_MSG;
std::cerr << "\nSending request read size OK!\n" << std::endl;
// char respond[bytes_transferred] = "";
boost::asio::async_read(socket_, boost::asio::buffer(reply_,4),
boost::bind(&SSLHandler::handle_read_msgsize,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
// std::cerr << "respond is " ;
}
}else{
LOG_FATAL << "Write failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_read_msgsize(const boost::system::error_code &error, size_t bytes_transferred)
{
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
if (!error){
//first 4 bytes contain size of message
size = getFirstFour();
mode = READ_SIZE;
boost::asio::async_read(socket_, boost::asio::buffer(reply_,size),
boost::bind(&SSLHandler::handle_read_message,
this,
// mWriteId++,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}else{
LOG_FATAL << "Read failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_read_message(const boost::system::error_code &error, size_t bytes_transferred)
{
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
if (!error){
replyMsg.clear();
replyMsg.assign(reply_,reply_+ size);
mpConnector->setReadMsg(replyMsg);
mode = READ_SIZE;
// read next message size
boost::asio::async_read(socket_, boost::asio::buffer(reply_,4),
boost::bind(&SSLHandler::handle_read_msgsize,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}else{
LOG_FATAL << "Read failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::connectToServer()
{
// boost::asio::ip::tcp::resolver r(socket_.get_io_service());
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
boost::asio::async_connect(socket_.lowest_layer(), mEndpointIterator, boost::bind(&SSLHandler::handle_connect, this, boost::asio::placeholders::error));
LOG_TRACE << "async_connect called";
}
void SSLHandler::do_reconnect()
{
// return;
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
// socket_.shutdown();
isConnectionOk = false;
else{
socket_.lowest_layer().cancel();
timer_.expires_from_now(boost::posix_time::millisec(500));
timer_.async_wait(boost::bind(&SSLHandler::handle_reconnect_timer, this, boost::asio::placeholders::error()));
}
}
void SSLHandler::handle_reconnect_timer(boost::system::error_code ec)
{
if(!ec){
connectToServer();
}
else{
LOG_TRACE << "Error with reconnect timer : " << ec.message();
}
}
void SSLHandler::writeMessageWithQueue(std::vector<char> &array)
{
// std::cerr << "write : " << (void*) array.data() << " | " << array.size() << std::endl;
if(isDestructing){
LOG_TRACE << "Is destructing ssl connect so abort " ;
return;
}
boost::asio::async_write(socket_,
boost::asio::buffer(array.data(), array.size()),
boost::bind(&SSLHandler::handle_write, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void SSLHandler::writeMessage(std::vector<char> &array)
{
if(mQueueMsg.size()==0){
mQueueMsg.push(array);
writeMessageWithQueue(array);
}
else
mQueueMsg.push(array);
}
void SSLHandler::setRequestMsg(std::vector<char> &&array)
{
requestMsg = std::move(array);
}
void SSLHandler::setIsDestructing(bool value)
{
LOG_INFO << "ssl connection destructing set as " << value;
isDestructing = value;
if(isDestructing == true){
if(isConnectionOk){
socket_.lowest_layer().cancel();
// socket_.shutdown();
LOG_INFO << "ssl connection destructing get pass shutdown";
}
}
}
PS: Here is the handler tracking which is weird, after (call) disconnect() and reconnect again: no hander was fired, so no handshake after connection is established :(
#asio|1521627714.863517|0|resolver#0x7fbe2affc898.cancel Empty fz
params!Empty fz rules!Empty fz param!#asio____________ socket_:
0x7fbe10005150 #asio____________192.168.2.36
#asio|1521627714.864161|0*1|socket#0x7fbe10005150.async_connect #asio:
connect called #asio|1521627714.865136|>1|ec=system:0
#asio|1521627714.865375|1*2|socket#0x7fbe10005150.async_send
#asio|1521627714.865416|<1|
#asio|1521627714.865421|>2|ec=system:0,bytes_transferred=517
#asio|1521627714.865429|2*3|socket#0x7fbe10005150.async_receive
#asio|1521627714.865451|<2|
#asio|1521627714.866829|>3|ec=system:0,bytes_transferred=994
#asio|1521627714.867764|3*4|socket#0x7fbe10005150.async_send
#asio|1521627714.867792|<3|
#asio|1521627714.867798|>4|ec=system:0,bytes_transferred=326
#asio|1521627714.867809|4*5|socket#0x7fbe10005150.async_receive
#asio|1521627714.867817|<4|
#asio|1521627714.870094|>5|ec=system:0,bytes_transferred=234
#asio|1521627714.870271|5*6|socket#0x7fbe10005150.async_send
#asio|1521627714.870318|<5|
#asio|1521627714.870333|>6|ec=system:0,bytes_transferred=154
#asio|1521627714.970430|6*7|socket#0x7fbe10005150.async_receive
#asio|1521627714.970443|<6|
#asio|1521627714.970449|>7|ec=system:0,bytes_transferred=138
#asio|1521627714.970470|7*8|socket#0x7fbe10005150.async_receive
#asio|1521627714.970475|<7|
#asio|1521627714.970479|>8|ec=system:0,bytes_transferred=0
#asio|1521627714.971418|8*9|socket#0x7fbe10005150.async_send
#asio|1521627714.971771|8*10|deadline_timer#0x5290628.async_wait
#asio|1521627714.972004|8*11|socket#0x7fbe10005150.async_receive
#asio|1521627714.972012|<8|
#asio|1521627714.972017|>9|ec=system:0,bytes_transferred=138
#asio|1521627714.982098|9*12|socket#0x7fbe10005150.async_send
#asio|1521627714.982115|<9|
#asio|1521627714.982121|>12|ec=system:0,bytes_transferred=138
#asio|1521627714.992214|12*13|socket#0x7fbe10005150.async_send
#asio|1521627714.992244|<12|
#asio|1521627714.992255|>11|ec=system:0,bytes_transferred=292
#asio|1521627714.992278|11*14|socket#0x7fbe10005150.async_receive
#asio|1521627714.992284|<11|
#asio|1521627714.992290|>13|ec=system:0,bytes_transferred=186
#asio|1521627715.002355|<13|
#asio|1521627715.002363|>14|ec=system:0,bytes_transferred=0
#asio|1521627715.002469|14*15|socket#0x7fbe10005150.async_receive
#asio|1521627715.002479|<14|
#asio|1521627715.002487|>15|ec=system:0,bytes_transferred=0
#asio|1521627715.002495|15*16|socket#0x7fbe10005150.async_receive
#asio|1521627715.002500|<15|
#asio|1521627715.002505|>16|ec=system:0,bytes_transferred=0
#asio|1521627715.002550|16*17|socket#0x7fbe10005150.async_receive
#asio|1521627715.002561|<16|
#asio|1521627715.002566|>17|ec=system:0,bytes_transferred=154
#asio|1521627715.002581|17*18|socket#0x7fbe10005150.async_receive
#asio|1521627715.002586|<17|
#asio|1521627715.002590|>18|ec=system:0,bytes_transferred=0
#asio|1521627715.002636|18*19|socket#0x7fbe10005150.async_receive
#asio|1521627715.002653|<18| #asio|1521627721.861983|>10|ec=system:0
#asio|1521627721.862105|10*20|socket#0x7fbe10005150.async_send
#asio|1521627721.862139|10|deadline_timer#0x5290628.cancel
#asio|1521627721.862144|10*21|deadline_timer#0x5290628.async_wait
#asio|1521627721.862258|<10|
#asio|1521627721.862268|>20|ec=system:0,bytes_transferred=138
#asio|1521627721.872365|<20|
#asio|1521627721.872398|>19|ec=system:0,bytes_transferred=138
#asio|1521627721.872436|19*22|socket#0x7fbe10005150.async_receive
#asio|1521627721.872443|<19|
#asio|1521627721.872447|>22|ec=system:0,bytes_transferred=0
#asio|1521627721.872503|22*23|socket#0x7fbe10005150.async_receive
#asio|1521627721.872515|<22| #asio|1521627724.861966|>21|ec=system:0
#asio|1521627724.862091|21*24|socket#0x7fbe10005150.async_send
#asio|1521627724.862148|21|deadline_timer#0x5290628.cancel
#asio|1521627724.862157|21*25|deadline_timer#0x5290628.async_wait
#asio|1521627724.862272|<21|
#asio|1521627724.862286|>24|ec=system:0,bytes_transferred=138
#asio|1521627724.872375|<24|
#asio|1521627724.872409|>23|ec=system:0,bytes_transferred=138
#asio|1521627724.872457|23*26|socket#0x7fbe10005150.async_receive
#asio|1521627724.872465|<23|
#asio|1521627724.872469|>26|ec=system:0,bytes_transferred=0
#asio|1521627724.872510|26*27|socket#0x7fbe10005150.async_receive
#asio|1521627724.872516|<26| #asio|1521627727.861968|>25|ec=system:0
#asio|1521627727.862084|25*28|socket#0x7fbe10005150.async_send
#asio|1521627727.862120|25|deadline_timer#0x5290628.cancel
#asio|1521627727.862125|25*29|deadline_timer#0x5290628.async_wait
#asio|1521627727.862204|<25|
#asio|1521627727.862211|>28|ec=system:0,bytes_transferred=138
#asio|1521627727.872283|<28|
#asio|1521627727.872314|>27|ec=system:0,bytes_transferred=138
#asio|1521627727.872362|27*30|socket#0x7fbe10005150.async_receive
#asio|1521627727.872366|<27|
#asio|1521627727.872371|>30|ec=system:0,bytes_transferred=0
#asio|1521627727.872412|30*31|socket#0x7fbe10005150.async_receive
#asio|1521627727.872418|<30| #asio|1521627730.861967|>29|ec=system:0
#asio|1521627730.862072|29*32|socket#0x7fbe10005150.async_send
#asio|1521627730.862118|29|deadline_timer#0x5290628.cancel
#asio|1521627730.862125|29*33|deadline_timer#0x5290628.async_wait
#asio|1521627730.862217|<29|
#asio|1521627730.862227|>32|ec=system:0,bytes_transferred=138
#asio|1521627730.872315|<32|
#asio|1521627730.872360|>31|ec=system:0,bytes_transferred=138
#asio|1521627730.872406|31*34|socket#0x7fbe10005150.async_receive
#asio|1521627730.872412|<31|
#asio|1521627730.872416|>34|ec=system:0,bytes_transferred=0
#asio|1521627730.872458|34*35|socket#0x7fbe10005150.async_receive
#asio|1521627730.872465|<34| #asio|1521627733.862001|>33|ec=system:0
#asio|1521627733.862114|33*36|socket#0x7fbe10005150.async_send
#asio|1521627733.862153|33|deadline_timer#0x5290628.cancel
#asio|1521627733.862158|33*37|deadline_timer#0x5290628.async_wait
#asio|1521627733.862244|<33|
#asio|1521627733.862250|>36|ec=system:0,bytes_transferred=138
#asio|1521627733.872342|<36|
#asio|1521627733.872379|>35|ec=system:0,bytes_transferred=138
#asio|1521627733.872416|35*38|socket#0x7fbe10005150.async_receive
#asio|1521627733.872422|<35|
#asio|1521627733.872424|>38|ec=system:0,bytes_transferred=0
#asio|1521627733.872461|38*39|socket#0x7fbe10005150.async_receive
#asio|1521627733.872466|<38| #asio|1521627736.861976|>37|ec=system:0
#asio|1521627736.862158|37*40|socket#0x7fbe10005150.async_send
#asio|1521627736.862235|37|deadline_timer#0x5290628.cancel
#asio|1521627736.862242|37*41|deadline_timer#0x5290628.async_wait
#asio|1521627736.862406|<37|
#asio|1521627736.862414|>40|ec=system:0,bytes_transferred=138
#asio|1521627736.872497|<40|
#asio|1521627736.872555|>39|ec=system:0,bytes_transferred=138
#asio|1521627736.872622|39*42|socket#0x7fbe10005150.async_receive
#asio|1521627736.872638|<39|
#asio|1521627736.872641|>42|ec=system:0,bytes_transferred=0
#asio|1521627736.872720|42*43|socket#0x7fbe10005150.async_receive
#asio|1521627736.872726|<42| #asio|1521627739.861978|>41|ec=system:0
#asio|1521627739.862096|41*44|socket#0x7fbe10005150.async_send
#asio|1521627739.862144|41|deadline_timer#0x5290628.cancel
#asio|1521627739.862148|41*45|deadline_timer#0x5290628.async_wait
#asio|1521627739.862243|<41|
#asio|1521627739.862249|>44|ec=system:0,bytes_transferred=138
#asio|1521627739.872335|<44|
#asio|1521627739.872375|>43|ec=system:0,bytes_transferred=138
#asio|1521627739.872421|43*46|socket#0x7fbe10005150.async_receive
#asio|1521627739.872425|<43|
#asio|1521627739.872429|>46|ec=system:0,bytes_transferred=0
#asio|1521627739.872477|46*47|socket#0x7fbe10005150.async_receive
#asio|1521627739.872492|<46| #asio|1521627742.861953|>45|ec=system:0
#asio|1521627742.862121|45*48|socket#0x7fbe10005150.async_send
#asio|1521627742.862204|45|deadline_timer#0x5290628.cancel
#asio|1521627742.862211|45*49|deadline_timer#0x5290628.async_wait
#asio|1521627742.862392|<45|
#asio|1521627742.862406|>48|ec=system:0,bytes_transferred=138
#asio|1521627742.872491|<48|
#asio|1521627742.872543|>47|ec=system:0,bytes_transferred=138
#asio|1521627742.872592|47*50|socket#0x7fbe10005150.async_receive
#asio|1521627742.872600|<47|
#asio|1521627742.872605|>50|ec=system:0,bytes_transferred=0
#asio|1521627742.872675|50*51|socket#0x7fbe10005150.async_receive
#asio|1521627742.872688|<50|
#asio|1521627745.316714|0|socket#0x7fbe10005150.close
#asio|1521627745.316777|>51|ec=system:125,bytes_transferred=0
#asio|1521627745.316858|<51| #asio: ~SSLHandler
#asio|1521627745.817594|0|resolver#0x7fbe00ff8758.cancel
#asio|1521627745.861965|>49|ec=system:0 #asio|1521627745.861984|<49|
#asio|1521627749.757091|0|resolver#0x7fbe00ff8648.cancel
#asio____________ socket_: 0x7fbde4008890
#asio____________192.168.2.36
#asio|1521627749.757178|0*52|socket#0x7fbde4008890.async_connect
#asio: connect called
I fixed the sample to be selfcontained, and ran it against a demo server:
#include <iostream>
#include <sstream>
#include <vector>
#ifdef STANDALONE
namespace {
struct LogTx {
std::stringstream _ss;
std::ostream& _os;
bool _armed = true;
LogTx(std::ostream& os) : _os(os) {}
LogTx(LogTx&& rhs) : _ss(std::move(rhs._ss)), _os(rhs._os) { rhs._armed = false; }
~LogTx() { if (_armed) _os << _ss.rdbuf() << std::endl; }
LogTx operator<<(std::ostream&(&v)(std::ostream&)) { _ss << v; return std::move(*this); }
template <typename T> LogTx operator<<(T&& v) { _ss << v; return std::move(*this); }
};
}
# define LOG_FATAL LogTx(std::cerr) << "FATAL: "
# define LOG_TRACE LogTx(std::clog) << "TRACE: "
# define LOG_ERROR LogTx(std::cerr) << "ERROR: "
# define LOG_INFO LogTx(std::clog) << "INFO: "
# define Q_UNUSED(a) static_cast<void>(a)
namespace {
struct Connector {
void sendDisconnectedStatus() { LOG_INFO << "Disconnected"; }
void setReadMsg(std::vector<char> const& v) { LOG_INFO << "response: '" << std::string(v.begin(), v.end()) << "'"; }
};
}
#endif
#ifndef SSLHANDLER_H
#define SSLHANDLER_H
#include <boost/endian/arithmetic.hpp> // for big_uint32_t
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <queue>
#include <string>
#include <thread>
const int READ_SIZE = 0;
const int READ_MSG = 1;
class SSLHandler {
public:
SSLHandler(boost::asio::io_service &io_service, boost::asio::ssl::context &context,
boost::asio::ip::tcp::resolver::iterator endpoint_iterator, Connector *pConnector)
: socket_(io_service, context), mEndpointIterator(endpoint_iterator),
mpConnector(pConnector), timer_{ io_service }, isConnectionOk{ false }
{
LOG_TRACE << "creating new sslhandler";
socket_.set_verify_mode(boost::asio::ssl::context::verify_none);
socket_.set_verify_callback(boost::bind(&SSLHandler::verify_certificate, this, _1, _2));
mode = READ_SIZE;
}
~SSLHandler();
bool verify_certificate(bool preverified, boost::asio::ssl::verify_context &ctx);
void handle_connect(const boost::system::error_code &error);
void handle_handshake(const boost::system::error_code &error);
void handle_write(const boost::system::error_code &error, size_t bytes_transferred);
void handle_write_auth(const boost::system::error_code &error, size_t bytes_transferred);
void handle_read_msgsize(const boost::system::error_code &error, size_t bytes_transferred);
void handle_read_message(const boost::system::error_code &error, size_t bytes_transferred);
void connectToServer();
void do_reconnect();
void handle_reconnect_timer(boost::system::error_code ec);
void writeMessage(std::vector<char> &array);
void setRequestMsg(std::vector<char> &&array);
void setIsDestructing(bool value);
private:
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
boost::asio::ip::tcp::resolver::iterator mEndpointIterator;
Connector *mpConnector; // ptr to object compose message
boost::asio::deadline_timer timer_;
char reply_[0x1 << 16]; //=65356 bytes
size_t getFirstFour() {
return *boost::asio::buffer_cast<boost::endian::big_uint32_t *>(boost::asio::buffer(reply_));
};
int mode;
uint32_t size;
std::vector<char> requestMsg;
std::vector<char> replyMsg;
std::queue<std::vector<char> > mQueueMsg;
bool isConnectionOk;
bool isDestructing = false;
private:
void writeMessageWithQueue(std::vector<char> &array);
};
#endif // SSLHANDLER_H
//#include "sslhandler.h"
//#include "connector.h"
//#include "BoostLogger.h"
//#include <QThread>
//#include "boost/enable_shared_from_this.hpp"
SSLHandler::~SSLHandler() {
LOG_FATAL << "ssl handler shutdown";
if (isConnectionOk) {
socket_.lowest_layer().close();
boost::system::error_code ec;
socket_.shutdown(ec);
if (ec) {
LOG_FATAL << "ssl handler socket shutdown with err: " << ec.message();
}
LOG_TRACE << "ssl handler shutdown complete";
}
}
bool SSLHandler::verify_certificate(bool preverified, boost::asio::ssl::verify_context &ctx) {
char subject_name[256];
X509 *cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying:\n" << subject_name << std::endl;
return preverified;
}
void SSLHandler::handle_connect(const boost::system::error_code &error) {
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
LOG_TRACE << "get past destructing";
if (!error) {
isConnectionOk = true;
LOG_TRACE << "Connection OK!" << std::endl;
socket_.async_handshake(boost::asio::ssl::stream_base::client,
boost::bind(&SSLHandler::handle_handshake, this, boost::asio::placeholders::error));
} else {
LOG_FATAL << "Connect failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_handshake(const boost::system::error_code &error) {
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
if (!error) {
std::cout << "Sending request: " << std::endl;
boost::asio::async_write(socket_, boost::asio::buffer(requestMsg.data(), requestMsg.size()),
boost::bind(&SSLHandler::handle_write_auth, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else {
LOG_FATAL << "Handshake failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_write(const boost::system::error_code &error, size_t bytes_transferred) {
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
if (error) {
LOG_FATAL << "Write failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
Q_UNUSED(bytes_transferred);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (!mQueueMsg.empty()) {
mQueueMsg.pop();
if (!mQueueMsg.empty()) {
auto msg = mQueueMsg.front();
writeMessageWithQueue(msg);
}
} else {
LOG_ERROR << "Empty queue messages!";
}
}
void SSLHandler::handle_write_auth(const boost::system::error_code &error, size_t bytes_transferred) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
if (!error) {
if (mode == READ_SIZE) {
mode = READ_MSG;
std::cerr << "\nSending request read size OK!\n" << std::endl;
// char respond[bytes_transferred] = "";
boost::asio::async_read(socket_, boost::asio::buffer(reply_, 4),
boost::bind(&SSLHandler::handle_read_msgsize, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
// std::cerr << "respond is " ;
}
} else {
LOG_FATAL << "Write failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_read_msgsize(const boost::system::error_code &error, size_t bytes_transferred) {
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
if (!error) {
// first 4 bytes contain size of message
size = getFirstFour();
LOG_TRACE << "Decoded size: " << size;
mode = READ_SIZE;
boost::asio::async_read(socket_, boost::asio::buffer(reply_, size),
boost::bind(&SSLHandler::handle_read_message, this,
// mWriteId++,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else {
LOG_FATAL << "Read failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::handle_read_message(const boost::system::error_code &error, size_t bytes_transferred) {
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
if (!error) {
replyMsg.clear();
replyMsg.assign(reply_, reply_ + size);
mpConnector->setReadMsg(replyMsg);
mode = READ_SIZE;
// read next message size
boost::asio::async_read(socket_, boost::asio::buffer(reply_, 4),
boost::bind(&SSLHandler::handle_read_msgsize, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else {
LOG_FATAL << "Read failed: " << error.message() << std::endl;
mpConnector->sendDisconnectedStatus();
do_reconnect();
}
}
void SSLHandler::connectToServer() {
// boost::asio::ip::tcp::resolver r(socket_.get_io_service());
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
boost::asio::async_connect(socket_.lowest_layer(), mEndpointIterator,
boost::bind(&SSLHandler::handle_connect, this, boost::asio::placeholders::error));
LOG_TRACE << "async_connect called";
}
void SSLHandler::do_reconnect() {
// socket_.shutdown();
isConnectionOk = false;
// return;
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
} else {
socket_.lowest_layer().cancel();
timer_.expires_from_now(boost::posix_time::millisec(500));
timer_.async_wait(boost::bind(&SSLHandler::handle_reconnect_timer, this, boost::asio::placeholders::error()));
}
}
void SSLHandler::handle_reconnect_timer(boost::system::error_code ec) {
if (!ec) {
connectToServer();
} else {
LOG_TRACE << "Error with reconnect timer : " << ec.message();
}
}
void SSLHandler::writeMessageWithQueue(std::vector<char> &array) {
// std::cerr << "write : " << (void*) array.data() << " | " << array.size() << std::endl;
if (isDestructing) {
LOG_TRACE << "Is destructing ssl connect so abort ";
return;
}
boost::asio::async_write(socket_, boost::asio::buffer(array.data(), array.size()),
boost::bind(&SSLHandler::handle_write, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void SSLHandler::writeMessage(std::vector<char> &array) {
if (mQueueMsg.size() == 0) {
mQueueMsg.push(array);
writeMessageWithQueue(array);
}
else
mQueueMsg.push(array);
}
void SSLHandler::setRequestMsg(std::vector<char> &&array) { requestMsg = std::move(array); }
void SSLHandler::setIsDestructing(bool value) {
LOG_INFO << "ssl connection destructing set as " << value;
isDestructing = value;
if (isDestructing == true) {
if (isConnectionOk) {
socket_.lowest_layer().cancel();
// socket_.shutdown();
LOG_INFO << "ssl connection destructing get pass shutdown";
}
}
}
int main() {
Connector c;
boost::asio::io_service svc;
boost::asio::ssl::context ctx(boost::asio::ssl::context_base::sslv23_client);
SSLHandler h(svc, ctx, boost::asio::ip::tcp::resolver{svc}.resolve({{}, 6767}), &c);
h.setRequestMsg({'h','e','l','l','o','\n','w','o','r','l','d'});
h.connectToServer();
svc.run();
}
Now, I didn't see any issue, regardless of how long it took for the server to be back after interruption.
You mention
Hi, I mean user actively call disconnect function (not connection dropped by network like last time). If the user call disconnect() then wait more than 10 secs, then call connect() the connection is failed with error: connection timed out. – Patton 11 hours ago
There's no such disconnect() function in your code, nor can I see how it's likely implemented. Therefore, either
the problem is on the server-side (which stops accepting connections or completing SSL handshake?)
the problem is in the code you don't show
Could someone help me with folowing questions?
I'm trying to call async_send within the while loop. The data is sent to server correctly but onSend handler is not called at all... If I do not use the while loop all works fine (the data is sent and received, all handlers ae called)
Will my code work correctly if we send some msgs before server's answer on previous msgs?
Here is the TCP client code
class TCPClient
{
public:
static const size_t maxBufLen = 100;
static const size_t MAX_INPUT_SIZE = 10;
TCPClient(boost::asio::io_service& IO_Service, tcp::resolver::iterator EndPointIter);
void close();
private:
boost::asio::io_service& m_IOService;
tcp::socket m_Socket;
char recieveBuffer[maxBufLen];
void promptTxMsgLoop();
void onConnect(const boost::system::error_code& ErrorCode, tcp::resolver::iterator EndPointIter);
void onReceive(const boost::system::error_code& ErrorCode);
void onSend(const boost::system::error_code& ErrorCode);
void doClose();
};
TCPClient::TCPClient(boost::asio::io_service& IO_Service, tcp::resolver::iterator EndPointIter)
: m_IOService(IO_Service), m_Socket(IO_Service)
{
tcp::endpoint EndPoint = *EndPointIter;
recieveBuffer[0] = '\0';
m_Socket.async_connect(EndPoint,
boost::bind(&TCPClient::onConnect, this, boost::asio::placeholders::error, ++EndPointIter));
}
void TCPClient::onConnect(const boost::system::error_code& ErrorCode, tcp::resolver::iterator EndPointIter)
{
if (ErrorCode == 0)
{
this->promptTxMsgLoop();
}
else if (EndPointIter != tcp::resolver::iterator())
{
cout << "m_Socket.close();!" << endl;
m_Socket.close();
tcp::endpoint EndPoint = *EndPointIter;
m_Socket.async_connect(EndPoint,
boost::bind(&TCPClient::onConnect, this, boost::asio::placeholders::error, ++EndPointIter));
}
}
void TCPClient::promptTxMsgLoop()
{
recieveBuffer[0] = '\0';
while (true)
{
cout << "> " ;
string tmp;
cin >> tmp;
cout << "Entered: " << tmp << endl;
tmp += "\0";
if (tmp.length() < MAX_INPUT_SIZE-1)
{
try
{
//lock untill buffer is emty
while (strlen(recieveBuffer) > 1)
{
}
//onSend handler is never is called inside while loop
m_Socket.async_send(boost::asio::buffer(tmp.c_str(),tmp.length()+1),
boost::bind(&TCPClient::onSend, this, boost::asio::placeholders::error));
}
catch(exception &e)
{
cerr << "Cannot add msg to send queue... " << e.what() << endl;
}
}
else
cout << "Error: input string is too long. Max length is " << MAX_INPUT_SIZE-1 << endl;
}
}
void TCPClient::onSend(const boost::system::error_code& ErrorCode)
{
cout << "Msg has been sent..." << endl;
if (strlen(recieveBuffer) > 1)
cout << "ERROR: recieveBuffer in not epmty. Data is overritten!" << endl;
if (!ErrorCode)
{
m_Socket.async_receive(boost::asio::buffer(recieveBuffer, TCPClient::maxBufLen),
boost::bind(&TCPClient::onReceive, this, boost::asio::placeholders::error));
}
else
{
cout << "onSend closing" << endl;
cout << "ERROR! onSend..." << ErrorCode << endl;
doClose();
}
}
void TCPClient::onReceive(const boost::system::error_code& ErrorCode)
{
cout << "Msg has been received..." << endl;
if (ErrorCode == 0)
{
cout << recieveBuffer << endl;
cout << "msg length: " << strlen(recieveBuffer) << endl;
//unlock buffer
recieveBuffer[0] = '\0';
}
else
{
cout << "ERROR! onReceive..." << ErrorCode << endl;
doClose();
}
}
void TCPClient::doClose()
{
m_Socket.close();
}
int main()
{
try
{
boost::asio::io_service IO_Service;
tcp::resolver Resolver(IO_Service);
tcp::resolver::query Query("127.0.0.1", "1");
tcp::resolver::iterator EndPointIterator = Resolver.resolve(Query);
TCPClient Client(IO_Service, EndPointIterator);
boost::thread ClientThread(boost::bind(&boost::asio::io_service::run, &IO_Service));
ClientThread.join();
Client.close();
}
catch (exception& e)
{
cerr << e.what() << endl;
}
cout << "\nClosing";
getch();
}
In 'onConnect' completion-handler you call promptTxMsgLoop that performs an infinite while loop, so you actually never let io_service to continue its work -- thus no completion handlers will be invoked anymore.
Besides, you call async_send multiple times, without waiting for the comletion handler of the previous async_send, which is also incorrect.
Please, see asio documentation to find out correct use patterns.
My idea was to create X threads, run it using KeepRunning method which has endless loop calling _io_service.run() and send tasks to _io_service when received a new connection using _io_service.poll() in async_accept handler.
I run the server with a code like this:
oh::msg::OHServer s("0.0.0.0", "9999", 200);
ConsoleStopServer = boost::bind(&oh::msg::OHServer::Stop, &s);
SetConsoleCtrlHandler(bConsoleHandler, TRUE);
s.Run();
but when I receive one connection, then serve it in Post() method using blocking read/writes in MsgWorker class, then all the threads are being closed.
I have code like below (it's some mix from http server3 asio example and mine):
OHServer::OHServer(const std::string& sAddress, const std::string& sPort, std::size_t tps)
: _nThreadPoolSize(tps), _acceptor(_io_service), _sockClient(new boost::asio::ip::tcp::socket(_io_service))
{
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(_io_service);
boost::asio::ip::tcp::resolver::query query(sAddress, sPort);
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
_acceptor.open(endpoint.protocol());
_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
_acceptor.bind(endpoint);
_acceptor.listen();
_acceptor.async_accept(
*_sockClient,
boost::bind(
&OHServer::AcceptConnection,
this,
boost::asio::placeholders::error
)
);
}
void OHServer::KeepRunning()
{
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Thread Start" << std::endl;
global_stream_lock.unlock();
while( true )
{
try
{
boost::system::error_code ec;
_io_service.run( ec );
if( ec )
{
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Error: " << ec << std::endl;
global_stream_lock.unlock();
}
break;
}
catch( std::exception & ex )
{
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Exception: " << ex.what() << std::endl;
global_stream_lock.unlock();
}
}
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Thread Finish" << std::endl;
global_stream_lock.unlock();
}
void OHServer::Run()
{
// Create a pool of threads to run all of the io_services.
for (std::size_t i = 0; i < _nThreadPoolSize; ++i)
{
boost::shared_ptr<boost::thread> thread(new boost::thread(
boost::bind(&OHServer::KeepRunning, this)));
threads.push_back(thread);
}
cout << "Hit enter to close server" << endl;
cin.get();
}
void OHServer::Stop()
{
boost::system::error_code ec;
_acceptor.close(ec);
_sockClient->shutdown( boost::asio::ip::tcp::socket::shutdown_both, ec );
_sockClient->close( ec );
_io_service.stop();
// Wait for all threads in the pool to exit.
for (std::size_t i = 0; i < threads.size(); ++i)
{
threads[i]->join();
cout << "threads[ "<< i << "]->join();" << endl;
}
}
void OHServer::Post()
{
std::cout << "Accepted new connection." << std::endl;
CMsgWorker *msgWorker = new CMsgWorker(_sockClient);
msgWorker->Start();
delete msgWorker;
}
void OHServer::AcceptConnection(const boost::system::error_code& e)
{
if (!e)
{
_io_service.post(boost::bind(&OHServer::Post, this));
_acceptor.async_accept(
*_sockClient,
boost::bind(
&OHServer::AcceptConnection,
this,
boost::asio::placeholders::error
)
);
}
}
What should I do for the threads to be still waiting for some work to do from _io_service?
Thanks for any help!
Check it out:
// Kick off 5 threads
for (size_t i = 0; i < 5; ++i) {
boost::thread* t = threads.create_thread(boost::bind(&boost::asio::io_service::run, &io));
std::cout << "Creating thread " << i << " with id " << t->get_id() << std::endl;
}
See the timer.cc example here for an idea on how to do this: https://github.com/sean-/Boost.Examples/tree/master/asio/timer
Finally I've ended up with some easy-to-use version of server:
Usage:
boost::shared_ptr<CTCPServer> _serverPtr;
void CMyServer::Start()
{
//First we must create a few threads
thread* t = 0;
for (int i = 0; i < COHConfig::_iThreads; ++i)
{
t =_threads.create_thread(bind(&io_service::run, &_io_service));
}
//Then we create a server object
_serverPtr.reset( new CTCPServer(&_io_service, PORT_NUMBER) );
//And finally run the server through io_service
_io_service.post(boost::bind(&CMyServer::RunServer, _serverPtr, &CMyServer::HandleMessage));
}
//This is the function which is called by io_service to start our server
void CMyServer::RunServer(CTCPServer* s, void (*HandleFunction)(shared_ptr<ip::tcp::socket>, deadline_timer*))
{
s->Run(HandleFunction);
}
//And this is our connection handler
void CMyServer::HandleMessage(shared_ptr< ip::tcp::socket > sockClient, deadline_timer* timer)
{
cout << "Handling connection from: " << sockClient->remote_endpoint().address().to_string() << ":" << sockClient->remote_endpoint().port() << endl;
//This is some class which gets socket in its constructor and handles the connection
scoped_ptr<CMyWorker> myWorker( new CMyWorker(sockClient) );
msgWorker->Start();
}
//Thanks to this function we can stop our server
void CMyServer::Stop()
{
_serverPtr->Stop();
}
The TCPServer.hpp file:
#ifndef TCPSERVER_HPP
#define TCPSERVER_HPP
#if defined(_WIN32)
#define BOOST_THREAD_USE_LIB
#endif
#include <boost/asio.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <string>
#include <vector>
class CTCPServer: private boost::noncopyable
{
private:
bool bKeepRunning;
boost::asio::io_service* _io_service;
std::string _sPort;
boost::asio::ip::tcp::acceptor _acceptor;
boost::shared_ptr< boost::asio::ip::tcp::socket > _sockClient;
boost::asio::deadline_timer _timer;
bool _bIPv6;
std::string SessionID();
public:
CTCPServer(boost::asio::io_service* ios, const std::string& sPort, bool bIPv6=false):
_sPort(sPort),
_acceptor(*ios),
_timer(*ios),
_bIPv6(bIPv6)
{
_io_service = ios;
bKeepRunning = false;
};
void Run(void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket > sock, boost::asio::deadline_timer* timer));
void AsyncAccept(void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket > , boost::asio::deadline_timer* ));
void AcceptHandler(const boost::system::error_code& e, void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket >, boost::asio::deadline_timer* ));
void Stop();
void Stop(void (*StopFunction)());
};
#endif
The TCPServer.cpp file:
#include "TCPServer.hpp"
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>
using namespace std;
string CTCPServer::SessionID()
{
ostringstream outs;
outs << "[" << boost::this_thread::get_id() << "] ";
return outs.str();
}
void CTCPServer::Run(void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket > , boost::asio::deadline_timer* ))
{
try
{
boost::asio::ip::tcp::resolver resolver(*_io_service);
boost::asio::ip::tcp::endpoint endpoint;
if(_bIPv6)
{
boost::asio::ip::tcp::resolver::query queryv6(boost::asio::ip::tcp::v6(), _sPort);
endpoint = *resolver.resolve(queryv6);
}
else
{
boost::asio::ip::tcp::resolver::query queryv4(boost::asio::ip::tcp::v4(), _sPort);
endpoint = *resolver.resolve(queryv4);
}
_acceptor.open(endpoint.protocol());
_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
_acceptor.set_option(boost::asio::socket_base::enable_connection_aborted(true));
_acceptor.bind(endpoint);
_acceptor.listen();
boost::system::error_code ec;
bKeepRunning = true;
AsyncAccept(HandleFunction);
}
catch(std::exception& e)
{
if(!_bIPv6)
std::cerr << "Exception wile creating IPv4 TCP socket on port "<< _sPort<< ": " << e.what() << std::endl;
else
std::cerr << "Exception wile creating IPv6 TCP socket on port "<< _sPort<< ": " << e.what() << std::endl;
}
}
void CTCPServer::AsyncAccept(void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket > , boost::asio::deadline_timer* ))
{
if(bKeepRunning)
{
try
{
_sockClient.reset(new boost::asio::ip::tcp::socket(*_io_service));
cout << SessionID() << "Waiting for connection on port: " << _sPort << endl;
_acceptor.async_accept(*_sockClient, boost::bind(&CTCPServer::AcceptHandler, this, boost::asio::placeholders::error, HandleFunction));
}
catch(exception& e)
{
string sWhat = e.what();
cout << SessionID() << "Error while accepting connection: " << e.what() << endl;
}
}
}
void CTCPServer::AcceptHandler(const boost::system::error_code& e,
void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket >,
boost::asio::deadline_timer* ))
{
if(!e)
{
try
{
(*_io_service).post(boost::bind(HandleFunction, _sockClient, &_timer));
AsyncAccept(HandleFunction);
}
catch(exception& e)
{
cout << SessionID() << "Exception: " << e.what() << endl;
}
}
}
void CTCPServer::Stop()
{
cout << SessionID() << "STOP port " << _sPort << endl;
if(!bKeepRunning)
return;
bKeepRunning = false;
try
{
_sockClient->close();
}
catch(exception& e)
{
cout << SessionID() << "Exception: " << e.what() << endl;
}
try
{
_acceptor.cancel();
}
catch(exception& e)
{
cout << SessionID() << "Exception: " << e.what() << endl;
}
try
{
_acceptor.close();
}
catch(exception& e)
{
cout << SessionID() << "Exception: " << e.what() << endl;
}
}
void CTCPServer::Stop(void (*StopFunction)())
{
Stop();
StopFunction();
}
It's also very easy to modify to be IPv6 compatible.
It's already tested and working very well. Just copy it and use!