I am using boost::asio::deadline_timer to add socket timeout option. I have implemented Asynchronous HTTP read, and I start the deadline_timer when I start connecting with the server and on every callback I reset the deadline_timer with function deadline_timer::expires_from_now. In the error handler of the deadline_timer I am clearly checking if the timeout was actual or operation_aborted. But almost always I receive actual timeout even before expected timeout. Please have a look at my given code. I don't understand in every callback I am resetting the timer then why I am getting this timeout error.
#define TCP_SOCKET_TIMEOUT 10
Http::AsyncDownload::AsyncDownload(boost::asio::io_service& io_service,
const std::string &protocol,
const std::string &serverip,
const std::string &port,
const std::string &path,
const std::string &filename,
const std::string &localFilePath,
const std::string &username,
const std::string &password) :
resolver_(io_service),
socket_(io_service),
timer_(io_service, boost::posix_time::seconds(TCP_SOCKET_TIMEOUT)),
localFilePath_(localFilePath),
downloadFile_(filename),
protocol_(protocol)
{
........
// Start TCP Socket Timer
start_socket_timer();
// Start an asynchronous resolve to translate the server and service names
// into a list of endpoints.
tcp::resolver::query query(serverip, port);
resolver_.async_resolve(query, boost::bind(&AsyncDownload::resolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator)
);
}
void Http::AsyncDownload::resolve(const boost::system::error_code &err,
tcp::resolver::iterator endpoint_iterator)
{
// Ok, we have received one packet, so refresh the socket timer
refresh_socket_timer();
if ( !err ) {
.........
boost::asio::async_connect(ssocket_->lowest_layer(), endpoint_iterator, boost::bind(&AsyncDownload::connect, this, boost::asio::placeholders::error));
} else {
// Error handling here
}
}
void Http::AsyncDownload::connect(const boost::system::error_code& err)
{
// Ok, we have received one packet, so refresh the socket timer
refresh_socket_timer();
if ( !err ) {
.........
boost::asio::async_write(socket_, request_,
boost::bind(&AsyncDownload::write_request, this, boost::asio::placeholders::error));
}
else {
// Error handling here
}
}
void Http::AsyncDownload::hand_shake(const boost::system::error_code& err)
{
// Ok, we have received one packet, so refresh the socket timer
refresh_socket_timer();
if ( !err ) {
.........
boost::asio::async_write(*ssocket_, request_,
boost::bind(&AsyncDownload::write_request, this,
boost::asio::placeholders::error));
} else {
// Error handling here.
}
}
void Http::AsyncDownload::write_request(const boost::system::error_code& err)
{
// Ok, we have received one packet, so refresh the socket timer
refresh_socket_timer();
if ( !err ) {
.............
boost::asio::async_read_until(*ssocket_, response_, "\r\n",
boost::bind(&AsyncDownload::read_status_line, this,
boost::asio::placeholders::error));
} else {
// Error handling here
}
}
void Http::AsyncDownload::read_status_line(const boost::system::error_code& err)
{
// Ok, we have received one packet, so refresh the socket timer
refresh_socket_timer();
if ( !err ) {
..........
boost::asio::async_read_until(*ssocket_, response_, "\r\n\r\n",
boost::bind(&AsyncDownload::read_headers, this,
boost::asio::placeholders::error));
} else {
// Error handling here.
}
}
void Http::AsyncDownload::read_headers(const boost::system::error_code& err)
{
refresh_socket_timer();
if ( !err ) {
..............
boost::asio::async_read(*ssocket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&AsyncDownload::read_content, this,
boost::asio::placeholders::error)
);
} else {
// Error handling here
}
}
void Http::AsyncDownload::read_content(const boost::system::error_code& err)
{
// Ok, we have received one packet, so refresh the socket timer
refresh_socket_timer();
if ( !err ) {
boost::asio::async_read(*ssocket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&AsyncDownload::read_content, this,
boost::asio::placeholders::error)
);
} else if ( err != boost::asio::error::eof ) {
// Error handling here.
} else {
// We have an EOF
}
}
void Http::AsyncDownload::start_socket_timer()
{
timer_.async_wait(boost::bind(&Http::AsyncDownload::socket_timeout, this,
boost::asio::placeholders::error));
}
void Http::AsyncDownload::refresh_socket_timer()
{
timer_.expires_from_now(boost::posix_time::seconds(TCP_SOCKET_TIMEOUT));
timer_.async_wait(boost::bind(&Http::AsyncDownload::socket_timeout, this, boost::asio::placeholders::error));
}
void Http::AsyncDownload::socket_timeout(const boost::system::error_code &error_)
{
// operation_aborted error is thrown whenever we cancel the timer or
// we reset the timer using expires_from_now function,
if ( error_ != boost::asio::error::operation_aborted ) {
csputils::Utils::DEBUG_MSG(downloadFile_, __LINE__, " ------------> TCP Connection Timeout. Broken Connection Found. Abort Operation ");
// Ok, our TCP connection is broken, we will cancel all asynchronous
// operations of our sockets.
ssocket_->shutdown(); // For Secure Socket & socket.close(); for normal socket.
} else {
// Ok, we have reset the timer, please continue...
}
}
Ok. In the above code you will notice I am starting the timer in the constructor, and once I receive one packet I am refreshing the timer with expries_from_now function call. This call will call error handler (socket_timeout) with the error code operation_aborted, but for every actual timeout this function will be called without operation_aborted and you can see I am checking operation_aborted explicitly, but still as per my expectation I am receiving timeout early, though I am refreshing the timer on every packet I receive but I am sure it is being expired before 10 seconds. I also tried with timeout value = 60 but same effect. Any thoughts.
UPDATE
Updated my code with error handling I have used in my actual code. I have trimmed down my actual code for the sake of simplicity. You can notice in timer timeout handler, I am checking if the timeout was not explicit (i.e operation_aborted), then close the socket. Once the socket will be closed I will get an exception in my socket data handler (mostly read_content function). In that function when I receive exception my socket will exit calling the AsyncDownload destructor where I am doing some more cleaning. My download code works perfect if I remove deadline_timer. I have added it here to detect unforeseen TCP connection drops. I am running this code on embedded Linux on ARM architecture, and my sockets are secure but as I have mentioned my code works perfect without timer so I don't think the problem has something to do with sockets.
Okay, so, I've played with your example hands on.
I can see an infinite loop happening when the timeout has expired, because there is a lack of error handling, and the read_content loop simply continues despite the fact that the timeout had been reached.
Note:
the first read_until_async suggests that it will only read the statusline. This is simply not the case!
See boost read_until does not stop at delimiter.
It will read the first "swoop" of data that includes a minimum of \r\n once. In practice, many servers send the headers in the same packet. (In fact, effective behaviour might depend on proxies and possibly hardware). So, it's not safe to assume that you should always require the second read_until_async to read the headers. And since another "\r\n\r\n" might never happen, it's easy to get into failure mode (when e.g. end-of-stream is reached).
If you don't carefully watch the flow, it's possible to conclude that "the timer fires too early" (because you land in refresh_socket_timer, for example). However, what is happening in my error situation is that read_until_async simply immediately returns, which is erronously treated as if a packet was returned.
So I propose something like
void read_content(const boost::system::error_code& err)
{
if (err && socket_.is_open())
{
DEBUG_TRACE();
boost::asio::async_read(*ssocket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&AsyncDownload::read_content, this,
boost::asio::placeholders::error)
);
refresh_socket_timer();
}
else
std::cerr << "Error '" << err.message() << "'\n";
}
So we avoid the infinite loop on connection dropped.
void refresh_socket_timer()
{
if (socket_.is_open())
{
DEBUG_TRACE();
timer_.expires_from_now(boost::posix_time::seconds(TCP_SOCKET_TIMEOUT));
timer_.async_wait(boost::bind(&AsyncDownload::socket_timeout, this, boost::asio::placeholders::error));
}
}
So we avoid refreshing the timer after the socket has been closed.
void socket_timeout(const boost::system::error_code &error_)
{
// operation_aborted error is thrown whenever we cancel the timer or
// we reset the timer using expires_from_now function,
if ( error_ != boost::asio::error::operation_aborted ) {
DEBUG_TRACE();
std::cout << " ------------> TCP Connection Timeout. Broken Connection Found. Abort Operation\n";
// Ok, our TCP connection is broken, we will cancel all asynchronous
// operations of our sockets.
socket_.close();
}
}
So we actively close the socket on timeout.
If you comment the if(...) conditionals out in the above, you'd see the failure mode I was describing.
Here's the full example I used to test:
#include <boost/asio.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/bind.hpp>
using tcp = boost::asio::ip::tcp;
#define TCP_SOCKET_TIMEOUT 2
#define DEBUG_TRACE() do { std::cout << __FILE__ << ':' << __LINE__ << "\t" << __FUNCTION__ << "\n"; } while(false)
struct AsyncDownload
{
tcp::resolver resolver_;
tcp::socket socket_;
tcp::socket* ssocket_ = &socket_;
boost::asio::deadline_timer timer_;
std::string localFilePath_;
boost::asio::streambuf response_;
AsyncDownload(boost::asio::io_service& io_service,
const std::string &protocol,
const std::string &serverip,
const std::string &port) :
resolver_(io_service),
socket_(io_service),
timer_(io_service, boost::posix_time::seconds(TCP_SOCKET_TIMEOUT))
{
DEBUG_TRACE();
// Start TCP Socket Timer
start_socket_timer();
// Start an asynchronous resolve to translate the server and service names
// into a list of endpoints.
tcp::resolver::query query(serverip, port);
resolver_.async_resolve(query, boost::bind(&AsyncDownload::resolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator)
);
}
void resolve(const boost::system::error_code &err, tcp::resolver::iterator endpoint_iterator)
{
DEBUG_TRACE();
boost::asio::async_connect(ssocket_->lowest_layer(), endpoint_iterator, boost::bind(&AsyncDownload::connect, this, boost::asio::placeholders::error));
refresh_socket_timer();
}
void connect(const boost::system::error_code& err)
{
DEBUG_TRACE();
std::string const request_ = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n";
boost::asio::async_write(socket_, boost::asio::buffer(request_),
boost::bind(&AsyncDownload::write_request, this, boost::asio::placeholders::error));
refresh_socket_timer();
}
void write_request(const boost::system::error_code& err)
{
DEBUG_TRACE();
boost::asio::async_read_until(*ssocket_, response_, "\r\n",
boost::bind(&AsyncDownload::read_status_line, this,
boost::asio::placeholders::error));
refresh_socket_timer();
}
void read_status_line(const boost::system::error_code& err)
{
DEBUG_TRACE();
std::cout << "read_status_line: " << &response_ << "\n";
boost::asio::async_read_until(*ssocket_, response_, "\r\n\r\n",
boost::bind(&AsyncDownload::read_headers, this,
boost::asio::placeholders::error));
refresh_socket_timer();
}
void read_headers(const boost::system::error_code& err)
{
DEBUG_TRACE();
// ..............
boost::asio::async_read(*ssocket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&AsyncDownload::read_content, this,
boost::asio::placeholders::error)
);
refresh_socket_timer();
}
void read_content(const boost::system::error_code& err)
{
if (err && socket_.is_open())
{
DEBUG_TRACE();
boost::asio::async_read(*ssocket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&AsyncDownload::read_content, this,
boost::asio::placeholders::error)
);
refresh_socket_timer();
}
else
std::cerr << "Error '" << err.message() << "'\n";
}
void start_socket_timer()
{
DEBUG_TRACE();
timer_.async_wait(boost::bind(&AsyncDownload::socket_timeout, this, boost::asio::placeholders::error));
}
void refresh_socket_timer()
{
if (socket_.is_open())
{
DEBUG_TRACE();
timer_.expires_from_now(boost::posix_time::seconds(TCP_SOCKET_TIMEOUT));
timer_.async_wait(boost::bind(&AsyncDownload::socket_timeout, this, boost::asio::placeholders::error));
}
}
void socket_timeout(const boost::system::error_code &error_)
{
// operation_aborted error is thrown whenever we cancel the timer or
// we reset the timer using expires_from_now function,
if ( error_ != boost::asio::error::operation_aborted ) {
DEBUG_TRACE();
std::cout << " ------------> TCP Connection Timeout. Broken Connection Found. Abort Operation\n";
// Ok, our TCP connection is broken, we will cancel all asynchronous
// operations of our sockets.
socket_.close();
}
}
};
int main()
{
DEBUG_TRACE();
boost::asio::io_service io_service;
AsyncDownload download(
io_service,
"http",
"www.google.com",
"80");
io_service.run();
}
Related
I have copied the chat-example from the boost examples.
server code:
https://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/example/chat/chat_server.cpp
chat_message.hpp: https://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/example/chat/chat_message.hpp
client code: https://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/example/chat/chat_client.cpp
In chat_server.cpp, I have added a cout in chat_room::leave to get notified when a client leaves. In chat_server, I tried to configure TCP keepalive according to this SO answer:
// additional includes that I have added
#include <winsock2.h>
#include <ws2tcpip.h>
#include <ws2spi.h>
#include <mstcpip.h>
#include <windows.h>
// ----
class chat_server {
public:
chat_server(boost::asio::io_service& io_service,
const tcp::endpoint& endpoint)
: acceptor_(io_service, endpoint),
socket_(io_service) {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(socket_,
[this](boost::system::error_code ec) {
if(!ec) {
unsigned long val = 1;
int res = setsockopt(socket_.native_handle(), SOL_SOCKET, SO_KEEPALIVE, (char*) &val, sizeof val);
if(res) std::cout << "Can't set sockopt!" << std::endl;
tcp_keepalive alive;
alive.onoff = TRUE;
alive.keepalivetime = 1000;
alive.keepaliveinterval = 1000;
DWORD bytes_ret = 0;
res = WSAIoctl(socket_.native_handle(), SIO_KEEPALIVE_VALS, &alive, sizeof(alive), NULL, 0,
&bytes_ret, NULL, NULL);
if(res) std::cout << "Can't set TCP keepalive!" << std::endl;
std::make_shared<chat_session>(std::move(socket_), room_)->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
chat_room room_;
};
My test setup is to run the server on my local machine. The client runs on a "VMware Workstation 15 Player"-virtual machine, where NAT is used as network adapter. When I connect to the server and uncheck the "Connected"-checkbox in Virtual Machine Settings (should be the same as unplugging the network cable), I would expect to see the output of chat_room::leave in the console, but it seems, that the server even after a few minutes thinks that the client is still connected.
How can I configure the TCP keepalive appropriately? (I would prefer a cross-platform solution which works on Linux too, but if it works on windows, it's better than nothing)
Edit: I have monitored the sent data with Wireshark and found out that no keep-alive packets were sent. What could cause this?
See EDIT1 at the end of the question for a possible solution - It would be great if somebody could comment on my interpretation, so that I can understand better what's happening
I'm writing a simple TCP client, based on QTcpSocket and managed by a QStateMachine (connect to server -> transmit data -> if disconnected for any reason, reconnect to server).
I noticed that if the connection is shut down on the server side (client is notified with RemoteHostClosedError), after reconnection the QTcpSocket write() method succeeds but no data is transmitted on the wire - nothing is received by the server, and the bytesWritten() signal on the client side does not fire up.
I found in the documentation for error() signal (https://doc.qt.io/qt-5/qabstractsocket.html#error) that
When this signal is emitted, the socket may not be ready for a reconnect attempt. In that case,
attempts to reconnect should be done from the event loop".
I think I'm already ok with that, as the reconnection happens in one of the QStateMachine states, and QStateMachine should have its own event loop according to the QT docs.
Below some simplified code to reproduce the issue (sorry, not so minimal but I could not find a simpler way to show the problem):
testclient.h
#ifndef TESTCLIENT_H
#define TESTCLIENT_H
#include <QObject>
#include <QTcpSocket>
#include <QDebug>
#include <QStateMachine>
class TestClient : public QObject
{
Q_OBJECT
public:
explicit TestClient(QObject *parent = nullptr);
public slots:
void start();
signals:
// FSM events
void fsmEvtConnected();
void fsmEvtError();
private slots:
void onSocketConnected(); // Notify connection to TCP server
void onSocketDisconnected(); // Notify disconnection from TCP server
void onSocketBytesWritten(qint64 bytes); // Notify number of bytes written to TCP server
void onSocketError(QAbstractSocket::SocketError err);
// FSM state enter/exit actions
void onfsmConnectEntered();
void onfsmTransmitEntered();
void onfsmTransmitExited();
private:
// Member variables
QTcpSocket* m_socket; // TCP socket used for communications to server
QStateMachine* m_clientFsm; // FSM defining general client behaviour
private:
void createClientFsm(); // Create client FSM
};
#endif // TESTCLIENT_H
testclient.cpp
#include "testclient.h"
#include <QState>
#include <QThread> // Sleep
//-----------------------------------------------------------------------------
// PUBLIC METHODS
//-----------------------------------------------------------------------------
TestClient::TestClient(QObject *parent) : QObject(parent)
{
m_socket = new QTcpSocket(this);
connect(m_socket, SIGNAL(connected()),this, SLOT(onSocketConnected()));
connect(m_socket, SIGNAL(disconnected()),this, SLOT(onSocketDisconnected()));
connect(m_socket, SIGNAL(bytesWritten(qint64)),this, SLOT(onSocketBytesWritten(qint64)));
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
}
void TestClient::start()
{
createClientFsm();
m_clientFsm->start();
}
//-----------------------------------------------------------------------------
// TCP CONNECTION MANAGEMENT SLOTS
//-----------------------------------------------------------------------------
void TestClient::onSocketConnected()
{
qDebug() << "connected...";
emit fsmEvtConnected();
}
void TestClient::onSocketDisconnected()
{
qDebug() << "disconnected...";
emit fsmEvtError();
}
void TestClient::onSocketBytesWritten(qint64 bytes)
{
qDebug() << bytes << " bytes written...";
}
void TestClient::onSocketError(QAbstractSocket::SocketError err)
{
qDebug() << "socket error " << err;
}
//-----------------------------------------------------------------------------
// FSM MANAGEMENT
//-----------------------------------------------------------------------------
void TestClient::createClientFsm()
{
m_clientFsm = new QStateMachine(this);
// Create states
QState* sConnect = new QState();
QState* sTransmit = new QState();
// Add transitions between states
sConnect->addTransition(this, SIGNAL(fsmEvtConnected()), sTransmit);
sTransmit->addTransition(this, SIGNAL(fsmEvtError()), sConnect);
// Add entry actions to states
connect(sConnect, SIGNAL(entered()), this, SLOT(onfsmConnectEntered()));
connect(sTransmit, SIGNAL(entered()), this, SLOT(onfsmTransmitEntered()));
// Add exit actions to states
connect(sTransmit, SIGNAL(exited()), this, SLOT(onfsmTransmitExited()));
// Create state machine
m_clientFsm->addState(sConnect);
m_clientFsm->addState(sTransmit);
m_clientFsm->setInitialState(sConnect);
}
void TestClient::onfsmConnectEntered()
{
qDebug() << "connecting...";
m_socket->connectToHost("localhost", 11000);
// Wait for connection result
if(!m_socket->waitForConnected(10000))
{
qDebug() << "Error: " << m_socket->errorString();
emit fsmEvtError();
}
}
void TestClient::onfsmTransmitEntered()
{
qDebug() << "sending data...";
m_socket->write("TEST MESSAGE");
}
void TestClient::onfsmTransmitExited()
{
qDebug() << "waiting before reconnection attempt...";
QThread::sleep(2);
}
main.cpp
#include <QCoreApplication>
#include "testclient.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
TestClient client(&a);
client.start();
return a.exec();
}
To test, you can just launch netcat (nc -l -p 11000) , then close the nc process after receiving TEST MESSAGE and finally relaunch it again. The second time, TEST MESSAGE is not received, and we don't have the onSocketBytesWritten() printout, see below:
connecting...
connected...
sending data...
12 bytes written... <<<<<<<<<< Correct transmission, event fires up
socket error QAbstractSocket::RemoteHostClosedError
disconnected...
waiting before reconnection attempt...
connecting...
connected...
sending data... <<<<<<<<<< No transmission, event does not fire up, no socket errors!
EDIT1: I found out that if I create the QTcpSocket on connection and destroy it on disconnection, the problem does not happen. Is this the expected/proper way to use sockets?
Wouldn't it be possible instead to create the socket just once and just connect/disconnect? Maybe it is just a matter of flushing or cleaning up in a specific manner, but I could not find it so far.
Here are the modifications that make the code above work on server-side disconnection:
Move socket creation from class constructor to onfsmConnectEntered() - handler for entry in the "Connect" QState:
void TestClient::onfsmConnectEntered()
{
m_socket = new QTcpSocket(this);
connect(m_socket, SIGNAL(connected()),this, SLOT(onSocketConnected()));
connect(m_socket, SIGNAL(disconnected()),this, SLOT(onSocketDisconnected()));
connect(m_socket, SIGNAL(bytesWritten(qint64)),this, SLOT(onSocketBytesWritten(qint64)));
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
qDebug() << "connecting...";
m_socket->connectToHost("localhost", 11000);
// The rest of the method is the same
}
Delete the socket on disconnection, so that it is deallocated and will be created again on reconnection:
void TestClient::onSocketDisconnected()
{
qDebug() << "disconnected...";
m_socket->deleteLater();
m_socket = nullptr;
emit fsmEvtError();
}
Do not use waitForX methods as they block the event loop and prevent them from using that resource as the signals do not do their job correctly or the QStateMachine.
Considering the above, the solution is:
void TestClient::onfsmConnectEntered()
{
m_socket->connectToHost("localhost", 11000);
}
But even so your code has errors since it does not consider other cases such as:
If when you start the client the server is not running, your application will try to connect the error will be launched and nothing else.
If the server fails for a longer time than the 10000 ms timeout set to waitForConnected(), the same will happen as in the previous case.
Then the idea is to try to connect until you are sure of the connection and that can be done through a QTimer with an appropriate period.
testclient.h
#ifndef TESTCLIENT_H
#define TESTCLIENT_H
#include <QObject>
class QTcpSocket;
class QStateMachine;
class QTimer;
#include <QAbstractSocket>
class TestClient : public QObject
{
Q_OBJECT
public:
explicit TestClient(QObject *parent = nullptr);
public slots:
void start();
signals:
// FSM events
void fsmEvtConnected();
void fsmEvtError();
private slots:
void onSocketConnected(); // Notify connection to TCP server
void onSocketDisconnected(); // Notify disconnection from TCP server
void onSocketBytesWritten(qint64 bytes); // Notify number of bytes written to TCP server
void onSocketError(QAbstractSocket::SocketError err);
// FSM state enter/exit actions
void onfsmConnectEntered();
void onfsmTransmitEntered();
private:
// Member variables
QTcpSocket* m_socket; // TCP socket used for communications to server
QStateMachine* m_clientFsm; // FSM defining general client behaviour
QTimer* m_timer;
private:
void createClientFsm(); // Create client FSM
void tryConnect();
};
#endif // TESTCLIENT_H
testclient.cpp
#include "testclient.h"
#include <QState>
#include <QStateMachine>
#include <QTcpSocket>
#include <QThread> // Sleep
#include <QTimer>
//-----------------------------------------------------------------------------
// PUBLIC METHODS
//-----------------------------------------------------------------------------
TestClient::TestClient(QObject *parent) : QObject(parent)
{
m_socket = new QTcpSocket(this);
m_timer = new QTimer(this);
m_timer->setInterval(100);
connect(m_timer, &QTimer::timeout, this, &TestClient::tryConnect);
connect(m_socket, &QAbstractSocket::connected,this, &TestClient::onSocketConnected);
connect(m_socket, &QAbstractSocket::disconnected,this, &TestClient::onSocketDisconnected);
connect(m_socket, &QIODevice::bytesWritten,this, &TestClient::onSocketBytesWritten);
connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this, &TestClient::onSocketError);
}
void TestClient::start()
{
createClientFsm();
m_clientFsm->start();
}
//-----------------------------------------------------------------------------
// TCP CONNECTION MANAGEMENT SLOTS
//-----------------------------------------------------------------------------
void TestClient::onSocketConnected()
{
m_timer->stop();
qDebug() << "connected...";
emit fsmEvtConnected();
}
void TestClient::onSocketDisconnected()
{
qDebug() << "disconnected...";
emit fsmEvtError();
}
void TestClient::onSocketBytesWritten(qint64 bytes)
{
qDebug() << bytes << " bytes written...";
}
void TestClient::onSocketError(QAbstractSocket::SocketError err)
{
qDebug() << "socket error " << err;
}
//-----------------------------------------------------------------------------
// FSM MANAGEMENT
//-----------------------------------------------------------------------------
void TestClient::createClientFsm()
{
m_clientFsm = new QStateMachine(this);
// Create states
QState* sConnect = new QState();
QState* sTransmit = new QState();
// Add transitions between states
sConnect->addTransition(this, SIGNAL(fsmEvtConnected()), sTransmit);
sTransmit->addTransition(this, SIGNAL(fsmEvtError()), sConnect);
// Add entry actions to states
connect(sConnect, &QAbstractState::entered, this, &TestClient::onfsmConnectEntered);
connect(sTransmit, &QAbstractState::entered, this, &TestClient::onfsmTransmitEntered);
// Create state machine
m_clientFsm->addState(sConnect);
m_clientFsm->addState(sTransmit);
m_clientFsm->setInitialState(sConnect);
}
void TestClient::tryConnect(){
m_socket->connectToHost("localhost", 11000);
}
void TestClient::onfsmConnectEntered()
{
m_timer->start();
}
void TestClient::onfsmTransmitEntered()
{
qDebug() << "sending data...";
m_socket->write("TEST MESSAGE");
}
I'm writing a multi-thread server using the boost::asio. There is a pool with X threads, using asynchronous reads and writes (base on this example).
The server structure seems like that:
Server
Manage the threads of program and start the async_accept creating new Session for each new client.
Session
Represents the client itself. Have the socket ref and manage the async_reads and async_writes from her socket(client). Also have the timeout management.
Sometimes the client (a hardware device) freezes and my server don't have answer from him. To solve this I read about using the async_wait with deadline_timer (like this example) and I applied it to my software, but something strange happened:
When a normal disconnection happens the async operations are cancelled (reaching a operation_aborted error) and the Session object is destroyed. But when the device freezes, the socket is closed but the Session object isn't destroyed and his instance remains in memory, even the socket.close() already called.
I simplified the code and put below:
server.h
class Server
{
private:
boost::asio::io_service& _io_service;
boost::asio::ip::tcp::acceptor* _acceptor;
boost::asio::ip::tcp::endpoint* _endpoint;
boost::asio::signal_set _signals;
Session_SP _session;
public:
Server(boost::asio::io_service& io_service);
~Server();
/**
* Queues async accept action.
*/
virtual void queueAccept();
/**
* Async accept handler.
*/
virtual void handleAccept(const boost::system::error_code& error);
/**
* Start the server
*/
virtual void run();
boost::asio::io_service& getIOService();
/**
* Shutdown the service
*/
virtual void shutdown();
};
server.cpp
#include "server.hpp"
Server::Server(boost::asio::io_service& io_service):
_io_service(io_service), _signals(io_service)
{
this->_endpoint = new boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), config.getServer().port);
this->_acceptor = new boost::asio::ip::tcp::acceptor(io_service);
this->_acceptor->open(boost::asio::ip::tcp::v4());
this->_acceptor->bind(*this->_endpoint);
this->_acceptor->listen();
this->_signals.add(SIGINT);
this->_signals.add(SIGTERM);
#if defined(SIGQUIT)
this->_signals_.add(SIGQUIT);
#endif // defined(SIGQUIT)
this->_signals.async_wait(boost::bind(&Server::shutdown, this));
this->queueAccept();
}
Server::~Server()
{
delete this->_acceptor;
delete this->_endpoint;
}
void Server::queueAccept()
{
this->_session.reset(new Session(*this));
_acceptor->async_accept(
this->_session->getSocket(),
boost::bind(
&Server::handleAccept,
this,
boost::asio::placeholders::error
)
);
}
void Server::handleAccept(const boost::system::error_code& error)
{
if (!error)
this->_session->start();
this->queueAccept();
}
boost::asio::io_service& Server::getIOService()
{
return this->_io_service;
}
void Server::shutdown()
{
this->_io_service.stop();
}
session.h
class Session:
public boost::enable_shared_from_this<Session>
{
public:
Session(Server& server);
~Session();
bool stopped() const;
virtual void start();
virtual boost::asio::ip::tcp::socket& getSocket();
virtual void disconnect();
/**
* Async read handler
*/
void handleRead(const boost::system::error_code& error, size_t bytesTransfered);
/**
* Async write handler
*/
void handleWrite(const boost::system::error_code& error);
/**
* Queues write action.
*/
void queueWrite();
/**
* Push a packet to be sent on queue end
*/
void pushPacket(protocols::SendPacket &packet);
void handleDeadlineAsyncWait(boost::asio::deadline_timer* deadline);
void handleDeadlineAsyncWaitKillConnection(boost::asio::deadline_timer* deadline);
private:
Server& _server;
boost::asio::ip::tcp::socket _socket;
boost::asio::io_service* _ioService;
boost::asio::io_service::strand _strand;
boost::asio::deadline_timer _input_deadline;
boost::asio::deadline_timer _non_empty_output_queue;
/**
* Queue that stores the packet to be sent.
*/
protocols::SendPacketQueue _writeQueue;
/**
* Referência do pacote que será atualizado.
*/
Protocol* _protocol;
/**
* Queues the async_read acction.
*/
virtual void queueRead();
virtual void _pushPacket(protocols::SendPacket &packet);
};
typedef boost::shared_ptr<Session> Session_SP;
session.cpp
#include "session.hpp"
Session::Session(Server& server):
_server(server), _socket(server.getIOService()), _protocol(NULL),
_ioService(&server.getIOService()), _strand(server.getIOService()),
_input_deadline(server.getIOService()),
_non_empty_output_queue(server.getIOService())
{
this->_input_deadline.expires_at(boost::posix_time::pos_infin);
this->_non_empty_output_queue.expires_at(boost::posix_time::pos_infin);
}
Session::~Session()
{
}
bool Session::stopped() const
{
return !_socket.is_open();
}
boost::asio::ip::tcp::socket& Session::getSocket()
{
return this->_socket;
}
void Session::disconnect()
{
this->_input_deadline.cancel();
this->_non_empty_output_queue.cancel();
try
{
this->getSocket().close();
LOG("Session::disconnect : close successful!");
}
catch (void* e)
{
// Never reached here!!
}
}
void Session::queueRead()
{
this->_input_deadline.expires_from_now(boost::posix_time::seconds(30));
boost::asio::async_read_until(
_socket,
_buffer,
"\x004", // Just a test
this->_strand.wrap(boost::bind(
&Session::handleRead,
this->shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
))
);
}
void Session::start()
{
this->queueRead();
this->_input_deadline.async_wait(
this->_strand.wrap(boost::bind(
&Session::handleDeadlineAsyncWait,
shared_from_this(),
&this->_input_deadline
))
);
this->queueWrite();
}
void Session::handleRead(const boost::system::error_code& error, size_t bytesTransfered)
{
if (this->stopped())
return;
if (!error)
{
// ... a lot of code here, but isn't important
}
else if (error != boost::asio::error::operation_aborted)
this->disconnect();
}
void Session::handleWrite(const boost::system::error_code& error)
{
if (this->stopped())
return;
if (!error)
{
this->_writeQueue.pop_front(); // Dequeue
this->queueWrite();
}
else
{
if (error != boost::asio::error::operation_aborted)
this->disconnect();
}
}
void Session::queueWrite()
{
if (this->stopped())
return;
if (this->_writeQueue.empty())
{
this->_non_empty_output_queue.expires_at(boost::posix_time::pos_infin);
this->_non_empty_output_queue.async_wait(
boost::bind(&Session::queueWrite, shared_from_this())
);
}
else
{
this->_input_deadline.expires_from_now(boost::posix_time::seconds(this->_server.getConfig().getServer().timeout));
boost::asio::async_write(
this->getSocket(),
boost::asio::buffer(
this->_writeQueue.front().getData(),
this->_writeQueue.front().getDataSize()
),
this->_strand.wrap(boost::bind(
&Session::handleWrite,
this,
boost::asio::placeholders::error
))
);
}
}
void Session::handleDeadlineAsyncWait(boost::asio::deadline_timer* deadline)
{
if (this->stopped())
return;
if (deadline->expires_at() <= boost::asio::deadline_timer::traits_type::now())
{
boost::system::error_code sdEc;
this->getSocket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, sdEc);
deadline->expires_from_now(boost::posix_time::seconds(15));
deadline->async_wait(
this->_strand.wrap(boost::bind(
&Session::handleDeadlineAsyncWaitKillConnection,
shared_from_this(),
deadline
))
);
}
else
{
deadline->async_wait(
this->_strand.wrap(boost::bind(
&Session::handleDeadlineAsyncWait,
shared_from_this(),
deadline
))
);
}
}
void Session::handleDeadlineAsyncWaitKillConnection(boost::asio::deadline_timer* deadline)
{
this->disconnect();
}
Your async_wait timeout handler should cancel the outstanding async_read() instead of just shutdown the socket, otherwise the socket will remain open.
void Session::handleDeadlineAsyncWait(boost::asio::deadline_timer* deadline)
{
if (this->stopped())
return;
if (deadline->expires_at() <= boost::asio::deadline_timer::traits_type::now())
{
boost::system::error_code sdEc;
this->getSocket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, sdEc);
this->getSocket().cancel(); // <-- add this
}
else
{
deadline->async_wait(
this->_strand.wrap(boost::bind(
&Session::handleDeadlineAsyncWait,
shared_from_this(),
deadline
))
);
}
}
additionally, in your Session::handleRead() handler, you should detect the boost::asio::error::operation_aborted error since that means the read was canceled.
I just found the problem.
The error is in the function Session::quereWrite.
session.cpp
void Session::queueWrite()
{
if (this->stopped())
return;
if (!this->_writeQueue.empty())
{
boost::asio::async_write(
this->getSocket(),
boost::asio::buffer(
this->_writeQueue.front().getData(),
this->_writeQueue.front().getDataSize()
),
this->_strand.wrap(boost::bind(
&Session::handleWrite,
this,
boost::asio::placeholders::error
))
);
}
}
I remove the _non_empty_output_queue and use another way to do the same thing, using the pushPack method.
The problem was that async_wait calls the queueWrite method and he called another async_wait to himself, but with a expired deadline timer causing a overhead on processor and preveting the Session self destruction.
Thank you #Sam Miller by your great contribution.
I want to send UDP packets to my static IP 122.***.***.*** where an UDP server listens on port 1213.
udp::resolver resolver(io_serviceSend);
udp::resolver::query query(udp::v4(),"122.***.***.***","1213");
udp::endpoint receiver_endpoint = *resolver.resolve(query);
udp::socket socket(io_serviceSend);
socket.open(udp::v4());
boost::shared_ptr<std::string> message(new std::string("Transfer"));
socket.send_to(boost::asio::buffer(*message), receiver_endpoint);
but this fails. Is it because i am sending it to the private port rather than sending it to the NAT's port. but for TCP it works perfectly.
Can some one explain me the theory or post a good link.
and my server code is
UDPTunnel::UDPTunnel(boost::asio::io_service& io_service)
:socket_(io_service, udp::endpoint(udp::v4(), 1213))
{
start_receive();
}
void UDPTunnel::start_receive()
{
socket_.async_receive_from(
boost::asio::buffer(recv_buffer_), remote_endpoint_,
boost::bind(&UDPTunnel::handle_receive, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
cout << "\nUDP Started To Listening";
}
void UDPTunnel::handle_receive(const boost::system::error_code& error,std::size_t)
{
cout << "\nUDP Recieved Message";
if (!error || error == boost::asio::error::message_size)
{
cout << "\nRecieved Message";
}
}
http://www.boost.org/doc/libs/1_46_0/doc/html/boost_asio/example/chat/chat_client.cpp
I am working on client application based on he example above.
I wanted to do the client connection in separte thread so that UI doesnot get stuck.Here UI is getiing stuck.
1. Can you tell me how to acheive this?
2. what is the meaning of the this line?
boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));
t.join();
Is this line create the separate thread for connection?
client::client(boost::asio::io_service& io_service, tcp::resolver::iterator endpoint_iterator)
: io_service_(io_service),
resolver_(io_service),
socket_(io_service_)
{
tcp::endpoint endpoint = *endpoint_iterator;
socket_.async_connect( endpoint,
boost::bind(&client::handle_connect, this,boost::asio::placeholders::error,
++endpoint_iterator));
}
void client::handle_connect(const boost::system::error_code& error,
tcp::resolver::iterator endpoint_iterator)
{
strcpy(data_,"Hello");
if (!error)
{
/*boost::asio::async_read(socket_,
boost::asio::buffer(data_, MAX_PATH),
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error));*/
boost::asio::async_write(socket_, boost::asio::buffer(data_, MAX_PATH),
boost::bind(&client::handle_read, 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(&client::handle_connect, this,
boost::asio::placeholders::error, ++endpoint_iterator));
}
}
void client::handle_read(const boost::system::error_code& error)
{
if (!error)
{
memset(data_,0,MAX_PATH);
boost::asio::async_read( socket_,
boost::asio::buffer(data_, MAX_PATH),
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error));
if (strcmp(data_,"Hello Response")==0)
{
MessageBox(NULL,_T("Regd Done"),_T("Vue"),1);
// return ;
}
}
}
CConnectionMgr::CConnectionMgr(void)
{
}
void CConnectionMgr::Connect()
{
try
{
char* host = "192.168.4.84";
char* port = "55555";
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(tcp::v4(),host , port);
tcp::resolver::iterator iterator = resolver.resolve(query);
c = new client(io_service, iterator);
//boost::thread thrd(boost::bind(&boost::asio::io_service::run, &io_service));
boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));
t.join();
// MessageBox(NULL,_T("Join"),_T("ff"),1);
}
catch (std::exception& e)
{
CString csMsg(e.what());
MessageBox(NULL,csMsg,_T("ff"),1);
}
}
The "t.join()" waits for the thread 't' to exit. Thread 't' is running the run() method on io_service and will exit when there is no remaining I/O to complete.
So, your Connect() method will block until all the I/O is finished, which is clear not what you want. If you are going to do asynchronous I/O so that your client doesn't block, you need to design a way for your I/O context to communicate with our UI context. It won't happen by magic.
Can you tell me how to acheive this?
You could launch the boost thread in your main i.e. somewhere before you enter the UI event loop (the blocking call) but don't do a join.
Have a look at the HTTP server examples: In one of them it's shown how you can start your io_service in the main and stop it via CTRL-c. In your case you would probably do this using a GUI button or event. Once you call the io_service stop method, you can then do the join on the thread.
what is the meaning of the this line? boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));
Runs the io_service::run in a new thread
t.join();
Waits for the thread to finish like janm stated which will happen once the io_service runs out of work or once the io_service::stop method is called