quinta-feira, outubro 13, 2022

Usando o WiFi da Raspberry Pi PIco W (Parte 4)

O próximo passo do estudo é brincar um pouco com comunicação TCP.  No pico-examples tem exemplos do client e do server, usando a mesma estrutura esquisita do exemplo de NTP. Vamos ver se conseguimos entender o que é feito.


Os dois exemplos são feitos para conversarem um com o outro. O cliente se conecta ao server, que envia uma mensagem. O cliente recebe esta mensagem e a transmite de volta. Isto se repete algumas vezes e o teste é concluído.

Não vou analisar a parte de conexão ao WiFi, que já vimos anteriormente.

TCP Client

O código pode ser visto em https://github.com/raspberrypi/pico-examples/blob/master/pico_w/tcp_client/picow_tcp_client.c

Como no UDP, o estado da comunicação é guardado em uma estrutura "pcb" (no caso tcp_pcb). A criação desta estrutura, o cadastro dos callbacks é o disparo da conexão estão na rotina tcp_client_open:

// Versão simplificada e comentada
static bool tcp_client_open(void *arg) {
	
    // cria o PCB (Protocol Control Block)
    state->tcp_pcb = tcp_new_ip_type(IP_GET_TYPE(&state->remote_addr));

    // Define o argumento que será passado aos callbacks
    tcp_arg(state->tcp_pcb, state);
	
    // Define um callback chamado perioridamente
    tcp_poll(state->tcp_pcb, tcp_client_poll, POLL_TIME_S * 2);
    
    // Define um callback chamado quando um transmissão foi confirmada pelo receptor
    tcp_sent(state->tcp_pcb, tcp_client_sent);
    
    // Define um callback chamado quando dados são recebidos
    tcp_recv(state->tcp_pcb, tcp_client_recv);
    
    // Define um callback chamado quando ocorre um erro fatal
    tcp_err(state->tcp_pcb, tcp_client_err);

    // Dispara a conexão, tcp_client_connected é chamado se e quando a conexão
    // tiver sucesso
    cyw43_arch_lwip_begin();
    err_t err = tcp_connect(state->tcp_pcb, &state->remote_addr, 
                        TCP_PORT, tcp_client_connected);
    cyw43_arch_lwip_end();

    return err == ERR_OK;
}

Ok, dá para entender. O funcionamento todo da comunicação TCP é através de callbacks , com o lwIP chamando as suas rotinas quando algo acontece. Vamos ver o que cada um dos callbacks faz:

  • tcp_client_poll(): só faz um print para dizer que foi chamada.
  • tcp_client_err(): imprime uma mensagem de erro
  • tcp_client_connected(): atualiza o estado da aplicação para esperar o server enviar algo.
  • tcp_client_recv(): recebe os dados e, opcionalmente, os imprime. É necessário chamar tcp_recved() para confirmar o recebimento. Os dados são enviados de volta ao servidor, através de tcp_write().
  • tcp_client_sent(): verifica se o teste foi concluído, se não volta a aguardar dados
Ao terminar o teste os callbacks são cancelados e a conexão é fechada.

TCP Server

A iniciação do server (em tcp_server_open) é composta por chamadas a:

  • tcp_bind() para indicar qual porta será TCP monitorada
  • tcp_listen_with_backlog(): prepara para receber conexões, devolve um pcb específica para isso. As chamadas serão colocadas em uma fila (no caso com apenas uma posição)
  • tcp_accept(): aguarda receber uma conexão e define a rotina que será chamada quando isso acontecer.
O próximo passo é dado por tcp_server_accept(), que é chamada quando uma conexão é estabelecida. Esta rotina define os callbacks para poll, err, recv e sent (como no client) e usa tcp_write para enviar dados ao client.

O callback de sent prepara o server para receber a resposta do client. Quando a resposta vem (em recv) ela é conferida. Tudo ok, se o teste não terminou, envia outro pacote de dados. 

Ao terminar o teste os callbacks são cancelados e a conexão é fechada.

Alguns Comentários

Uma vez acostumado com a estrutura dos exemplos não foi difícil entender os programas. A interface do lwIP para TCP não é muito complexa (desde que você esteja habituado a trabalhar com callbacks).

Os exemplos são feitos para rodar uma única vez o teste. O server não volta a fazer um accept ao final do teste para atender um novo client.

Com base nestes exemplos (e mais um tanto de código) não parece difícil implementar uma aplicação que conversa com outra via pacotes TCP simples. Mas o padrão atual é usar APIs REST e é isso que vamos tentar na próxima parte.

Nenhum comentário: