简体   繁体   中英

boost::asio client connection stops receiving datas

I'm really willing to learn boost::asio for my C++ application. So I started with a "simple" client application which compiles and runsn on my laptop. The idea is really simple. The client starts a connection with a server and asks every 2 seconds for an answer. I have the possibility to trigger the server' status (let say an alarm signal). As a reaction, when the client try to connect to the server, the latter will inform it that I must be ready to receive some data (a list with some double and integer value). The client ask the list and the server send the wanted list through the connection.

Problem: the list is not entirely transmitted. A big block of the list is going to be sent and be correctly received, but then I receive no data anymore.

Since I splitted my application into classes, here the code:

client.h

#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <ostream>
#include <chrono>

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/array.hpp>
#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/ip/tcp.hpp>

#ifndef __CLIENT_CLASS__
#define __CLIENT_CLASS__

//#define DEBUG

class Client
{
    boost::asio::io_service io_service_;

    boost::asio::ip::tcp::resolver resolver_{ io_service_ };
    boost::asio::ip::tcp::socket   socket_{ io_service_ };

    boost::asio::streambuf request_;
    boost::asio::streambuf response_;

    const std::string host_server_; 
    const std::string port_; 
    const uint uiId_;            
    const long double dLon_;             
    const long double dLat_; 
    const double dHeight_;     

    const std::string useragent_ = "HTMLGET 1.0";

    enum State { STANDBY = 0x01,
                 ALARM   = 0x02,
                 WPL_REC = 0x04 };

    State state_ = STANDBY;

    std::chrono::milliseconds server_response_delay_  = std::chrono::milliseconds( 2000 ); // SERVER_RESPONSE_DELAY 
    uint uiError_ = 0;

    bool bAlarmFlag_;
    unsigned int uiRequestCounter_;

    std::chrono::high_resolution_clock::time_point begin_time_;

    public:
        explicit Client( const std::string &address, const std::string &port, uint id,
                         long double start_longitude, long double start_latitude,
                         double start_height );
        virtual ~Client();

        void run( void );

        std::chrono::milliseconds getServerResponseDelay( void ) const;
        void setServerResponseDelay( std::chrono::milliseconds milliseconds );


    private:
        void buildQuery( void );
        void handleResolve( const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator it );
        void handleConnect( const boost::system::error_code &ec );
        void handleWriteRequest( const boost::system::error_code &ec );
        void handleReadStatusLine( const boost::system::error_code &ec );
        void handleMessage( const boost::system::error_code &error_msg );
        void readContent( const boost::system::error_code &error_msg );
        void build_wpl_query( void );
        std::string build_ok_answer( void );
        std::string build_alarm_answer( void );
        std::string build_wpl_answer( void );
};

#endif

my client.cpp . I put one line of comment where I have the problem.

#include "client.h"

Client::Client( const std::string &address, const std::string &port, uint id,
                         long double start_longitude, long double start_latitude,
                         double start_height ) : host_server_( address ), port_( port ),
                         uiId_(id), dLon_( start_longitude ), dLat_( start_latitude ), dHeight_( start_height )
{

    std::cout << "Constructor of the class \"Client\" called" << std::endl;

    bAlarmFlag_       = false;
    uiRequestCounter_ = 1;

    begin_time_ = std::chrono::high_resolution_clock::now();

}

Client::~Client()
{
    std::cout << "Destructor of the class \"Client\" called" << std::endl; 
}

void Client::run( void )
{
    if( std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::high_resolution_clock::now() - begin_time_ ) > server_response_delay_ ) 
    {
        begin_time_ = std::chrono::high_resolution_clock::now();

        boost::asio::ip::tcp::resolver::query local_query( host_server_, port_ );

        resolver_.async_resolve( local_query, boost::bind( &Client::handleResolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator ) );

        size_t service_status = io_service_.run();

        std::cout << "IO Service status: " << service_status << std::endl << std::endl;

        io_service_.reset();
    }
}

void Client::handleResolve( const boost::system::error_code &error_msg, boost::asio::ip::tcp::resolver::iterator it )
{
    if( !error_msg ) {
        boost::asio::async_connect( socket_, it, boost::bind( &Client::handleConnect, this, boost::asio::placeholders::error ) );

    } else {
        std::cout << "Handle Resolv Error: " << error_msg.message() << std::endl;
    }
}

void Client::handleConnect( const boost::system::error_code &error_msg ) 
{
    if( !error_msg ) {

        std::cout << "-------------------------------------------------------------------------------";
        std::cout << std::endl << "Request Nr: " << (int)uiRequestCounter_ << std::endl;
        std::cout << "Socket created" << std::endl;

        if( !bAlarmFlag_ )
            buildQuery();
        if( bAlarmFlag_ ) {
            build_wpl_query();
            bAlarmFlag_ = false;
        }

        boost::asio::async_write( socket_, request_, boost::bind( &Client::handleWriteRequest, this, boost::asio::placeholders::error ) );

        ++uiRequestCounter_;

    } else {
        std::cout << "Handle Connect Error: " << error_msg.message() << std::endl;
    }
}

void Client::handleWriteRequest( const boost::system::error_code &error_msg )
{
    std::cout << "Waiting answer from Server..." << std::endl;

    if( !error_msg ) {

        boost::asio::async_read_until( socket_, response_, "\r\n", boost::bind( &Client::handleReadStatusLine, this, boost::asio::placeholders::error ) );      
    } else {
        std::cout << "Handle Write Request Error: " << error_msg.message() << std::endl;
    }
}

void Client::handleReadStatusLine( const boost::system::error_code &error_msg )
{
    std::string  header_string;
    unsigned int status_code;
    std::string  server_status; 

    if( !error_msg ) {
        std::istream response_stream( &response_ );
        response_stream >> header_string;
        response_stream >> status_code;
        response_stream >> server_status;


        std::cout << std::endl << header_string << "   " << status_code << "   " << server_status << std::endl;

        if( header_string.substr(0, 8) != "HTTP/1.1" ) {

            std::cout << "Not a valid message from Server" << std::endl;
            return;
        } 

        if( status_code != 200 ) {
            std::cout << "Code 200 not available from Server" << std::endl;
            return;
        }

        if( server_status != "OK" ) {

            std::cout << "Not a OK message from Server" << std::endl;
            return;
        }

        if( ( header_string.substr(0, 8) == "HTTP/1.1" ) && ( status_code == 200 ) && ( server_status == "OK" ) ) {

            std::cout << "Server OK, start retrieving data" << std::endl;

            boost::asio::async_read_until( socket_, response_, "\r\n\r\n", boost::bind( &Client::handleMessage, this, boost::asio::placeholders::error ) );

        } else {

            std::cout << "Not a valid packet from Server" << std::endl;
        }


    } else {

        std::cout << "Handle Read Status Line Error: " << error_msg.message() << std::endl;
    }
}

void Client::handleMessage( const boost::system::error_code &error_msg )
{
    if( !error_msg ) {

        std::istream response_stream( &response_ );
        std::string header_string;

        while( std::getline( response_stream, header_string ) && header_string != "\r\n" ) {

            // http://stackoverflow.com/a/30083146
/*          if( static_cast<int>( header_string.find( "Content-Length:" ) ) != -1 ) {

                std::string car = boost::regex_replace( header_string, boost::regex( "[^0-9]*([0-9]+).*" ), std::string( "\\1" ) );

                std::cout << "Content-Length: " << std::stoi( car ) << std::endl;

            }*/

            if( header_string.find( build_ok_answer() ) != std::string::npos ) {

                bAlarmFlag_ = false;
                std::cout << "I received a \033[1;32mALIVE-Signal\033[0m from the Server." << std::endl;

            } else if( header_string.find( build_alarm_answer() ) != std::string::npos ) {
                bAlarmFlag_ = true;
                std::cout << "I received a \033[1;31mALARM-Signal\033[0m from Server!" << std::endl;

            } else if( header_string.find( build_wpl_answer() ) != std::string::npos ) {

                std::cout << std::endl << "List \033[1;34mData\033[0m received." << std::endl;

/* HERE I READ A BLOCK OF THE LIST BUT NOT THE WHOLE LIST!!!! */
std::cout << &response_;

            }

        }

        boost::asio::async_read( socket_, response_, boost::asio::transfer_at_least(1), 
                                boost::bind(&Client::readContent, this, boost::asio::placeholders::error ) );

    } else {

        std::cout << "Handle Message Error: " << error_msg.message() << std::endl;
    }
}

void Client::readContent( const boost::system::error_code &error_msg )
{
    if( !error_msg ) {

        /* Read until the EOF */
        boost::asio::async_read( socket_, response_, boost::asio::transfer_at_least(1),
                                boost::bind( &Client::readContent, this, boost::asio::placeholders::error ) );

    } else if ( error_msg != boost::asio::error::eof ) {
        std::cout << "Read Content Error: " << error_msg.message() << std::endl;
    }
}

void Client::buildQuery( void )
{
    std::ostream ssRequest( &request_ );

    ssRequest << "GET /api/rest/v1/register?";
    ssRequest << "id=" << uiId_ << '&' << "delay=" << ( static_cast<int>(server_response_delay_.count()) ) << '&' << "lon=" << dLon_ << '&';
    ssRequest << "lat=" << dLat_ << '&' << "height=" << dHeight_ << '&' << "state=" << state_ << '&' << "error=" << uiError_;
    ssRequest << " HTTP/1.0\r\nHost: " << host_server_ << "\r\nUser-Agent: " << useragent_ << "\r\n\r\n";

}

std::string Client::build_ok_answer( void )
{
    std::ostringstream ssAnswerOk;
    ssAnswerOk << "{\"Text\":\"OK\",\"Id\":" << uiId_ << "}"; 
    return ssAnswerOk.str();
}

std::string Client::build_alarm_answer( void )
{
    std::ostringstream ssAnswerAlarm;
    ssAnswerAlarm << "{\"Text\":\"ALARM\",\"Id\":" << uiId_ << "}"; 
    return ssAnswerAlarm.str();
}

std::string Client::build_wpl_answer( void )
{
    std::ostringstream ssAnswerWpl;
    ssAnswerWpl << "QGC WPL 120";
    return ssAnswerWpl.str();
}

std::chrono::milliseconds Client::getServerResponseDelay( void ) const
{
    return server_response_delay_;
}

void Client::build_wpl_query( void )
{
    std::ostream ssRequestWpl( &request_ );
    ssRequestWpl << "GET /File/data" << uiId_ << ".txt";
    ssRequestWpl << " HTTP/1.0\r\nHost: " << host_server_ << "\r\nUser-Agent: " << useragent_ << "\r\n\r\n";
}

Now...I was inspired to the example found on boost website . If I start the program, it starts to ask the server every 2 seconds as normal. I receive the right answer:

Constructor of the class "Client" called

----------------------------------- 
Request Nr: 1 Socket created Waiting answer from Server...

HTTP/1.1   200   OK 
Server OK, start retrieving data 
I received a ALIVE-Signal from the Server. 
IO Service status: 6

------------------------------------
Request Nr: 2 Socket created Waiting answer from Server...

HTTP/1.1   200   OK 
Server OK, start retrieving data 
I received a ALIVE-Signal from the Server. 
IO Service status: 6

------------------------------------
Request Nr: 3 Socket created Waiting answer from Server...

HTTP/1.1   200   OK 
Server OK, start retrieving data 
I received a ALIVE-Signal from the Server. 
IO Service status: 6

------------------------------------

But as far as I start requesting the list I receive the following:

Request Nr: 6
Socket created
Waiting answer from Server...

HTTP/1.1   200   OK
Server OK, start retrieving data

List Data received.
0   1   3   22  1   1000    1   3   51.9896769729347099 8.62269043922424316 35  1   0
1   0   3   16  1   1000    3   1   51.9908133374338917 8.6260378360748291  0   1   0
2   0   3   16  1   5000    3   1   51.9903706872495235 8.62733602523803711 -30 1   1
3   0   3   16  1   1000    3   1   51.9906
IO Service status: 7

which is not really the whole list. The list has a couple of more lines:

0   1   3   22  1   1000    1   3   231.9896769729347099    83.62269043922424316    35  1   0
1   0   3   16  1   1000    3   1   221.9908133374338917    82.6260378360748291 0   1   0
2   0   3   16  1   5000    3   1   121.9903706872495235    84.62733602523803711    -30 1   1
3   0   3   16  1   1000    3   1   45.99066872495235564

82.62733602523803711 -30 1 1 2 0 3 16 1 5000 3 1 124.9903706872495235 24.62733602324442711 -30 1 1 3 0 3 16 1 1000 3 1 22.99066872495235564 22.62235353533535351 -30 1 1

It seems to me that the connection (maybe I m wrong about this) is abruptly broken. Then it started again from the beginning and it receives the remaining lines. But I need to receive the whole list at once. What is wrong?


EDIT : many thanks to rhashimoto. I helped me a lot to understand what is going on and what can be improved. His anwser didn't fix the problem. Because, if I do:

void Client::run( void )
{
    if( std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::high_resolution_clock::now() - begin_time_ ) > server_response_delay_ ) 
    {
        begin_time_ = std::chrono::high_resolution_clock::now();

        boost::asio::ip::tcp::resolver::query local_query( host_server_, port_ );

        resolver_.async_resolve( local_query, boost::bind( &Client::handleResolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator ) );

        size_t service_status = io_service_.run();

        if( flag_ ) {

            list << &response_;

            std::cout << list.str();
        }

        std::cout << "IO Service status: " << service_status << std::endl << std::endl;

        io_service_.reset();

    }
}

(where flag is just once set when I request the list), then I receive the second part of the list but not the first.

Any idea?

If I put that line without the flag variable the connection stucks at that point and doen't visualize anything.

Your async_read* handlers are missing the bytes_transferred argument. They should have the signature :

void handler(
  const boost::system::error_code& error, // Result of operation.
  std::size_t bytes_transferred           // Number of bytes copied into the
                                          // buffers. If an error occurred,
                                          // this will be the  number of
                                          // bytes successfully transferred
                                          // prior to the error.
);

Note that you will also need to add the placeholder argument wherever you bind them.

That's not the problem for your question, though. The issue there is that you're printing out the response body before you read it. Note that this line:

/* HERE I READ A BLOCK OF THE LIST BUT NOT THE WHOLE LIST!!!! */
std::cout << &response_;

is executed before this line:

boost::asio::async_read( socket_, response_, boost::asio::transfer_at_least(1), 
                         boost::bind(&Client::readContent, this, boost::asio::placeholders::error ) );

Okay, now you're probably wondering how any of the body could get printed out before you read it. The answer is that you used async_read_until to read the HTTP status line and all headers. The documentation notes:

After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter. An application will typically leave that data in the streambuf for a subsequent async_read_until operation to examine.

So you're printing out that additional data that you didn't consume with the std::istream . That happens to be the first part of your list. Then you go on to read the rest but you don't print that out so it just sits in the streambuf (and you see it on the next request).

Try moving that std::cout line to Client::run() , after the read is complete.:

void Client::run( void )
{
    if( std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::high_resolution_clock::now() - begin_time_ ) > server_response_delay_ ) 
    {
        begin_time_ = std::chrono::high_resolution_clock::now();

        boost::asio::ip::tcp::resolver::query local_query( host_server_, port_ );

        resolver_.async_resolve( local_query, boost::bind( &Client::handleResolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator ) );

        size_t service_status = io_service_.run();

        // ******************************
        // ***** MOVE PRINT TO HERE *****
        // ******************************
        std::cout << &response_;

        std::cout << "IO Service status: " << service_status << std::endl << std::endl;

        io_service_.reset();
    }
}

Or you could put at the top of Client::readContent() (if it is okay to print it out in chunks).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM