Data is lost on TCP / IP with boost :: asio :: read_some?
I have implemented a TCP server using boost::asio
. This server uses a function basic_stream_socket::read_some
to read data. I know there is read_some
no guarantee that the supplied buffer will be full before it is returned.
In my project, I am sending delimited strings (if it matters). On the client side, I am using a function WinSock::send()
to send data. Now my problem is server side. I cannot get all the lines that were sent from the client side. My suspect is that it is read_some
receiving some data and discarding the remaining data for some reason. Then again in the next call it gets another string.
Is this possible in TCP / IP?
I tried to use async_receive
, but this is my whole cpu, as the buffer has to be flushed out by the callback function, which is causing a severe memory leak in my program. (I use a IoService::poll()
handler to call. This handler gets called at a very slow rate compared to the call rate async_read()
).
Again I tried using a free function read
, but that doesn't solve my purpose as it blocks too much time with the buffer size I supply.
My previous server implementation was from WinSock API
, where I was able to get all the data using WinSock::recv()
. Please give me some taps to get the complete data with help boost::asio
.
Here is my server side 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 on 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;
}
Asynchronous receiver
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;
}
The sendData function on the client side.
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;
}
}
source to share
Boost.Asio TCP's capabilities are fairly well utilized, so I hesitate to suspect that this is the source of the problem. In most cases of data loss, the problem is due to the application code.
In this case, there is a problem in the receiver code. The sender delimits strings with \0
. However, the receiver cannot properly handle the delimiter in cases where multiple lines are read in a single read operation, as it string::insert()
will truncate char*
when it reaches the first delimiter.
For example, the sender writes two lines "Test string\0"
and "Another test string\0"
. The TCPSocket::receiveData()
receiver reads "Test string\0Another test string\0"
in buf
. dataString
then filled dataString.insert(0, buf)
. This particular overload will be copied before the delimiter, so it dataString
will contain "Test string"
. To solve this problem, consider using the overload string::insert()
that takes the number of inserted characters dataString.insert(0, buf, len)
.
source to share
I haven't used the poll feature before. I created a worker thread that is designed to handle ASIO handlers with a trigger function that blocks. The Boost documentation says that every thread that needs to be available to handle 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 is causing the poll.
So, I would suggest allocating at least one worker thread for the async ASIO event handlers and using run instead of polling. If you want the worker thread to continue processing all asynchronous messages without returning and exiting, add the worker object to the io_service object. See the link for an example.
source to share