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
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
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();
}
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!