Como Programar Sockets
Links úteis:
Informações de Contato:
Programming in C
Nome: Andre Leon S. Gradvohl
Biblioteca GNU para Linguagem C
Email: andre_gradvohl@yahoo.com
Tutorial de Makefile

Essa página contém informações a respeito de como programar sockets nos sistemas operacionais UNIX e Linux.

Sockets são uma abstração do sistema operacional (S.O.) para prover comunicação ponto-a-ponto entre processos. Esses processos podem estar na mesma máquina, em máquinas distintas com o mesmo S.O. e até mesmo em máquinas distintas com S.Os. diferentes. Todavia, para que os processos se comuniquem, eles precisam estar de acordo a respeito do tipo de socket utilizado.

Para facilitar a compreensão, define-se como servidor o processo que fica bloqueado aguardando a solicitação de conexão de um outro processo - o cliente.

Existem cinco tipos diferentes de sockets (em 23 domínios diferentes). Na Internet, dois deles são largamente utilizados:

1. Stream Socket ( SOCK_STREAM ): provê um fluxo de dados sequencial, em duas vias e seguro. No domínio da Internet é o equivalente ao protocolo TCP.
2. Datagram Socket ( SOCK_DGRAM ): suporta um fluxo de dados em duas vias. Nesse caso, os processos podem receber as mensagens em uma ordem diferente da ordem em que as mensagens foram enviadas. No domínio da Internet é o equivalente ao protocolo UDP.

No exemplo a seguir, bem simples, programa-se um servidor que imprime a mensagem de um cliente na tela. Se essa mensagem for a string "saida" o servidor é encerrado. Baixe os arquivos servidor.c, cliente.c e Makefile que estão comprimidos no arquivo cliserv.zip (versão para windows: cliservWin.zip)para compilar o programa nos S.Os. Linux e/ou Unix.

Observe o código do cliente a seguir:

       #include <stdio.h>
       #include <sys/types.h>
       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <netdb.h> 

       int main(int argc, char *argv[])
       {
        int sockDesc;     
        int numPorta;     
        char buffer[256]; 
        int n;
 
/* 1 */ struct sockaddr_in endServ;
/* 2 */ struct hostent *servidor;

        if (argc < 3) 
        {
         fprintf(stderr,"Utilizacao: %s <end_servidor> <num_porta>\n", argv[0]);
         exit(0);
        }

         numPorta = atoi(argv[2]);
 
/* 3 */ sockDesc = socket(AF_INET, SOCK_STREAM, 0);

        if (sockDesc < 0) 
           erro("ERRO abrindo socket",1);

/* 4 */ servidor = gethostbyname(argv[1]);

        if (servidor == NULL) 
        {
          fprintf(stderr,"ERRO, servidor desconhecido\n");
          exit(0);
        }

/* 5 */ endServ.sin_family = AF_INET;
/* 6 */ bcopy((char *) servidor->h_addr, 
              (char *)&endServ.sin_addr.s_addr,
              servidor->h_length);
/* 7 */ endServ.sin_port = htons(numPorta);

/* 8 */ if (connect(sockDesc, &endServ , sizeof(endServ) ) < 0) 
            erro("ERRO na conexão",2);

          printf("Digite a mensagem: ");

/* 9 */ bzero(buffer,sizeof(buffer)); 
        fgets(buffer,sizeof(buffer),stdin);

/*10 */ n = write(sockDesc,buffer,strlen(buffer));
        if (n < 0) 
            erro("ERRO enviando mensagem pelo socket",3);

        bzero( buffer, sizeof(buffer) );
/*11 */ n = read(sockDesc,buffer,sizeof(buffer));
        if (n < 0) 
          erro("ERROR lendo do socket",4);

         printf("Mensagem recebida do servidor: %s\n",buffer);
        return 0;
       } 
Para que se possa usar os sockets é preciso incluir algumas bibliotecas que estão nas primeiras linhas. Nelas estão as primitivas usadas pelo cliente.

Na linhas /* 1 */ e /* 2 */ são definidas as estruturas que armazenarão os dados relativos ao socket que liga o cliente ao servidor e informações sobre o servidor propriamente dito.

Na linha /* 3 */ o socket é definido. A primitiva socket recebe três parâmetros: o domínio, o tipo de socket e o protocolo que será utilizado. Nesse caso, foram utilizados as constante AF_INET para indicar que é um socket para Internet, SOCK_STREAMpara indicar um serviço baseado em conexão e 0 (zero) para utilizar o protocolo default para isso, o TCP. O retorno da primitiva socket é um descritor que identifica o socket criado.

Na linha /* 4 */ a primitiva gethostbyname busca informações a respeito do servidor cujo nome foi passado como parâmetro.

Nas linhas /* 5 */, /* 6 */ e /* 7 */ são incluidas algumas informações na estrutura endServ. Essa estrutura servirá como parâmetro para a primitiva connect, na linha /* 8 */, que efetivamente solicita a conexão ao servidor.

Na linha /* 9 */, o buffer onde a mensagem que será enviada para o servidor é zerado. Na linha seguinte, o que for lido do teclado será armazenado no buffer.

Na linha /*10 */, a primitiva write escreve no socket especificado na variável sockDesc, o conteúdo do buffer, no tamanho do buffer. O retorno dessa primitiva é um valor menor que zero se houveram problemas, zero ou maior que zero, indicando quantos bytes foram transmitidos.

Na linha /*11 */, a primitiva read lê uma string do socket especificado na variável sockDesc e armazena o conteúdo no buffer, no tamanho do buffer. O retorno dessa primitiva é um valor menor que zero se houveram problemas, zero ou maior que zero, indicando quantos bytes foram transmitidos.

Observe agora o código do servidor:

        #include <stdio.h>
        #include <sys/types.h> 
        #include <sys/socket.h>
        #include <netinet/in.h>

        int main(int argc, char *argv[])
        {
         int sockDesc, novoSockDesc; 
         int numPorta;               
         char buffer[256];           
         int n, tamCli;

/* 1 */  struct sockaddr_in endServ; 
/* 2 */  struct sockaddr_in endCli; 
 
         if (argc < 2) 
         {
          fprintf(stderr,"ERRO!, nao especificou a porta\n");
          exit(1);
         }

/* 3 */  sockDesc = socket(AF_INET, SOCK_STREAM, 0); 
         if (sockDesc < 0) 
          erro("ERRO abrindo socket",2);

/* 4 */  numPorta = atoi(argv[1]);
         endServ.sin_family = AF_INET;
         endServ.sin_addr.s_addr = INADDR_ANY;
         endServ.sin_port = htons(numPorta);

/* 5 */  if (bind(sockDesc, (struct sockaddr *) &endServ, sizeof(endServ)) < 0) 
          erro("ERRO na ligação",3);

/* 6 */  while (1)
         {
/* 7 */     listen(sockDesc,5);

          tamCli = sizeof(endCli);
/* 8 */   novoSockDesc = accept(sockDesc, (struct sockaddr *) &endCli, &tamCli);

          if (novoSockDesc < 0) 
          {
            erro("ERROR on accept",4);
          }

/* 9 */   n = read(novoSockDesc,buffer,255);
          if (n < 0) 
            erro("ERRO lendo do socket",5);

          printf("Mensagem recebida: %s\n",buffer);

          if (!strncmp("saida\n",buffer))
          {
/* 10*/    n = write(novoSockDesc,"Servidor desativado",19);
           printf("Servidor sendo desativado!\n");
           if (n < 0) 
              erro("ERROR escrevendo no socket",7); 
           exit(0);
          }           
  
/* 11*/   n = write(novoSockDesc,"Mensagem recebida",17);
          if (n < 0) 
            erro("ERROR escrevendo no socket",7);
          bzero(buffer,sizeof(buffer));
    }
   }
Para que se possa usar os sockets é preciso incluir algumas bibliotecas que estão nas primeiras linhas. Nelas estão as primitivas usadas pelo servidor.

Na linhas /* 1 */ e /* 2 */ são definidas as estruturas que armazenarão os dados relativos ao socket que liga o cliente ao servidor e informações sobre o cliente propriamente dito.

A linha /* 3 */ é equivalente à do cliente. Nesse caso, o socket é reservado no servidor.

Na linha /* 4 */ as informações (tipo de socket, tipo de serviço e porta) a respeito do socket servidor são armazenadas na estrutura endServ que será utilizada em seguida na primitiva bind.

Na linha /* 5 */ é feita a ligação entre o socket e as informações armazenadas na linha /* 4 */.

A linha /* 6 */ estabelece um laço infinito que permitirá ao servidor permanecer no ar, prestando serviços a vários clientes.
Na linha /* 7 */ a primitiva listen é usada para bloquear o processo servidor enquanto aguarda conexões. Os dois parâmetros usados são o descritor do socket e a quantidade de conexões que pode aceitar.

Na linha /* 8 */ a primitiva accept é usada quando o servidor recebeu uma solicitação de conexão e precisa indicar que aceita tal solicitação. Os parâmetros usados são descritor do socket, a estrutura onde ficarão armazenadas as informações do cliente e o tamanho dessa estrutura.

Na linha /* 9 */, a primitiva read lê uma string do socket especificado na variável sockDesc e armazena o conteúdo no buffer, no tamanho do buffer. O retorno dessa primitiva é um valor menor que zero se houveram problemas, zero ou maior que zero, indicando quantos bytes foram transmitidos.

As linhas /* 10*/ e /* 11*/ usam a primitiva write para escrever no socket especificado na variável sockDesc, o conteúdo do buffer, no tamanho do buffer.