Manual

do

Maker

.

com

Pilha TCP/IP no FreeRTOS Plus TCP

Pilha TCP/IP no FreeRTOS Plus TCP

Eu não escrevo notícias, mas não pude deixar passar essa.

Passeando pelo linkedin me deparei com uma postagem do Sérgio Prado, onde ele fala de duas novas APIs do FreeRTOS. Em relação ao TCP/IP, ele só comentou, mas foi o suficiente para eu ir atrás da documentação ver o que era essa novidade. E vocês não vão acreditar; a equipe do FreeRTOS implementou uma pilha TCP/IP lindíssima! Chega de usar o lwip? Sim? Bem, não ainda. Resta que esses recursos sejam implementados no ESP-IDF para utilizarmos também no ESP32.

Nada nos impede de contamplar os recursos, hum? Primeiro, gostaria de deixar o link da implementação dolorosa de um socket TCP utilizando o lwip, que fiz nesse artigo. Por favor, se puder, dê uma olhada no código desse artigo que citei antes de seguir a leitura, garanto que ficará muito mais empolgante.

Características

A implementação contempla uma série importante de recursos.

  • API para Berkeley sockets.
  • Thread safe e reentrância
  • ARP, DHCP, DNS, LLMNR, NBNS
  • manifestação de ARPs gratuitos
  • Endereçamento estático, DHCP e auto-IP
  • sockets TCP e UDP (client e server)
  • Interface opcional de callback

Esses são os recursos que me sinto livre para errar, porque se eu disser alguma besteira, será minima. Mas vamos lá, porque não consigo me conter.

Berkley sockets

Eu não entendo como isso pode ter sido implementado, é simplesmente fantástico!

O Berkley sockets é utilizado para fazer IPC em sistemas usando UNIX domain sockets. Escrevi a respeito nesse artigo, com um exemplo em Python. Essa é uma forma simples de fazer callback e facilitará muitas tarefas, se implementado no FreeRTOS do ESP32 (atualmente usando a versão Vanilla).

Thread safe e reentrância

Thread safe, caso não saiba, é dito de uma porção de código que maniple apenas estruturas de dados compartilhadas, garantindo a execução de threads simultâneas. Não sei se vou conseguir exemplificar adequadamente, mas é como dizer que você cria pequenos indivíduos que cumpre individualmente uma mesma tarefa, com parâmetros diferentes. Em suma, podemos abrir sockets distintos.

A reentrância é a possiblidade de execução concorrente de forma isolada. Podemos chamar várias vezes uma instância mesmo com a rotina alvo em execução, limitado apenas ao uso de dados globais, que não poderão fazer parte dessa rotina.

Protocolos

Não vou explicar cada um deles, trabalhei com isso durante muitos anos e estou enjoado dos conceitos. Só pra não deixar sem nada, o ARP é uma manifestação usada em uma camada baixa do modelo OSI (enlace) para anunciar um endereçamento físico na rede. É utilizado para mapeamento entre um endereço IP e o endereço ethernet e nem sei dizer um caso em que não seja utilizado, já fiz muito diagnóstico baseado em ARP enquanto administrador de redes.

O DHCP é o protocolo utilizado para atribuição de endereço baseado em um servidor de endereçamentos da rede e é o mais comumente utilizado em redes domésticas e redes de interação humana (que não sejam servidores, quero dizer).

O LLMNR é um protocolo de resolução de nomes utilizado em redes locais. É impressionante aos meus olhos tal recurso!

O NBNS é, bem, o conhecido WINS! Também para resolução de nomes, reconhecendo dispositivos da rede local.

Socket TCP e UDP

Agora, se você leu o artigo supracitado, tenho certeza que ficará no mínimo sorridente. Olhe que facilidade implementar um socket com a nova API do FreeRTOS+TCP.

Socket_t FreeRTOS_socket( BaseType_t xDomain, BaseType_t xType, BaseType_t xProtocol );

Os parâmetros são:

xDomain - Que deve ser configurado para FREERTOS_AF_INET.

**xType -**Pode ser TCP ou UDP, passando a definição FREERTOS_SOCK_STREAM ou FREERTOS_SOCK_DGRAM, respectivamente.

xProtocol- Para criar um socket TCP, deve ser FREERTOS_IPPROTO_TCP. Para um socket UDP, deve ser FREERTOS_IPPROTO_UDP.

O retorno da função quando inválida, é FREERTOS_INVALID_SOCKET. Com isso, já podemos testar se a conexão foi criada adequadamente. Agora, recline a cabeça sobre a cadeira para não desmaiar de emoção, porque a implementação é simples demais!

// Fundamental fazer esse include, mas nada mais de lwip!
#include "FreeRTOS_sockets.h"

void aFunction(void){
    // Variável para o retorno do socket
    Socket_t xSocket;
    struct freertos_sockaddr xBindAddress;

    // Agora para criar o socket!
    xSocket = FreeRTOS_socket( FREERTOS_AF_INET,
                               FREERTOS_SOCK_STREAM,
                               FREERTOS_IPPROTO_TCP );

    //Veja que simples testar sua criação:
    if( xSocket != FREERTOS_INVALID_SOCKET )
    {
        /*Tradução direta da documentação: 
        O soquete foi criado com sucesso e agora pode ser usado para se conectar a
         um soquete remoto usando FreeRTOS_connect (), antes de enviar dados usando
         FreeRTOS_send (). Alternativamente, o soquete pode ser ligado a uma porta usando
         FreeRTOS_bind (), antes de ouvir conexões recebidas usando
         FreeRTOS_listen (). */
    }
    else
    {
        /* Se deu mer... digo, problema, é aqui que algo deverá ser feito.*/
    }
}

