Neste artigo vamos fazer uma breve introdução aos Sockets de Berkeley em linguagem C para ambientes UNIX e Linux. Basicamente os Sockets de Berkeley são um API, isto é, um conjunto de bibliotecas de funções para a programação sobre protocolos de comunicação.
O modelo Cliente – Servidor é composto por um conjunto de dois programas em execução que comunicam entre si. O servidor está sempre à espera de pedidos efectuados pelos clientes mas desconhece a sua localização. O cliente tem de conhecer obrigatoriamente a localização do servidor (IP) para poder ligar-se e comunicar com ele. O código do cliente e do servidor não têm de ser necessariamente diferentes, facilmente se desenvolve uma aplicação que tanto pode funcionar como cliente como para servidor. A grande diferença está no seu comportamento, ou seja, o servidor inicia a execução e aguarda uma ligação, o cliente inicia a execução e a ligação ao servidor.
Para dar um exemplo prático de como funcionam os sockets, vamos fazer um simples par de aplicações em que o cliente envia uma palavra com letras minúsculas para o servidor. O servidor trata de converter as letras em maiúsculas devolvendo a palavra ao cliente. Vamos utilizar o modo TCP do protocolo TCP/IP e o conjunto de aplicações são fechadas quando o cliente digita a palavra “exit”.
O cliente tem o seguinte código. De seguida vamos analisá-lo passo a passo para melhor compreensão do mesmo.
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/ioctl.h> #include <unistd.h> int main() { struct sockaddr_in target; int sock = socket(AF_INET, SOCK_STREAM, 0); char palavra[50], palavra2[50]; int ad1 = sizeof(target); bzero((char *)&target, ad1); target.sin_family = AF_INET; target.sin_addr.s_addr = inet_addr("127.0.0.1"); target.sin_port = htons(8450); if(connect(sock, (struct sockaddr *)&target, ad1) == -1) { close(sock); puts("Conexao falhada!"); exit(0); } do { scanf("%s", palavra); write(sock, palavra, 50); if(strcmp(palavra, "exit") != 0){ read(sock, palavra2, 50); printf("%sn", palavra2); } }while(strcmp(palavra, "exit") != 0); close(sock); return 0; }
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/ioctl.h> #include <unistd.h>
// definição das estruturas do cliente e do servidor struct sockaddr_in me, from; // criação dos sockets.o “sock” é declarado identicamente ao cliente int newSock, sock = socket(AF_INET, SOCK_STREAM, 0); // declaração das variáveis int tam = 0, i = 0; int ad1 = sizeof(me); char palavra[50], palavra2[50]; // inicializa a estrutura do cliente bzero((char *)&target, ad1);
// indica a família do protocolo target.sin_family = AF_INET; // especifica o endereço (IP) do servidor target.sin_addr.s_addr = inet_addr("127.0.0.1"); // porta que o programa vai usar target.sin_port = htons(8450);
// efectua a ligação ao servidor. Se falhar (por exemplo o servidor estar em baixo) o programa termina if(connect(sock, (struct sockaddr *)&target, ad1) == -1) { close(sock); puts("Conexao falhou!"); exit(0); }
Vamos usar um método de leitura de daods muito simples. Lê uma palavra do teclado enquanto esta for diferente de “exit”. Em seguida envia-a para o servidor e recebe-a com letras maiúsculas
do { scanf("%s", palavra); // envia para o servidor os dados contidos na variável “palavra” write(sock, palavra, 50); if(strcmp(palavra, "exit") != 0){ // recebe do servidor os dados e guarda-os na variável “palavra2” read(sock, palavra2, 50); printf("%sn", palavra2); } }while(strcmp(palavra, "exit") != 0);
close(sock);
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <ctype.h> int main() { struct sockaddr_in me, from; int newSock, sock = socket(AF_INET, SOCK_STREAM, 0); int tam = 0, i = 0; int ad1 = sizeof(me); char palavra[50], palavra2[50]; bzero((char *)&me, ad1); me.sin_family = AF_INET; me.sin_addr.s_addr = htonl(INADDR_ANY); me.sin_port = htons(8450); if(bind(sock, (struct sockaddr *)&me, ad1) == -1) { close(sock); puts("Porta Ocupada!"); exit(0); } listen(sock, 5); newSock = accept(sock, (struct sockaddr *)&from, (void *)&ad1); close(sock); for(;;){ read(newSock, palavra, 50); if(strcmp(palavra, "exit") != 0){ tam = strlen(palavra); for(i=0;i<tam;i++){ if((palavra[i]>='a') && (palavra[i]<='z')){ palavra2[i] = toupper(palavra[i]); } } palavra2[i] = '0'; write(newSock, palavra2, 50); } else{ close(newSock); exit(0); } } return 0; }
Segue a análise do código passo a passo
// definição das estruturas do cliente e do servidor struct sockaddr_in me, from; // criação dos sockets.o “sock” é declarado identicamente ao cliente int newSock, sock = socket(AF_INET, SOCK_STREAM, 0); // declaração das variáveis int tam = 0, i = 0; int ad1 = sizeof(me); char palavra[50], palavra2[50]; // inicializa a estrutura do servidor bzero((char *)&me, ad1);
// indica a família do protocolo me.sin_family = AF_INET; // fica associado a todos os endereços IP do host local me.sin_addr.s_addr = htonl(INADDR_ANY); // porta em que o servidor vai estar à escuta me.sin_port = htons(8450);
Se estiver ocupada é fechado o servidor
// se a porta estiver ocupada o servidor não pode correr e é terminado if(bind(sock, (struct sockaddr *)&me, ad1) == -1) { close(sock); puts("Porta Ocupada!"); exit(0); }
É criado um novo socket para tratar apenas deste pedido enquanto que o outro socket continua à espera de pedidos:
GeSHi (c): // coloca o socket à escuta. Podem ser mantidos em espera 5 pedidos de ligação listen(sock, 5); // gera um novo socket específico para essa ligação newSock = accept(sock, (struct sockaddr *)&from, (void *)&ad1); // fechar o socket antigo close(sock);
for(;;){ // recebe os dados do cliente e guarda-os na variável “palavra” read(newSock, palavra, 50); if(strcmp(palavra, "exit") != 0){ tam = strlen(palavra); for(i=0;i<tam;i++){ if((palavra[i]>='a') && (palavra[i]<='z')){ palavra2[i] = toupper(palavra[i]); } } palavra2[i] = '0'; // envia os dados que estão na variável “palavra2” para o cliente write(newSock, palavra2, 50); } else{ close(newSock); exit(0); } }