Hands on: Introdução a sockets em C

Introdução a sockets em C

Desde sempre a comunicação via sockets é o mais baixo nível de comunicação que existe entre sistemas operacionais. Aplicações como browsers, mensageiros instantâneos(Facebook Message, WhatsApp, Google Hangout, etc.), visualização de área de trabalho remota, conexões telnet/ssh, enfim, qualquer troca de informações entre dois ou mais computadores é feito com sockets.

Nota-se também que processos de um mesmo computador podem se comunicar com um socket local. Este post do Wikipédia dá uma explicação bem detalhada entre sockets através de hosts diferentes e IPC (Inter Process Comunication) sockets, que são usados para troca de informações entre processos dentro da mesma máquina.

Para exemplificar como os sockets funcionam, vejamos o programa abaixo. Ele se trata de um servidor de horas e pode ser facilmente testado com uma conexão telnet. Para compilar basta invocar o GCC:

Terminal
gcc server.c -o server

Explicando o código

Segue explicação do código que tange o assunto sockets:

struct sockaddr_in serv_addr;
Aqui é declarada a estrutura básica para o servidor de horas. Esta estrutura é usada para descrever o socket no lado do servidor, como o protocolo a ser usado (ipv4 ou ipv6), quais endereços serão aceitos por ele, entre várias outras informações. Mais informações podem ser encontradas aqui.

serv_socket = socket(AF_INET, SOCK_STREAM, 0);
Declaração da chamada de sistema para criação do socket em si. A variável serv_socket vai receber um inteiro com o file descriptor do socket. O primeiro parâmetro diz respeito ao domínio utilizado na criação do socket. O domínio passado neste exemplo é o protocolo IP. Outros protocolos disponíveis são o IPX, X25, Appletalk, etc. O segundo parâmetro diz respeito ao tipo de comunicação. Neste exemplo usamos TCP(SOCK_STREAM), mas existem outras configurações possíveis para este parâmetro como SOCK_RAW, SOCK_DGRAM(comunicação UDP), SOCK_SEQPACK, etc. O último parâmetro diz ao protocolo. Neste exemplo utilizamos o valor 0, que informa ao kernel que ele escolha o protocolo baseado no domínio e tipo de comunicação. Para mais informações sobre esta chamada de sistema veja aqui.

serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = INADDR_ANY;

Da mesma forma, aqui estamos populando a estrutura previamente criada. sin_family terá também o mesmo valor do domínio do socket. sin_port terá o número da porta que o socket irá usar para esperar por novas conexões. s_addr é o endereço do host. Neste caso colocar INADDR_ANY pois não nos importamos com o valor.

bind(serv_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr))
A chamada bind faz com que o socket previamente seja atribuído a um endereço/porta. Desta forma a chamada bind recebe o file descriptor do socket e nossa estrutura sockaddr_in.

listen(serv_socket, MAX_LISTEN)
A chamada listen faz com que o socket passado como parâmetro entre no modo passivo, ou seja, que está apto a receber conexões. O segundo parâmetro é o número de pacotes que podem ser enfileirados enquanto o servidor está processando um pacote.

cli_socket = accept(serv_socket, (struct sockaddr *)NULL, NULL);
A chamada accept faz com que o servidor espere por novas conexões. Seu retorno é um novo file descriptor, mas desta vez relacionado a conexão do cliente. O segundo parâmetro espera uma estrutura sockaddr_in. Se esta for informada, a chamada accept popula a estrutura com informações do host, como seu IP por exemplo. O terceiro parâmetro espera o tamanho da estrutura.

send(cli_socket, buf, sizeof(buf), 0);
A chamada send envia dados ao socket passado como primeiro parâmetro. Os dados são passados como segundo parâmetro da função, e o terceiro parâmetro espera o tamanho dos dados a serem enviados.

close(cli_socket);
Esta chamada close fecha o socket aberto para o cliente pela chamada accept. Como no mundo UNIX tudo são arquivos, sockets não são uma exceção.

// não deveria chegar aqui…
close(serv_socket);

Este fecha o socket do lado do servidor. O comentário de código informa que esta parte não deve ser executada pois um servidor deve ficar sempre executando a fim de “servir” os clientes.

Para fazer um teste basta executar um telnet na porta 9999 da maquina:
bash-4.2$ telnet localhost 9999
Trying 127.0.0.1…
Connected to localhost.
Escape character is ‘^]’.
Sat Dec 6 11:53:22 2014
Connection closed by foreign host.