As funções relacionadas:

FreeRTOS_socket()
FreeRTOS_setsockopt()
FreeRTOS_bind()
FreeRTOS_listen()
FreeRTOS_connect()
FreeRTOS_accept()
FreeRTOS_send() / FreeRTOS_sendto()
FreeRTOS_recv() / FreeRTOS_recvfrom()

Na documentação oficial tem exemplos de uso bem claras. Vou dispor aqui o código de uma comunicação TCP client e um exemplo de TCP server. É impressionante a simplicidade:

void vCreateTCPClientSocket( void )
{
Socket_t xClientSocket;
socklen_t xSize = sizeof( freertos_sockaddr );
static const TickType_t xTimeOut = pdMS_TO_TICKS( 2000 );

    /* Attempt to open the socket. */
    xClientSocket = FreeRTOS_socket( PF_INET,
                                     SOCK_STREAM,  /* SOCK_STREAM for TCP. */
                                     IPPROTO_TCP );

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

    /* If FREERTOS_SO_RCVBUF or FREERTOS_SO_SNDBUF are to be used with
    FreeRTOS_setsockopt() to change the buffer sizes from their default then do
    it here!.  (see the FreeRTOS_setsockopt() documentation. */

    /* If ipconfigUSE_TCP_WIN is set to 1 and FREERTOS_SO_WIN_PROPERTIES is to
    be used with FreeRTOS_setsockopt() to change the sliding window size from
    its default then do it here! (see the FreeRTOS_setsockopt()
    documentation. */

    /* Set send and receive time outs. */
    FreeRTOS_setsockopt( xClientSocket,
                         0,
                         FREERTOS_SO_RCVTIMEO,
                         &xTimeOut,
                         sizeof( xTimeOut ) );

    FreeRTOS_setsockopt( xClientSocket,
                         0,
                         FREERTOS_SO_SNDTIMEO,
                         &xTimeOut,
                         sizeof( xTimeOut ) );

    /* Bind the socket, but pass in NULL to let FreeRTOS+TCP choose the port number.
    See the next source code snipped for an example of how to bind to a specific
    port number. */
    FreeRTOS_bind( xClientSocket, NULL, xSize );

E agora, o TCP server:

void vCreateTCPServerSocket( void )
{
struct freertos_sockaddr xClient, xBindAddress;
Socket_t xListeningSocket, xConnectedSocket;
socklen_t xSize = sizeof( xClient );
static const TickType_t xReceiveTimeOut = portMAX_DELAY;
const BaseType_t xBacklog = 20;

    /* Attempt to open the socket. */
    xListeningSocket = FreeRTOS_socket( PF_INET,
                                        SOCK_STREAM,  /* SOCK_STREAM for TCP. */
                                        IPPROTO_TCP );

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

    /* If FREERTOS_SO_RCVBUF or FREERTOS_SO_SNDBUF are to be used with
    FreeRTOS_setsockopt() to change the buffer sizes from their default then do
    it here!.  (see the FreeRTOS_setsockopt() documentation. */

    /* If ipconfigUSE_TCP_WIN is set to 1 and FREERTOS_SO_WIN_PROPERTIES is to
    be used with FreeRTOS_setsockopt() to change the sliding window size from
    its default then do it here! (see the FreeRTOS_setsockopt()
    documentation. */

    /* Set a time out so accept() will just wait for a connection. */
    FreeRTOS_setsockopt( xListeningSocket,
                         0,
                         FREERTOS_SO_RCVTIMEO,
                         &xReceiveTimeOut,
                         sizeof( xReceiveTimeOut ) );

    /* Set the listening port to 10000. */
    xBindAddress.sin_port = ( uint16_t ) 10000;
    xBindAddress.sin_port = FreeRTOS_htons( xBindAddress.sin_port );

    /* Bind the socket to the port that the client RTOS task will send to. */
    FreeRTOS_bind( xListeningSocket, &xBindAddress, sizeof( xBindAddress ) );

    /* Set the socket into a listening state so it can accept connections.
    The maximum number of simultaneous connections is limited to 20. */
    FreeRTOS_listen( xListeningSocket, xBacklog );

    for( ;; )
    {
        /* Wait for incoming connections. */
        xConnectedSocket = FreeRTOS_accept( xListeningSocket, &xClient, &xSize );
        configASSERT( xConnectedSocket != FREERTOS_INVALID_SOCKET );

        /* Spawn a RTOS task to handle the connection. */
        xTaskCreate( prvServerConnectionInstance,
                     "EchoServer",
                     usUsedStackSize,
                     ( void * ) xConnectedSocket,
                     tskIDLE_PRIORITY,
                     NULL );
    }
}

Se tirar os comentários vai ficar mais parecido com uma anotação do que um código funcional!

Recomendo a visita ao site do FreeRTOS pra dar uma observada e torçamos para que esteja nativamente implementado no ESP_IDF o mais rápido possível. Serão vários artigos, torçamos!

Inscreva-se no nosso canal Manual do Maker no YouTube.

Também estamos no Instagram.

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.