Home > database >  socket write in for loop mixes string buffers
socket write in for loop mixes string buffers

Time:06-23

I call a function multiple times using a for loop like this:

for ( int con=0; con < this->controller_info.size(); con   ) {
  try {
    this->pi.home_axis( this->controller_info.at(con).addr );
  }
  catch( std::out_of_range &e ) { ... }
}

where the home_axis() function is defined as:

long ServoInterface::home_axis( int addr ) {
  std::stringstream cmd;
  if ( addr > 0 ) cmd << addr << " ";
  cmd << "FRF";
  cmd << "\n";
  int bytes=this->controller.Write( cmd.str() );
  return NO_ERROR;
}

and the controller.Write() function is just a wrapper for the standard write(2) which writes the characters in the string to a socket file descriptor.

You can see that each time home_axis() is called it should have its own, fresh, std::stringstream cmd buffer. But what is happening is that, for the first time the for loop executes, the host that is receiving the bytes written by home_axis, is receiving a single string, once:

1 FRF2 FRF

but if I print the bytes written then it prints 6, twice. So the writer is writing correctly, 6 bytes two different times, but the host is receiving it apparently as a single buffer.

If I execute that for loop again, then the host receives (properly),

1 FRF

and then

2 FRF

handling the two received buffers each as they come in.

How can the std::stringstream cmd buffers be getting mixed like this?

There are no threads involved here.

In an effort to pick this apart a bit, if I insert just 1µsec of delay in that for loop, i.e. usleep(1); then it works properly. Also, if I call the home_axis() function manually, but equally rapid succession, without using a for loop like this,

this->pi.home_axis( this->controller_info.at(0).addr );
this->pi.home_axis( this->controller_info.at(1).addr );

then that also works.

So I'm wondering if it's possible there is a compiler optimization going on?

CodePudding user response:

This has nothing to do with the compiler at all.

TCP is a byte stream. It has no concept of message boundaries. There is no 1:1 relationship between writes and reads. You can write 2 messages of 6 bytes each, and the receiver may receive all 12 bytes at a time, or 1 byte and then 11 bytes, or any combination in between. That is just the way TCP works. By default, it breaks up data packets as it sees fit to optimize transmissions.

What is important is that TCP guarantees the bytes will be delivered (unless the connection is lost), and it will deliver the bytes in the same order that they are written.

As such, the sender must indicate in the data itself where each message begins and ends. Either by sending a message's length before its content, or by separating each message with a unique delimiter (as you are).

On the receiving side, a single read may receive a partial message, or pieces of multiple messages, etc. It is the receiver's responsibility to buffer incoming bytes and extract only complete messages from that buffer as needed, regardless of however many reads it takes to complete them.

As you are delimiting your messages with a trailing \n, the receiver should buffer all bytes and extract only messages that have received their \n, leaving any incomplete message at the end of the buffer for subsequent reads to finish.

This way, message boundaries are preserved and handled correctly.

  • Related