Socket é o nome dado a qualquer uma das duas pontas (endpoints) de uma conexão entre dois processos, estabelecida sobre uma rede de comunicação. Leia a definição novamente. Atente para cada detalhe. Semelhante ao que ocorre quando usamos pipes, sockets são parte de um mecaniso de comunicação entre processos. A diferença relevante é que, neste caso, trata-se de um “pipe” estabelecido sobre uma rede de comunicação, permitindo que os processos que se comunicam estejam executando em máquinas diferentes, desde que conectadas através da rede. Embora o conceito seja mais amplo, na prática, sockets são definidos no contexto de redes TCP/IP. Isto significa que, em princípio, o conceito de sockets e a respectiva conexão entre eles nos permitem a interação de processos em quaisquer computadores ligados à Internet.
Tal como o conceito de arquivo, um socket é uma abstração provida pelo sistema operacional. Sockets abstraem os dispositivos e recursos de rede disponíveis na máquina e na própria rede de comunicação para que processos possam utilizá-las de forma simples. Melhor que isso: a abstração provida pelo sistema operacional enquadra a conexão no mesmo modelo conceitual de streams em que já temos outras entidades, tais como: processos, dispositivos e arquivos.
O poder de tal abstração é enorme. Significa que todo o corpo de conhecimento existente sobre sistemas criados através de comunicação entre processos em uma mesma máquina pode ser aproveitado e tomado como ponto de partida para a compreensão de sistemas distribuídos (ou aplicações distribuídas).
A figura abaixo ilustra os elementos envolvidos em uma comunicação entre dois processos através de sockets e sobre uma conexão na Internet. Observe que a figura não difere muito das que usamos para a compreensão de pipes. A principal diferença é que a conexão é viabilizada através da Internet ao invés de um pipe do sistema operacional. Do ponto de vista do desenvolvimento de software, contudo, permite pensar na conexão como algo semelhante a um pipe (ou um par deles, já que é bidirecional).
FIGURA 6
Do ponto de vista mais técnico, sockets são proporcionados pela porção do SO e/ou do hardware que implementa os protocolos de rede. São, portanto, providos como funcionalidade básica do sistema operacional e independem da linguagem de programação. Às linguagens de programação cabe oferecer bibliotecas de acesso às funcionalidades que permitam o uso de sockets de forma mais apropriada para a construção de aplicações.
A forma mais popular de acesso a sockets é através de APIs que seguem o modelo conhecida como sockets Berkeley. A grande maioria das linguagens hoje provê APIs para sockets baseadas desse tipo. A ideia central é seguir à risca a filosofia Unix e tratar sockets de acordo com a abstração de arquivos (em Unix tudo é arquivo). Assim, para o programador de aplicações, um socket se apresenta na forma de um streams bidirecional (ou dois streams se você preferir), em que o processo pode ler e/ou escrever caracteres para se comunicar com o processo na outra ponta da conexão.
É de conhecimento comum que toda máquina conectada à Internet tem um número único chamado IP (de Internet Protocol). Contudo, sockets conectam processos, não apenas as máquinas. Isso significa que uma mesma máquina pode hospedar múltiplos processos e cada um deles pode se comunicar através de sockets. Por isso, para que um processo se conecte a outro não basta o endereço IP da máquina.
Todo sockets criado pelo sistema operacional recebe um identificar único na máquina, chamado porta. Esse identificador serve como complemento do endereço IP, para identificar unicamente o socket e, indiretamente, o serviço provido pelo processo que opera o socket. Portas são um números inteiros entre 0 e 65535 (um inteiro sem sinal de 16 bits). Assim, o endereço de um serviço na internet consiste no par formado pelo IP e pela porta.
Observação. Um mesmo processo pode criar vários sockets e, portanto, pode dispor de vários endereços na Internet. Por isso, costumamos dizer que um par IP + porta identifica um serviço.
Portas com valores abaixo de 1024, chamadas de portas privilegiadas, são reservados para serviços especiais e não podem ser usadas por usuários sem acesso administrativo ao sistema (por isso, costumam ser consideradas mais confiáveis). Serviços muito usados costumam usar portas baixas pré-estabelecidas, tais como: ftp (20), ssh, email/smtp (25), DNS (53), HTTP (80), HTTPS (443), etc.
Portas com valores iguais ou acima de 1024 são portas não privilegiadas, também ditas portas altas, e podem ser usadas para criar serviços por qualquer usuário do sistema. Mais abaixo veremos um pequeno exemplo em que criaremos um socket. Como faremos isso sem uso privilegiado do sistema, usaremos uma porta alta.
Nada melhor para entender um conceito que exercitá-lo na prática.
Para isso, usaremos uma ferramenta de linha de comando chamada
netcat que vem pré-instalada
em várias distribuições Linux, ou pode ser facilmente
instalada através dos gerenciadores de pacotes das distribuições
(usuários Mac, podem usar a versão chamda nc; a sintaxe é
praticamente a mesma). Se você não conseguir instalar nenhuma
delas, tente usar uma máquina
Primeiro, vamos usar netcat para criar um servidor em uma porta qualquer, digamos 2197 (pode ser qualquer porta alta de sua escolha que você saiba que não está sendo usada).
$ nc -l 2197
Logo ao executar o comando, ainda não há qualquer conexão e, portanto, estritamente, não há um socket em operação (a biblioteca em uso, contudo, pode chamar de socket o objeto usado para interagir com a API de Sockets e isso pode ser fonte de alguma confusão). As bibliotecas permitem que a espera por uma conexão seja feita de forma bloqueante. Assim, o processo em espera por uma conexão ficará bloqueado até que uma conexão seja estabelecida.
Criemos um cliente para nosso servidor. Usando outro terminal,
use o comando abaixo para criar um processo cliente que deve se
conectar ao servidor. Observe que, desta vez, não usamos o
argumento -l usado acima para fazer netcat ouvir (l de
listen). Mas, em compensação, usamos o argumento 127.0.0.1.
Esse argumento é o IP de nosso servidor. Observe que se trata de
um IP especial que, na verdade, representa a própria máquina de
onde se origina a conexão.
$ nc 127.0.0.1 2197
O resultado do comando deve ser silencioso. Uma vez estabelecida a conexão, os dois processos (cliente e servidor) ficam bloqueados esperando por dados. Para testar se está funcionando, digite qualquer coisa em qualquer um deles e, ao final, digite Enter. Se você tiver os dois terminais visíveis, você verá que tão logo digitar o Enter a mensagem será copiada para o outro processo e netcat irá exibí-la no terminal associado.
Lembre que a conexão TCP à qual os sockets estão associados é bidirecional. Isso significa que ambos os processos podem atuar como fonte do texto. Teste isso. Digite mensagens em ambos os terminais e verifique que será copiada para o outro terminal.
Apesar de ser interessante, nosso exemplo de sockets e de conexão TCP funciona com cliente e servidor em uma mesma máquina. Não parece tão diferente do que o que já podemos fazer com um pipe, por exemplo. Contudo, o mesmo exercício pode ser colocado para funcionar em duas máquinas independentes.
Para isso, você precisará de duas máquinas que estejam conectadas à Internet. Há, contudo, um pequeno problema antes de prosseguir. Relembre que a conexão TCP atravessará todos os domínios administrativos entre as duas máquinas. Isso significa que a conexão estará sujeita às regras das máquinas e também desses domínios. Na prática, é comum que administradores de rede “fechem as portas”. Veja que não é mera figura de linguagem. É que muitos administradores têm por política fechar o acesso às portas altas (tanto TCP quanto UDP) de suas redes, impedindo que serviços sejam colocados à disposição livremente. Assim, você tem que se assegurar que as duas máquinas em que você vai fazer o exercício têm as portas liberadas.
Dica. Nos laboratórios de ensino do curso de Ciência da Computação da UFCG é possível fazer o experimento usando duas máquinas dentro de um mesmo laboratório. Infelizmente, as portas altas entre laboratórios diferentes estão fechadas.
Observe que o comando para criar o servidor continua o mesmo, dado que netcat acha o IP local por conta própria. O comando para lançar o cliente, contudo, requer que você saiba o IP da máquina em que o servidor está sendo executado.
Caso você precise descobrir o IP, você pode usar o comando
ip addr show. Se não estiver disponível, uma alternativa simples é usar o browser e Google. No campo de busca digite: my IP.