FreeRTOS UDP server drops bytes between recvfrom() calls

Hello again,
I am back with a question regarding the FreeRTOS IP stack implementation for Zynq7, this time regarding UDP servers.

I noticed something strange if you send a message of a known size from a UDP client to the UDP server on the Zynq:
If you try to split up the read process into two separate calls to recvfrom(), the second one will always fail.
When using TCP client & server, the same scenario works. I don’t know if this different behaviour is intended for UDP or a bug.

To demonstrate the issue I wrote a simple test program, based on the example on how to implement UDP recvfrom() from here.

I create two tasks and start the scheduler:

  1. InitTask() - starts the TCP/IP stack

  2. TutorialTask() - implements the UDP server

void vStartTutorialTask( void )
{
	xTaskCreate( vUDPReceivingUsingStandardInterface, "TutorialTask", TUTORIALTASK_STACK_SIZE, NULL, tutuorialTASK_PRIORITY, ( TaskHandle_t * ) NULL );
}

static void vUDPReceivingUsingStandardInterface( void *pvParameters )
{
	long lBytes;
	uint8_t cReceivedString[ 60 ] = {};
	struct freertos_sockaddr xClient, xBindAddress;
	uint32_t xClientLength = sizeof( xClient );
	Socket_t xListeningSocket;
	TickType_t xNextWakeTime = 0;

	/* Initialize xNextWakeTime */
	xNextWakeTime = xTaskGetTickCount();

	// Wait for init task to finish
	xEventGroupWaitBits( os_syncEvents,
						 (1 << OS_FLAG_POS_SYNC_EVENTS_BASIC_RESOURCE_INIT_DONE),
						 OS_FLAG_KEEP_ON_EXIT,
						 OS_FLAG_WAIT_FOR_ALL_BITS,
						 OS_WAIT_FOREVER );

   /* Attempt to open the socket. */
   xListeningSocket = FreeRTOS_socket( FREERTOS_AF_INET,
                                       FREERTOS_SOCK_DGRAM, /*FREERTOS_SOCK_DGRAM for UDP.*/
                                       FREERTOS_IPPROTO_UDP );

   /* Check the socket was created. */
   configASSERT( xListeningSocket != FREERTOS_INVALID_SOCKET );

   /* Bind to port 10000. */
   xBindAddress.sin_port = FreeRTOS_htons( 10000 );
   FreeRTOS_bind( xListeningSocket, &xBindAddress, sizeof( xBindAddress ) );

   //The sender always sends this amount of bytes (hard-coded)
   const uint32_t totalBytesSent = 10;

   //Amount of bytes requested to receive
   uint32_t requestedBytes = 1;

   for( ;; )
   {
       /* Receive data from the socket.  ulFlags is zero, so the standard
          interface is used.  By default the block time is portMAX_DELAY, but it
          can be changed using FreeRTOS_setsockopt(). */
       lBytes = FreeRTOS_recvfrom( xListeningSocket,
                                   cReceivedString,
								   requestedBytes,
                                   0,
                                   &xClient,
                                   &xClientLength );

       if( lBytes > 0 )
       {
           /* Data was received and can be process here. */
    	   xil_printf("\nFirst Part: %d Bytes: ", lBytes);
    	   xil_printf((char *) cReceivedString);
    	   memset(cReceivedString, 0, sizeof(cReceivedString));

    	   //Try to receive second part of data.
    	   if(totalBytesSent - requestedBytes > 0)
    	   {
			   lBytes = FreeRTOS_recvfrom( xListeningSocket,
										   cReceivedString,
										   totalBytesSent - requestedBytes,
										   0,
										   &xClient,
										   &xClientLength );

			   xil_printf(", Second Part: %d Bytes: ", lBytes);
			   xil_printf((char *) cReceivedString);
			   memset(cReceivedString, 0, sizeof(cReceivedString));
    	   }
    	   else
    	   {
    		   xil_printf(", There is no second part, all bytes already received.");
    	   }

    	   //Increase number of bytes read out as the first part, or start over at 1 if max size is reached
    	   if(requestedBytes < totalBytesSent)
    		   requestedBytes++;
    	   else
    		   requestedBytes = 1;
       }

       vTaskDelayUntil(&xNextWakeTime, TUTORIAL_TASK_TIMEOUT_MS);

   }
}

As a UDP client, I used the most basic python implementation, running on my host PC. I’ll attach the code for reference. Basically, it just sends 10 bytes of data (ASCII digits from 0-9), every 10 sec.

The output is the following:

First Part: 1 Bytes: 0, Second Part: -11 Bytes: 
First Part: 2 Bytes: 01, Second Part: -11 Bytes: 
First Part: 3 Bytes: 012, Second Part: -11 Bytes: 
First Part: 4 Bytes: 0123, Second Part: -11 Bytes: 
First Part: 5 Bytes: 01234, Second Part: -11 Bytes: 
First Part: 6 Bytes: 012345, Second Part: -11 Bytes: 
First Part: 7 Bytes: 0123456, Second Part: -11 Bytes: 
First Part: 8 Bytes: 01234567, Second Part: -11 Bytes: 
First Part: 9 Bytes: 012345678, Second Part: -11 Bytes: 
First Part: 10 Bytes: 0123456789, There is no second part, all bytes already received.
First Part: 1 Bytes: 0, Second Part: -11 Bytes:
...

As you can see, the second part says (-11) as byte count, which is the error code that recvfrom() returns when attempting to read more data. I am aware that (-11) essentially means timeout.

#define	pdFREERTOS_ERRNO_EWOULDBLOCK	11	/* Operation would block */

It may looks like, I just have to wait longer for the missing bytes, but given the fact that the data can indeed be received, if it is read during the first attempt, it seems extremly unlikely to me to be the reason. More likely is that the data somehow is lost, if not read all in one go.

In some applications this may not be viable, e.g. if the packet size is not known beforehand, but is transmitted as part of a fixed-sized message header upfront. Once you retrieve the actual message length by first reading the header and try to retrieve it by calling recvfrom() once more, it is already gone.

All I can think of is that I somehow messed up the configuration of the socket, or that this is indeed desired UDP behaviour, or indeed a bug!

Best Regards,
Emanuel

simpleUdpSenderClient.py.zip (510 Bytes)

This is typical behavior for any Berkeley sockets like interface when using UDP. From the Linux UDP documentation: “All receive operations return only one packet. When the packet is smaller than the passed buffer, only that much data is returned; when it is bigger, the packet is truncated and the MSG_TRUNC flag is set. MSG_WAITALL is not supported.”

3 Likes

@Stonebull

As @apcountryman suggested this is expected BSD socket behaviour. You find this behaviour different in TCP compared to UDP because TCP is a stream-oriented protocol, while UDP is a packet-oriented protocol.

You can use the iptraceRECVFROM_DISCARDING_BYTES macro to define a callback [listed here] to know/log whenever this happens or the number of bytes discarded.

2 Likes

@apcountryman, @tony-josi-aws Thanks for your replys. I honestly did not know about this UDP feature.