Neste artigo, será mostrado como desenvolver um programa básico de socket server TCP utilizando linguagem C, compilado e executado no Linux da placa Intel Edison. Com este exemplo, será possível estabelecer uma conexão via socket com a Intel Edison e ligar e desligar um led através de comandos enviados do cliente para o servidor (Intel Edison).

Pré-requisitos

Para conseguir compreender e reproduzir o exemplo aqui fornecido, será necessário:

Antes de botar a mão na massa, vamos falar um pouco sobre sockets.

Sockets

A grande maioria da comunicação entre processos distintos via rede (internet ou em rede local) utiliza o modelo Cliente-Servidor. Neste modelo, o cliente se conecta ao servidor e, a partir disto, a comunicação entre ambos é estabelecida até que um dos dois encerre a comunicação. Para isto funcionar, os seguintes pontos devem ser ressaltados: 

Em outras palavras, uma comunicação via socket é como se fosse uma ligação telefônica: há um servidor (pessoa a ser contactada) e um cliente (a pessoa que deseja contatar o servidor). O servidor possui uma identificação única na rede (na rede telefônica seria um número de telefone, já em uma rede de dados seria um IP e porta válidos) e o cliente, conhecendo esta identificação, é capaz de entrar em contato com o servidor e estabelecer uma conexão. Quando o cliente não desejar mais se comunicar ou quando o servidor precisar interromper a comunicação, a ligação é encerrada.

Na comunicação via socket as coisas funcionam exatamente da mesma maneira. Para compreender como a comunicação com base em sockets é feita no C, observe o diagrama da figura 1.

socket tcp em c
Figura 1 – Diagrama de comunicação de sockets TCP em C

Há dois tipos básicos de protocolos que os sockets podem operar: TCP e UDP. Sockets TCP são utilizados quando é necessário ter garantia que um dado enviado pelo remetente realmente chegou ao destinatário. Já UDP é utilizado quando o volume de dados a ser transmitido é grande e não há necessidade de garantia de recepção de todos os dados (exemplo: streaming de vídeo). O programa-exemplo deste artigo irá compreender o uso de socket no protocolo TCP.

Agora mãos à obra!

Uma vez que já foram revistos os conceitos básicos de sockets, vamos botar a mão na massa! 

No programa-exemplo deste artigo, o Intel Edison será o servidor, capaz de se comunicar pela porta 8888 com um cliente. Uma vez estabelecida a conexão, uma mensagem de boas vindas é enviada ao cliente, bem como o número de seu IP (obtido pelo servidor). Além disso, após estabelecida a conexão, se for recebido um ‘L’ do cliente, a Intel Edison irá acender um led. Já se for recebido um ‘D’, a Intel Edison irá apagar o led.

 O código-fonte do exemplo é o seguinte:

/*----------------------------------------------------------------------------
 * Programa: Comunicação via Socket TCP em C (compilado e  executado no Linux)
 * Hardware utilizado para desenvolviemnto e teste: Intel Edison +
 *                                                 Arduuino Expansion Board
 *
 * Autor: Pedro Bertoleti												 
------------------------------------------------------------------------------*/
//includes
#include <stdio.h>
#include <string.h>     //incluido para usar strlen
#include <sys/socket.h> 
#include <arpa/inet.h>  //incluido para usar inet_addr
#include <unistd.h>     //incluido para usar write (enviar mensagens para client)
#include "mraa.h"      //incluido para manipulação de GPIO na Intel Edison

//defines
#define TAM_MAX_MENSAGEM_BOAS_VINDAS   300
#define TAM_MAX_MENSAGEM_STATUS_LED    100  
#define TAM_MAX_MENSAGEM_CLIENT        2000 
#define NUM_MAX_CONEXAO_CLIENTS        1
#define GPIO_LED                       2      //GPIO escolhido para colocar o LED. 
#define PORTA_SOCKET_SERVER            8888   //Nota: o valor da porta pode ser qualquer um entre 2000 e 65535. Caso der erro ao fazer o bind (ou seja, 
                                              //a porta escolhida já estiver em uso, troque este valor por qualquer outro no range válido.


