Sockets de Berkeley
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 uma API, isto é, um conjunto de bibliotecas de funções para a programação sobre protocolos de comunicação.
Modelo Cliente – Servidor
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”.
Cliente
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("%s\n", palavra2);
}
}while(strcmp(palavra, "exit") != 0);
close(sock);
return 0;
}
Bibliotecas necessárias
#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>
Definir e inicializar a estrutura de destino (servidor) e as variáveis
// 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);
Definir as propriedades do servidor ao qual nos vamos ligar
// 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);
Estabelecer a ligação ao servidor
// 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);
}
Ler dados do teclado
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("%s\n", palavra2);
}
}while(strcmp(palavra, "exit") != 0);
Fechar o socket
close(sock);
Servidor
#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
Definir a estrutura do cliente, do servidor e as variáveis
// 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);
Definir as propriedades do servidor
// 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);
Alocar a porta
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);
}
Esperar por pedidos
É 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);
Ciclo infinito que trata dos pedidos do cliente
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);
}
}