FreeRTOS_recv returns pdFREERTOS_ERRNO_ENOTCONN but also all the data

joehinkle wrote on Thursday, January 03, 2019:

How do you delete a post?

heinbali01 wrote on Saturday, January 05, 2019:

Just in case some readers are curious about the title of this post, here is an explanation:

It is not uncommon to shut down a TCP connection immediately after sending some data.

When your application runs on a laptop, it is usually safe to do the following:

send( socket, buffer, length, 0 );
shutdown( socket, SHUT_RDWR );
close( socket );

When an application closes a socket, the OS will take care of the socket and make sure that the data will be delivered before the socket is destroyed.

Under FreeRTOS+TCP, when you close a socket (FreeRTOS_closesocket()), the socket will be destroyed immediately and the TCP connection will be aborted in a non-gracious way.

You will have to wait for the connection to get closed (-pdFREERTOS_ERRNO_ENOTCONN).

    FreeRTOS_send( xSocket, pcBuffer, uxLength, 0 );
    FreeRTOS_shutdown( xSocket, FREERTOS_SHUT_RDWR );
    for( ;; )
    {
    BaseType_t xResult;

        xResult = FreeRTOS_recv( xSocket, pcBuffer, sizeof( pcBuffer ), 0 );
        /* Testing for -pdFREERTOS_ERRNO_EAGAIN is actually not necessary
        because FreeRTOS_recv() will never return that pseudo error. */
        if( ( xResult < 0 ) && ( xResult != -pdFREERTOS_ERRNO_EAGAIN ) )
        {
            break;
        }
    }
    FreeRTOS_closesocket( xSocket );

Calling shutdown() results in sending the FIN flag. But as long as the socket has data queued to be sent, the FIN flag will not be set. Only the TCP packet carrying the last bytes will have FIN set.

This mechanism can be seen when using the FTP server: files with an arbitrary length are being sent. The connection gets closed immediately and gracefully after the last byte.

At the receiving end, it may happen that the connection is closed while the socket still holds received data.

FreeRTOS_recv() will do 5 things:

  • If the socket is invalid, return -pdFREERTOS_ERRNO_EINVAL
  • If the RX stream is not empty, return the number of bytes read
  • If the connection is closed, return -pdFREERTOS_ERRNO_ENOTCONN
  • If the socket has a memory problem, -pdFREERTOS_ERRNO_ENOMEM
  • Or else, if the receive time-out is non-zero: wait for data in a blocking way.
    Return either 0 or the number of bytes

Finally, one thing about send() / shutdown(): when calling send(), the IP-task will be triggered to start sending the data immediately. It may happen that the packet carrying the last bytes does not have the FIN flag set.

That is not a real problem, it is just less efficient.

When you know that send() is called for the last time, you can set a flag in the socket:

    BaseType_t xTrueValue = 1;

    FreeRTOS_setsockopt( xSocket,
                         0,
                         FREERTOS_SO_CLOSE_AFTER_SEND,
                         ( void * ) &xTrueValue,
                         sizeof( xTrueValue ) );
    FreeRTOS_send( xSocket, pcBuffer, uxLength, 0 );
    /* shutdown() will be done automatically.
    Just wait for the connection to close as described above. */

This is a small optimisation found in FreeRTOS+TCP only.