Introdução à Programação com Sockets Fernando Jorge Silveira Filho [email protected] Daniel Sadoc Menasché [email protected]
Apr 07, 2016
Introdução à Programação com Sockets
Fernando Jorge Silveira [email protected]
Daniel Sadoc Menasché[email protected]
Visão Geral
• Objetivos:– Realizar comunicação entre processos.– Entender como usar as funções da API
sockets.• Histórico da API sockets: desenvolvida na
época da implementação dos protocolos TCP/IP pelo grupo da UC, Berkeley.
• Inicialmente iremos (re)ver alguns conceitos que serão importantes.
Rotinas do S.O.
Sockets
Comandos no Hardware
Rotinas do Driver
API Sockets
• Application Program Interface – elemento de ligação entre a aplicação e um sistema de mais baixo nível.
Aplicação
Transporte
Rede
Enlace de Dados
Física
Sistema Operacional(Kernel)
Drivers e Hardware
Aplicação Final
Conceitos Importantes
• Argumentos de Valor/Resultado• Descritores de Arquivo• Chamadas de Sistema de E/S• Byte Ordering• Portas e Endereços IP• Associações & Conexões• Serviços de Transporte
Argumentos de Valor/Resultado• É possível (e comum) na linguagem C, utilizar o recurso
de passagem de argumentos por referência para passar valores para uma função ao mesmo tempo em que se espera um valor de retorno na mesma variável.
• Exemplo: imagine uma função is_prime() que retorna 1 se um número apontado por um ponteiro n é primo e 0 (zero) se composto. Neste último caso, a função também retorna o número primo mais próximo do valor passado. Este segundo valor de retorno pode ser colocado no próprio endereço de memória referenciado por n.
int is_prime( int * n ){ /* verifica se (*n) é primo e em caso negativo coloca o primo mais próximo em (*n) */}
Descritores de Arquivos
• Números inteiros não negativos que são ponteiros para estruturas de dados do sistema que representam os arquivos abertos por um processo.
7 26 15 50 39 21xDescritores
de Arquivo yz
Imagem do Processo Estruturas do S.O.
zz
Chamadas de Sistema de E/S
• Operações básicas que podem ser realizadas com arquivos.
• open, close, read, write.#include <unistd.h>
int open( const char * filename, int oflag, ... );
int close( int fd );
size_t read( int fd, void * buffer, size_t n_bytes );size_t write( int fd, void * buffer, size_t n_bytes );
• Como um valor é armazenado na memória?• Exemplo: 16909060 = 0x01020304
• Para evitar conflitos entre hosts de arquiteturas diferentes é convencionado que informações de controle na rede são armazenadas em Big-Endian.
Byte Ordering
Big-Endian Little-Endian
01
02
03
04
n
n+1
n+2
n+3
Endereço Memória
04
03
02
01
n
n+1
n+2
n+3
Endereço Memória
Endereços IP e Portas• Endereços IP identificam hosts na rede TCP/IP
e são formados por 4 bytes, normalmente chamado octetos por razões históricas.
• Portas são identificadores dos processos interessados em usar os recursos de rede em um host. Portas ocupam 2 bytes na memória.
• Ambos tipos de variáveis são armazenadas no byte order da rede (Big-Endian).
146 164 10 2 39990
92 A4 0A 02 9C 36Notação Padrão
Hexadecimal
Associações
• Um processo precisa associar um socket a um endereço para “avisar” ao sistema operacional que deseja receber dados que chegam ao host com o endereço de destino especificado.
602789
1026
102
Processos2023
146.164.41.10 : 80
2541
146.164.41.10 : 21
146.164.41.10 : 20
EndereçoAssociadoS
ocke
ts
602789
1026
102
Processos2023
146.164.41.10 : 80
2541
146.164.41.10 : 21
146.164.41.10 : 20
146.164.41.10 : 56
EndereçoAssociadoS
ocke
ts
Conexões
• Um socket pode ser conectado a um endereço diferente do seu próprio para que pacotes possam ser enviados por ele diretamente ao processo parceiro.
602Processo
20
EndereçoAssociadoS
ocke
t
200.128.40.12 : 10020
EndereçoConectado
146.164.41.10 : 13003
Serviços de Transporte
• A camada de transporte dos protocolos TCP/IP fornece duas opções de tipos de serviço:– Serviço orientado a conexão: utiliza o
protocolo TCP (Transmission Control Protocol) e garante entrega e ordenação.
– Serviço datagrama: utiliza o protocolo UDP (User Datagram Protocol) e não faz nenhuma garantia.
Esquema de umaAplicação TCP
• Um par de aplicações que se comunicam por TCP em geral tem a seguinte forma:
accept( )
close( )
connect( )
socket( )
socket( )
bind( )
listen( )
close( )
Estabelecimentoda Conexão TCP
Cliente
Servidor
writen( )
readn( )
readn( )
writen( )
readn( )
Esquema de umaAplicação TCP
• Um par de aplicações que se comunicam por TCP em geral tem a seguinte forma:
accept( )
close( )
connect( )
socket( )
socket( )
bind( )
listen( )
close( )
Estabelecimentoda Conexão TCP
Cliente
Servidor
writen( )
readn( )
readn( )
writen( )
readn( )
ter telefone associar número
ao telefoneligar alarme
responder chamadafazer chamada
Sockets - Criação• Utiliza-se a chamada de sistema socket():
• Parâmetros:– domain – o tipo de rede do socket. Usamos AF_INET (Address
Family Internet). Outros tipos: AF_LOCAL, AF_INET6.– type – o tipo de serviço de transporte. Para sockets TCP usamos
SOCK_STREAM, para UDP usamos SOCK_DGRAM.– protocol – o protocolo utilizado. Passando 0 (zero) é utilizado o
padrão.• A função retorna um descritor do socket criado ou –1 em
caso de erro.
#include <sys/types.h>#include <sys/socket.h>
int socket( int domain, int type, int protocol );
Sockets - Endereços• Endereços IP e portas são
armazenados em estruturas do tipo struct sockaddr_in.
• sin_family é o tipo do endereço. AF_INET deve ser usado.
• sin_port é a porta associada ao endereço.
• sin_addr é uma estrutura que contém o endereço IP (os 4 octetos).
• O endereço IP e a porta devem ser armazenados no byte order da rede (Big-Endian).
#include <netinet/in.h>
struct sockaddr_in { sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; /* ... outros campos */};
struct in_addr { in_addr_t s_addr;};
uint16 htons( uint16 data_in_host_order );uint16 ntohs( uint16 data_in_net_order );
uint32 htonl( uint32 data_in_host_order );uint32 ntohl( uint32 data_in_net_order );
Sockets - Associação• Utiliza-se a chamada de sistema bind():
• Parâmetros:– sockfd – o descritor do socket.– my_addr – a estrutura com o endereço para ser associado.– addrlen – o tamanho da estrutura do endereço.
• A função retorna 0 (zero) em caso de sucesso ou –1 no caso de um erro.
• Erro comum: EADDRINUSE (“Address already in use”)
#include <sys/types.h>#include <sys/socket.h>
int bind( int sockfd, struct sockaddr *my_addr, socklen_t addrlen );
Sockets – Traduzindo Endereços IP
• Para converter um endereço IP entre as formas de string e binária:
• A função inet_aton() converte um endereço na notação do ponto (1.2.3.4) para o formato binário em byte order de rede (como a struct sockaddr_in espera) e retorna 0 (zero) em caso de sucesso.
• A função inet_ntoa() faz a conversão oposta.
#include <arpa/inet.h>
int inet_aton(const char * str, struct in_addr * addrptr);char * inet_ntoa(struct in_addr addr);
Sockets – Resolvendo Nomes com DNS
• Para resolver nomes de hosts (www.land.ufrj.br) para endereços IP (146.164.47.193) usamos as rotinas de acesso ao serviço DNS:
#include <netdb.h>
struct hostent * gethostbyname(const char * str);
struct hostent { int h_length; /* tamanho do endereço */ char **h_addr_list; /* lista de endereços */ char *h_addr; /* primeiro endereço */ /* ... outros campos */}
Sockets TCP• Sockets orientados a conexão com garantias de entrega
e ordenação.• É preciso estabelecer a conexão antes da troca de
dados.• O servidor cria um socket especial para “escutar”
pedidos de conexão do cliente.• Cada vez que o servidor aceita um pedido de conexão
recebido no socket de escuta, um novo socket é criado para realizar a comunicação. Assim é possível para o servidor voltar a aceitar novos pedidos de conexão mais tarde (usando o socket de escuta).
Sockets TCP – listen()• Para por um socket em modo de escuta usamos a
chamada de sistema listen():
• Parâmetros:– sockfd – o descritor do socket.– backlog – a soma das filas de conexões completas e
incompletas. Este parâmetro é extremamente dependente da implementação do Sistema Operacional.
• O valor de retorno da função é 0 (zero) em caso de sucesso ou –1 caso contrário.
#include <sys/socket.h>
int listen( int sockfd, int backlog );
Sockets TCP – accept()• Esta função aceita pedidos de conexão pendentes ou fica
bloqueada até a chegada de um.
• Parâmetros:– sockfd – o descritor do socket.– cliaddr – a estrutura onde será guardado o endereço do cliente.– addrlen – argumento valor/resultado com o tamanho da
estrutura do endereço.• O valor de retorno da função é um novo descritor (não
negativo) em caso de sucesso ou –1 caso contrário.• Cada novo descritor retornado por accept() está
associado à mesma porta do socket de escuta.
#include <sys/socket.h>
int accept( int sockfd, struct sockaddr * cliaddr, socklen_t * addrlen );
Sockets TCP – send()• Usada para enviar dados por um socket conectado.
• Parâmetros:– sockfd – o descritor do socket.– buffer – um ponteiro para os dados a serem enviados.– n_bytes – quantidade de bytes a serem enviados a partir do
ponteiro buffer.– flags – opções para essa operação.
• O valor de retorno da função é a quantidade de bytes enviados em caso de sucesso ou –1 caso contrário.
#include <sys/socket.h>
int send( int sockfd, void * buffer, size_t n_bytes, int flags );
Sockets TCP – recv()• Recebe dados por um descritor conectado, ou bloqueia
a execução até que algum dado chegue ao socket:
• Parâmetros:– sockfd – o descritor do socket.– buffer – um ponteiro para a área de memória onde devem ser
armazenados os dados recebidos.– n_bytes – quantidade máxima de bytes a serem recebidos.– flags – opções para essa operação.
• O valor de retorno da função é a quantidade de bytes recebidos em caso de sucesso ou –1 caso contrário.
#include <sys/socket.h>
int recv( int sockfd, void * buffer, size_t n_bytes, int flags );
Atenção para uma Armadilha!•As funções
read, recv, recvfrom, write, send e sendtopodem retornar menos bytes do que a quantidade requisitada, e nenhuma mensagem de erro é retornada neste caso!•Solução: ao usar as funções recv, recvfrom, send e sendto pode-se (deve-se!) passar como parâmetro a flag MSG_WAITALL.
Atenção para uma Armadilha!•As funções read e write não devem ser utilizadas! Ao invés disto, deve-se usar as funções de “embrulho” (“wrap functions”) readn e writen.
ssize_t readn(int fd, const void *vptr, size_t n){ size_t nleft; ssize_t nread; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( ( nread = read ( fd, ptr, nleft ) ) <=0 ) { if (errno == EINTR) nread = 0; else return (-1); } else if ( nread == 0) break; /* EOF */ nleft -= nread; ptr += nread; } return (n - nleft);}
#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <netinet/in.h>#include <arpa/inet.h>
int main( int argc, char ** argv ){ int sockfd; char recvline[30]; struct sockaddr_in servaddr;
if( argc != 2 ) return –1;
sockfd = socket( AF_INET, SOCK_STREAM, 0 );
memset( &servaddr, 0, sizeof(servaddr) ); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(12345); inet_aton( argv[1], &servaddr.sin_addr );
connect( sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr) );
readn( sockfd, recvline, 30 ); fputs( recvline, stdout ); close( sockfd );
return 0;}
Exemplo – Um Cliente TCP
Inicializar a estrutura servaddr com zeros.
Preencher os campos da estrutura servaddr:
sin_family com o tipo de endereço (AF_INET)
sin_port com a porta (12345) no byte order da rede.
sin_addr com o endereço passado como argumento para o programa.
Conecta o socket com o endereço do servidor (servaddr). Isto irá estabelecer a conexão TCP entre o cliente e o servidor.
Lê 30 bytes do socket. Esta função pode ler menos bytes se o outro lado (o servidor) fechar a conexão.
Imprime os dados recebidos na saída padrão.
Fecha o socket.
Criar um socket para rede TCP/IP (AF_INET), orientado a conexão (SOCK_STREAM), usando TCP (o protocolo padrão dado por 0).
#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <netinet/in.h>
int main( int argc, char ** argv ){ int listenfd, connfd, size; struct sockaddr_in myaddr, cliaddr;
listenfd = socket( AF_INET, SOCK_STREAM, 0 );
memset( &myaddr, 0, sizeof(myaddr) ); myaddr.sin_family = AF_INET; myaddr.sin_port = htons(12345); myaddr.sin_addr.s_addr = INADDR_ANY;
bind( listenfd, (struct sockaddr *)&myaddr, sizeof(myaddr) );
listen( listenfd, 5 );
for( ; ; ) { memset( &cliaddr, 0, sizeof(cliaddr) ); size = sizeof( cliaddr ); connfd = accept( listenfd, (struct sockaddr *)&cliaddr, &size ); writen( connfd, “Alo Mundo”, 10 ); close( connfd ); }
return 0;}
Preencher a estrutura do endereço local (myaddr) para associação com o socket:
sin_addr contém o endereço IP pelo qual o processo deseja receber pacotes. Como uma máquina pode ter vários IPs, é mais prático especificar que se deseja receber pacotes para qualquer um deles. Para isso usamos a constante INADDR_ANY.
Exemplo – Um Servidor TCP
Associar o endereço preenchido com o socket, para informar o S.O. de que o processo deseja receber pacotes para este endereço.
Aguarda um pedido de conexão.
Envia uma mensagem de 10 bytes pelo socket.
Fecha o socket da conexão.
Volta a aceitar novos pedidos.
Colocar o socket em modo de escuta. Isto cria uma fila de pedidos de conexão para o socket.
Servidores Concorrentes• Muitas vezes é necessário para um servidor lidar
com vários clientes de uma única vez. Para conseguir isto é preciso, de alguma maneira, voltar a aceitar conexões, sem esperar que um cliente seja completamente servido.
• Isto normalmente é feito através da criação de novas threads ou novos processos.
• Um servidor, após o retorno da função accept(), se divide em dois, e enquanto uma linha de execução se dedica a atender o cliente, outra volta a esperar por novos pedidos de conexão.
Servidores concorrentesEsquema de um Servidor Típico
pid_t pid;int listenfd, confd;
listenfd = socket(...);bind(listenfd, ...);listen(listenfd, LISTENQ);
for( ; ; ) {connfd = accept(listenfd, ...);
if ( ( pid = fork() ) == 0 ){
close(listenfd);doit(connfd);close(connfd);exit(0)
}close (connfd);
}
Conclusões sobre sockets TCP• Um par de aplicações que se comunicam por TCP em
geral tem a seguinte forma:
accept( )
close( )
connect( )
socket( )
socket( )
bind( )
listen( )
close( )
Troca de Dados
Estabelecimentoda Conexão TCP
Cliente
Servidor
ServidoresConcorrentes
Sockets UDP• Sockets sem conexão e sem garantias de entrega ou
ordenação.• Só é preciso saber o endereço de destino antes de
enviar dados. É possível receber dados a qualquer momento e de qualquer um. Usamos as funções sendto() e recvfrom().
• Pode-se ainda usar a função connect() com sockets UDP, mas o seu significado aqui é diferente, uma vez que NÃO faz sentido falar em uma conexão UDP. Para sockets UDP, a função connect() apenas “fixa” o endereço conectado. Assim é possível usar as funções send() e recv() como para sockets TCP.
Sockets UDP – sendto()• Envia dados por um socket NÃO conectado.
• Parâmetros:– sockfd – o descritor do socket.– buffer – um ponteiro para os dados a serem enviados.– n_bytes – quantidade de bytes a serem enviados.– flags – opções para essa operação.– to – endereço de destino dos dados.– addrlen – tamanho em bytes do endereço de destino.
• O valor de retorno da função é a quantidade de bytes enviados em caso de sucesso ou –1 caso contrário.
#include <sys/socket.h>
int sendto( int sockfd, void * buffer, size_t n_bytes, int flags, const struct sockaddr * to, socklen_t addrlen );
Sockets UDP – recvfrom()• Recebe dados por um descritor NÃO conectado, ou bloqueia a execução
até que algum dado chegue:
• Parâmetros:– sockfd – o descritor do socket.– buffer – um ponteiro para a área de memória onde devem ser armazenados os
dados recebidos.– n_bytes – quantidade máxima de bytes a serem recebidos.– flags – opções para essa operação.– from – ponteiro para a estrutura onde será escrito o endereço de origem.– addrlen – argumento valor/resultado com o tamanho do endereço de origem.
• O valor de retorno da função é a quantidade de bytes recebidos em caso de sucesso ou –1 caso contrário.
#include <sys/socket.h>
int recvfrom( int sockfd, void * buffer, size_t n_bytes, int flags, struct sockaddr * from, socklen_t * addrlen );
#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <netinet/in.h>#include <arpa/inet.h>
int main( int argc, char ** argv ){ int sockfd; struct sockaddr_in servaddr;
if( argc != 2 ) return –1;
sockfd = socket( AF_INET, SOCK_DGRAM, 0 );
memset( &servaddr, 0, sizeof(servaddr) ); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(12345); inet_aton( argv[1], &servaddr.sin_addr );
sendto( sockfd, “Alo Servidor”, 13, MSG_WAITALL,
(struct sockaddr *)&servaddr, sizeof(servaddr) );
close( sockfd );
return 0;}
Exemplo – Um Cliente UDP
Inicializar a estrutura servaddr com zeros.
Preencher os campos da estrutura servaddr:
sin_family com o tipo de endereço (AF_INET)
sin_port com a porta (12345) no byte order da rede.
sin_addr com o endereço passado como argumento para o programa.
Envia 13 bytes de dados para o servidor.
Criar um socket para rede TCP/IP (AF_INET), orientado a datagramas (SOCK_DGRAM), usando UDP (o protocolo padrão dado por 0).
#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <netinet/in.h>
int main( int argc, char ** argv ){ int sockfd, size, n; struct sockaddr_in myaddr;
sockfd = socket( AF_INET, SOCK_DGRAM, 0 );
memset( &myaddr, 0, sizeof(myaddr) ); myaddr.sin_family = AF_INET; myaddr.sin_port = htons(12345); myaddr.sin_addr.s_addr = INADDR_ANY;
bind( sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr) );
for( ; ; ) { recvfrom( sockfd, recvline, 30,
MSG_WAITALL, NULL, NULL ); fputs( recvline, stdout ); }
return 0;}
Exemplo – Um Servidor UDP
Associar o endereço preenchido com o socket, para informar o S.O. de que o processo deseja receber pacotes para este endereço.
Aguarda até 30 bytes. É possível para esta função retornar antes se o outro lado enviar um datagrama menor.
Imprime os dados recebidos na saída padrão.
Conclusões sobre sockets UDP• Um par de aplicações que se comunicam por UDP em
geral tem a seguinte forma:
close( )
socket( ) socket( )
bind( )
close( )
Troca de Dados
Cliente Servidor
bind( )
• Esta é a forma mais simples de depurar aplicações que envolvem sockets, threads e mais de um processo.
• Atenção! Deve-se gerar as mensagens de aviso em ‘stderr’ (que não possui buffer) e não ‘stdout’!
• Inconveniente desta técnica de depuração: pode afetar o funcionamento do programa.
Depurando: Passo 0,Exibindo mensagens de aviso
Depurando: Passo 1,A Máquina de Estados
Comando chave: netstat -a
[sadoc@copa src]$ netstat -a | grep 32568Proto Local Address Foreign Address State tcp *:32568 *:* LISTEN tcp localhost:47415 localhost:32568 ESTABLISHED tcp localhost:32568 localhost:47415 ESTABLISHED
[sadoc@copa src]$ netstat -a | grep 32568tcp localhost:47415 localhost:32568 CLOSE_WAIT tcp localhost:32568 localhost:47415 FIN_WAIT2
Depurando: Passo 1, A Máquina de Estados do TCP
Comando chave: netstat -a
wildcard
• tcpdump -x src host_name > log_file
• Programa equivalente, com interface gráfica: ethereal
Depurando: Passo 2,O comando tcpdump
Imprime o conteúdo de cada pacote,em hexadecimal
Fonte dos pacotes
Lições Aprendidas
Bibliografia Recomendada
• Stevens, W. R. – UNIX Network Programming: Volume I – Networking APIs: Sockets and XTI
• Stevens, W.R. – TCP/IP Illustrated vol.1• Besaw, L. – BSD Sockets Reference
(formato PostScript (.ps) em www.land.ufrj.br/~classes/tp/index-103.html).