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:
- Possuir um Intel Edison;
- Possuir Arduino Expansion Board e Base Shield (ambos presentes no kit de sensores Groove);
- Conhecer comandos básicos de Linux;
- Dominar alguma forma de acesso à Intel Edison (conexão via terminal serial ou SSH). Aqui eu gostaria de recomendar o uso do PuTTY;
- Conhecimentos básicos de linguagem C;
- Possuir um hyperterminal qualquer no computador cliente (o próprio PuTTY pode ser usado para isto).
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:
- O cliente deve “saber” que o servidor existe (ou seja, qual seu IP e porta disponíveis para comunicação);
- O servidor normalmente não “sabe” da existência do cliente, tão quanto garante exclusividade de conexão com um determinado cliente. Logo, qualquer programa capaz de se conectar ao IP e uma das portas disponíveis para comunicação do servidor, é certamente capaz de se comunicar com o servidor;
- Normalmente, o detentor das informações mais importantes da comunicação é o servidor. Logo, em geral, o cliente é quem solicita informações e envia comandos ao servidor.
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.
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:
- Para aqueles que estão iniciando nos estudos e projetos com o Intel Edison, recomendo a leitura do artigo do Pacman;
- Para os que querem se aprofundar mais em sockets em linguagem C, recomendo a leitura deste site;
- Ah, não deixe de conferir também a publicação deste projeto no Instructables:

