Abhishek Bansal
Abhishek Bansal

Reputation: 5335

Data is getting discarded in TCP/IP with boost::asio::read_some?

I have implemented a TCP server using boost::asio. This server uses basic_stream_socket::read_some function to read data. I know that read_some does not guarantee that supplied buffer will be full before it returns.

In my project I am sending strings separated by a delimiter(if that matters). At client side I am using WinSock::send() function to send data. Now my problem is on server side I am not able to get all the strings which were sent from client side. My suspect is that read_some is receiving some data and discarding leftover data for some reason. Than again in next call its receiving another string.

Is it really possible in TCP/IP ?

I tried to use async_receive but that is eating up all my CPU, also since buffer has to be cleaned up by callback function its causing serious memory leak in my program. (I am using IoService::poll() to call handler. That handler is getting called at a very slow rate compared to calling rate of async_read()).

Again I tried to use free function read but that will not solve my purpose as it blocks for too much time with the buffer size I am supplying.

My previous implementation of the server was with WinSock API where I was able to receive all data using WinSock::recv(). Please give me some leads so that I can receive complete data using boost::asio.

here is my server side thread loop

void 
TCPObject::receive()
{
    if (!_asyncModeEnabled)
    {
        std::string recvString;
        if ( !_tcpSocket->receiveData( _maxBufferSize, recvString ) )
        {
            LOG_ERROR("Error Occurred while receiving data on socket.");
        }
        else
            _parseAndPopulateQueue ( recvString );
    }
    else
    {
        if ( !_tcpSocket->receiveDataAsync( _maxBufferSize ) )
        {
            LOG_ERROR("Error Occurred while receiving data on socket.");
        }
    }
}

receiveData() in TCPSocket

bool 
TCPSocket::receiveData( unsigned int bufferSize, std::string& dataString )
{
   boost::system::error_code error;
   char *buf = new char[bufferSize + 1];
   size_t len = _tcpSocket->read_some( boost::asio::buffer((void*)buf, bufferSize),        error);
   if(error)
   {
       LOG_ERROR("Error in receiving data.");
       LOG_ERROR( error.message() );
       _tcpSocket->close();
       delete [] buf; 
       return false;
   }
   buf[len] ='\0';
   dataString.insert( 0, buf );
   delete [] buf;
   return true;
}

receiveDataAsync in TCP Socket

bool 
TCPSocket::receiveDataAsync( unsigned int bufferSize )
{
    char *buf = new char[bufferSize + 1];

    try
    {
        _tcpSocket->async_read_some( boost::asio::buffer( (void*)buf, bufferSize ), 
                                     boost::bind(&TCPSocket::_handleAsyncReceive, 
                                                    this,
                                                    buf,
                                                    boost::asio::placeholders::error,
                                                    boost::asio::placeholders::bytes_transferred) );
        //! Asks io_service to execute callback
        _ioService->poll();
    }
    catch (std::exception& e)
    {
        LOG_ERROR("Error Receiving Data Asynchronously");
        LOG_ERROR( e.what() );
        delete [] buf;
        return false;
    }

    //we dont delete buf here as it will be deleted by callback _handleAsyncReceive
    return true;
}

Asynch Receive handler

void 
TCPSocket::_handleAsyncReceive(char *buf, const boost::system::error_code& ec, size_t size)
{
    if(ec)
    {
        LOG_ERROR ("Error occurred while sending data Asynchronously.");
        LOG_ERROR ( ec.message() );
    }
    else if ( size > 0 )
    {
        buf[size] = '\0';
        emit _asyncDataReceivedSignal( QString::fromLocal8Bit( buf ) );
    }
    delete [] buf;
}

Client Side sendData function.

sendData(std::string data)
{
    if(!_connected)
    {
        return;
    }

    const char *pBuffer = data.c_str();

    int bytes = data.length() + 1;

    int i = 0,j;
    while (i < bytes)
    {
        j = send(_connectSocket, pBuffer+i, bytes-i, 0);

        if(j == SOCKET_ERROR)
        {
            _connected = false;
            if(!_bNetworkErrNotified)
            {
                _bNetworkErrNotified=true;
                emit networkErrorSignal(j);
            }
            LOG_ERROR( "Unable to send Network Packet" );
            break;
        }
        i += j;
    }
}

Upvotes: 3

Views: 1888

Answers (2)

Bob Bryan
Bob Bryan

Reputation: 3837

I have not used the poll function before. What I did is create a worker thread that is dedicated to processing ASIO handlers with the run function, which blocks. The Boost documentation says that each thread that is to be made available to process async event handlers must first call the io_service:run or io_service:poll method. I'm not sure what else you are doing with the thread that calls poll.

So, I would suggest dedicating at least one worker thread for the async ASIO event handlers and use run instead of poll. If you want that worker thread to continue to process all async messages without returning and exiting, then add a work object to the io_service object. See this link for an example.

Upvotes: 1

Tanner Sansbury
Tanner Sansbury

Reputation: 51871

Boost.Asio's TCP capabilities are pretty well used, so I would be hesitant to suspect it is the source of the problem. In most cases of data loss, the problem is the result of application code.

In this case, there is a problem in the receiver code. The sender is delimiting strings with \0. However, the receiver fails to proper handle the delimiter in cases where multiple strings are read in a single read operation, as string::insert() will cause truncation of the char* when it reaches the first delimiter.

For example, the sender writes two strings "Test string\0" and "Another test string\0". In TCPSocket::receiveData(), the receiver reads "Test string\0Another test string\0" into buf. dataString is then populated with dataString.insert(0, buf). This particular overload will copy up to the delimiter, so dataString will contain "Test string". To resolve this, consider using the string::insert() overload that takes the number of characters to insert: dataString.insert(0, buf, len).

Upvotes: 5

Related Questions