0

I am converting an app which had a very simple heartbeat / status monitoring connection between two services. As that now needs to be made to run on linux in addition to windows, I thought I'd use boost (v1.51, and I cannot upgrade - linux compilers are too old and windows compiler is visual studio 2005) to accomplish the task of making it platform agnostic (considering, I really would prefer not to either have two code files, one for each OS, or a littering of #defines throughout the code, when boost offers the possibility of being pleasant to read (6mos after I've checked in and forgotten this code!)

My problem now, is the connection is timing out. Actually, it's not really working at all.

First time through, the 'status' message is sent, it's received by the server end which sends back an appropriate response. Server end then goes back to waiting on the socket for another message. Client end (this code), sends the 'status' message again... but this time, the server never receives it and the read_some() call blocks until the socket times out. I find it really strange that

The server end has not changed. The only thing that's changed, is my having altered the client code from basic winsock2 sockets, to this code. Previously, it connected and just looped through send / recv calls until the program was aborted or the 'lockdown' message was received.

Why would subsequent calls (to send) silently fail to send anything on the socket and, what do I need to adjust in order to restore the simple send / recv flow?

#include <boost/signals2/signal.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

using boost::asio::ip::tcp;
using namespace std;

boost::system::error_code ServiceMonitorThread::ConnectToPeer(
    tcp::socket &socket,
    tcp::resolver::iterator endpoint_iterator)
{
    boost::system::error_code error;
    int tries = 0;

    for (; tries < maxTriesBeforeAbort; tries++)
    {
        boost::asio::connect(socket, endpoint_iterator, error);

        if (!error)
        {
            break;
        }
        else if (error != make_error_code(boost::system::errc::success))
        {
            // Error connecting to service... may not be running?
            cerr << error.message() << endl;
            boost::this_thread::sleep_for(boost::chrono::milliseconds(200));
        }
    }

    if (tries == maxTriesBeforeAbort)
    {
        error = make_error_code(boost::system::errc::host_unreachable);
    }

    return error;
}

// Main thread-loop routine.
void ServiceMonitorThread::run() 
{
    boost::system::error_code error;

    tcp::resolver resolver(io_service);
    tcp::resolver::query query(hostnameOrAddress, to_string(port));
    tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);

    tcp::socket socket(io_service);

    error = ConnectToPeer(socket, endpoint_iterator);
    if (error && error == boost::system::errc::host_unreachable)
    {
        TerminateProgram();
    }

    boost::asio::streambuf command;
    std::ostream command_stream(&command);
    command_stream << "status\n";

    boost::array<char, 10> response;
    int retry = 0;

    while (retry < maxTriesBeforeAbort)
    {
        // A 1s request interval is more than sufficient for status checking.
        boost::this_thread::sleep_for(boost::chrono::seconds(1));

        // Send the command to the network monitor server service.
        boost::asio::write(socket, command, error);

        if (error)
        {
            // Error sending to socket
            cerr << error.message() << endl;
            retry++;
            continue;
        }

        // Clear the response buffer, then read the network monitor status.
        response.assign(0);
        /* size_t bytes_read = */ socket.read_some(boost::asio::buffer(response), error);

        if (error)
        {
            if (error == make_error_code(boost::asio::error::eof))
            {
                // Connection was dropped, re-connect to the service.
                error = ConnectToPeer(socket, endpoint_iterator);
                if (error && error == make_error_code(boost::system::errc::host_unreachable))
                {
                    TerminateProgram();
                }
                continue;
            }
            else 
            {
                cerr << error.message() << endl;
                retry++;
                continue;
            }
        }

        // Examine the response message.
        if (strncmp(response.data(), "normal", 6) != 0)
        {
            retry++;

            // If we received the lockdown response, then terminate.
            if (strncmp(response.data(), "lockdown", 8) == 0)
            {
                break;
            }

            // Not an expected response, potential error, retry to see if it was merely an aberration.
            continue;
        }

        // If we arrived here, the exchange was successful; reset the retry count.
        if (retry > 0)
        {
            retry = 0;
        }
    }

    // If retry count was incremented, then we have likely encountered an issue; shut things down.
    if (retry != 0)
    {
        TerminateProgram();
    }
}
Jon
  • 1,675
  • 26
  • 57

1 Answers1

0

When a streambuf is provided directly to an I/O operation as the buffer, then the I/O operation will manage the input sequence appropriately by either commiting read data or consuming written data. Hence, in the following code, command is empty after the first iteration:

boost::asio::streambuf command;
std::ostream command_stream(&command);
command_stream << "status\n";
// `command`'s input sequence contains "status\n".

while (retry < maxTriesBeforeAbort)
{
  ...

  // write all of `command`'s input sequence to the socket.
  boost::asio::write(socket, command, error);
  // `command.size()` is 0, as the write operation will consume the data.
  // Subsequent write operations with `command` will be no-ops.

  ...
}

One solution would be to use std::string as the buffer:

std::string command("status\n");

while (retry < maxTriesBeforeAbort)
{
  ...

  boost::asio::write(socket, boost::asio::buffer(command), error);

  ...
}

For more details on streambuf usage, consider reading this answer.

Community
  • 1
  • 1
Tanner Sansbury
  • 51,153
  • 9
  • 112
  • 169
  • Well, that's a subtle (or maybe, not so subtle, if it should have been obvious... it wasn't, to me) point that made all the difference! Code is working fine now, with your above correction. – Jon Dec 02 '15 at 20:18