Sending 2 Independent UDP packets on same socket from different tasks. Is this thread safe?

I wrote:

It is only allowed to use a socket for reading in one task, and for writing in a different task.

You might get around it by using mutexes, but I would not recommend that. The best and the safest way is to make one task “the owner” of a (series of) socket(s).

Some people want to use two tasks per socket because they use blocking APIs, one for reading and one for writing to a socket. Sometimes you want to read and write, which ever comes first.

There are some alternatives:

Handling multiple sockets within a single task.

There are 4 possibilities:

  1. The most “standard way” is to use FreeRTOS_select(). It takes a socket-set, and each socket has a set of event types. The select APIs can be included by defining ipconfigSUPPORT_SELECT_FUNCTION=1 in your FreeRTOSIPConfig.h.
    BaseType_t rc = FreeRTOS_select( xSocketSet, pdMS_TO_TICKS( 1000U ) );
    if( rc > 0 )
    {
        /* Handle the triggered events. */
    }
  1. A very easy way of handling multiple sockets from a single task is to use a semaphore, and bind that to all of your sockets, using the socket option FREERTOS_SO_SET_SEMAPHORE. Make a main-loop and let it sleep/block on the semaphore. This feature is enabled when ipconfigSOCKET_HAS_USER_SEMAPHORE=1.
    for( ; ; )
    {
        xSemaphoreTake( xServerSemaphore, pdMS_TO_TICKS( 1000U ) );
        /* Check your sockets. */
    }
  1. Another easy approach is to write a callback function and bind to your sockets. The callback (“application hook”) will be called from the IP-task at every important event: received a packet, sent a packet, connected, disconnected, error. It does not specify what exactly has happened. Your handler will just wakeup the owning task, and it will be handled soon. The option is enabled by defining ipconfigSOCKET_HAS_USER_WAKE_CALLBACK=1.
    void socketWakeupCallback( Socket_t xSocket )
    {
        /* Wake up the handler of this socket. */
    }
  1. And finally: ipconfigUSE_CALLBACKS=1. That allows you to set a specific application hooks for five different types of events like reception, transmission, connect/disconnect.
    baseType_t onTCPReceive( Socket_t xSocket, void * pData, size_t xLength )
    {
        /* Inspect the incoming data, wakeup task. */
        return 0;
    }

Note that every application hook is called from the IP-task. You may not call any FreeRTOS+TCP API. That should be delegated to the handling task. You can set flags and wakeup that particular task. And also set a LED if you want to show that a device is connected.

Very useful: using Socket ID’s :

BaseType_t xSocketSetSocketID( const Socket_t xSocket, void * pvSocketID );
void * pvSocketGetSocketID( const ConstSocket_t xSocket );

It allows you to connect a socket with “an object” that exists in your application. So when your handler is called with a socket parameter, your can find the object or task that is handling that socket by calling pvSocketGetSocketID(). For example:

    void onConnected( Socket_t xSocket, BaseType_t ulConnected )
    {
        BaseType_t rc = 0;

        class CHandler_t * handler = ( CHandler_t *) pvSocketGetSocketID( xSocket );
        if( handler != NULL )
        {
            rc = handler->onConnected( xSocket, ulConnected );
        }
        return rc;
    }

Isn’t that beautiful?

4 Likes