int main(int argc , char *argv[])
{
    int socket_desc , client_sock , c , read_size;           //socket_desc: descriptor do socket servidor 
	                                                         //client_sock: descriptor da conexao com o client
													         //read_size: contem o tamanho da estrutura que contem os dados do socket 
    struct sockaddr_in server , client;                      //server: estrutura com informações do socket (lado do servidor)
	                                                         //client: estrutura com informações do socket (lado do client) 
    char client_message[TAM_MAX_MENSAGEM_CLIENT];       //array utilizado como buffer dos bytes enviados pelo client
    char MensagemBoasvindas[TAM_MAX_MENSAGEM_BOAS_VINDAS];   //array que contem a mensagem de boas vindas (enviada no momento que a conexao e estabelecida)
	char MensagemStatusLed[TAM_MAX_MENSAGEM_STATUS_LED];     //array que contem a mensagem do status do LED (enviada apos qualquer alteracao do status do LED)
	mraa_gpio_context gpio;                                  //variavel utilizada para manipular um gpio (o tipo dela, mraa_gpio_context, vem do MRAA)
	
    //inicializa MRAA
    mraa_init();	
	
	//configura GPIO (designa um GPIO e configura sua direcao como saida)
    gpio = mraa_gpio_init(GPIO_LED);
    mraa_gpio_dir(gpio, MRAA_GPIO_OUT);	 
	 
    //Tenta criar socket
    socket_desc = socket(AF_INET , SOCK_STREAM , 0);
    if (socket_desc == -1)
    {
        printf("Impossivel criar socket");
    }
    puts("Socket criado com sucesso!");
     
    //Prepara a estrutura de socket do servidor (contendo configurações do socket, como protocolo IPv4, porta de comunicacao e filtro de ips que podem se conectar)
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons( PORTA_SOCKET_SERVER );
     
    //Tenta fazer Bind (informa que o referido socket operara na porta definida por PORTA_SOCKET_SERVER)
    if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0)
    {
        perror("Erro ao fazer bind");
        return 1;
    }
    puts("Bind feito com sucesso!");
     
    //Faz o Listen. É permitido apenas uma conexao no socket
    listen(socket_desc , NUM_MAX_CONEXAO_CLIENTS);
     
    //Aguarda uma conexao
    puts("Aguardando conexao...");
    c = sizeof(struct sockaddr_in);
	
    client_sock = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c);
	
	//foi recebido um pedido de conexao. Verifica se o pedido foi bem sucedido
	if (client_sock < 0)
	{
		perror("Falha ao aceitar conexao");
		return 1;
	}
	puts("Conexao aceita!");
    
	//mensagem de boas vindas 
	sprintf(MensagemBoasvindas,"Bem vindo ao socket - Intel Edison IoT\n\rSeu IP eh: %s\n\n\r", inet_ntoa(client.sin_addr));
	write(client_sock , MensagemBoasvindas , strlen(MensagemBoasvindas));
		
	//Aguarda receber bytes do client
	while( (read_size = recv(client_sock , client_message , 2000 , 0)) > 0 )
	{
		//Send the message back to client
		if (client_message[0]=='L')
		{
			//liga led
			sprintf(MensagemStatusLed,"O Led foi ligado\n\r");
			mraa_gpio_write(gpio, 1);
		}
	
		if (client_message[0]=='D')
		{
			//desliga led
			sprintf(MensagemStatusLed,"O Led foi desligado\n\r");
			mraa_gpio_write(gpio, 0);
		}
	
		write(client_sock , MensagemStatusLed , strlen(MensagemStatusLed));
		memset(MensagemStatusLed,0,TAM_MAX_MENSAGEM_STATUS_LED);
		memset(client_message,0,TAM_MAX_MENSAGEM_CLIENT);
	}
    
	//client se desconectou. O programa sera encerrado.
	if(read_size == 0)
	{
		puts("Client desconectado. A aplicacao sera encerrada.");
		mraa_gpio_close(gpio);
		fflush(stdout);
                close(client_sock);   //fecha o socket utilizado, disponibilizando a porta para outras aplicacoes
	}
	else if(read_size == -1)  //caso haja falha na recepção, o programa sera encerrado
	{
		perror("recv failed");
		mraa_gpio_close(gpio);
	}
    
	return 0;
}

Instruções de como colocar o código no Intel Edison, compilar e rodar

Assumindo que você já esteja conectado à placa Intel Edison via um terminal serial ou SSH, que você esteja logado e que a Intel Edison está conectada na rede via Wi-Fi nativo dele, o primeiro passo é saber seu IP. Para isto, digite o seguinte comando: 

ifconfig

Como resultado do comando, irão aparecer todas as interfaces de rede e usb presentes no momento. Procure por wlan0 e, nos dados da interface, constará o IP. Anote-o, ele será útil mais pra frente neste artigo.

Feito isto, devemos criar o arquivo .c do programa. Para isso utilizaremos o vi. Para tal, digite o seguinte comando: 

vi socketserver.c

No vi, aperte a letra ‘I’ para entrar em modo de inserção. Agora, copie o código-fonte e cole no vi. Para colar o código-fonte, pressione o botão direto do mouse sobre qualquer área da tela do terminal serial/SSH. Feito isso, preesione a tecla ESC, digite dois pontos, digite wq e depois dê Enter. Esta combinação (que mais parece um macete de videogame) garante que o arquivo socketserver.c será salvo e que após fazer isso o vi seja fechado.

Agora estamos prontos para compilar e rodar o código. Para compilar, digite o seguinte comando:

gcc socketserver.c -o socketserver -lmraa

Após a compilação, podemos rodar o programa. Para isso, digite:

./socketserver

Pronto, agora você está pronto para se comunicar via socket com sua Intel Edison! Para isto, basta utilizar um programa hyperterminal, conectar-se ao IP da Intel Edison e na porta informada no código-fonte.

Para ver o projeto em ação, veja este vídeo.

Referências

Como referências, recomendo duas fontes interessantes: