Upload
internet
View
142
Download
7
Embed Size (px)
Citation preview
Programação com sockets
API Sockets apareceu no BSD4.1 UNIX
em 1981 são explicitamente
criados, usados e liberados por aplicações
paradigma cliente/servidor dois tipos de serviço de
transporte via API Sockets: datagrama não confiável fluxo de bytes, confiável
Socket é uma interface (uma “porta”), local ao
hospedeiro, criada por e pertencente à
aplicação, e controlado pelo SO, através da
qual um processo de aplicação pode tanto enviar como receber mensagens para/de outro processo de
aplicação (remoto ou local)
socket
Meta: aprender a construir aplicações cliente/servidor que se comunicam usando sockets
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.
Rotinas do S.O.
Sockets
Comandos no Hardware
Rotinas do Driver
API Sockets
Application Program Interface – Elemento de ligação entre uma aplicação e um sistema operacional de mais baixo nível.
Aplicação
Transporte
Rede
Enlace de Dados
Física
Sistema Operacional(Kernel)
Drivers e Hardware
Aplicação Final
Programação com sockets usando TCPSocket: Uma porta entre o processo de aplicação
e um protocolo de transporte fim-a-fim (UDP ou TCP)
Serviço TCP: Transferência confiável de bytes de um processo para outro
processo
TCP combuffers,
variáveis
socket
Controlado peloprogramador de
aplicação
Controladopelo sistemaoperacional
estação ouservidor
processo
TCP combuffers,
variáveis
socket
Controlado peloprogramador deaplicação
Controladopelo sistemaoperacional
estação ouservidor
internet
Cliente deve contactar servidor:
processo servidor deve antes estar em execução
servidor deve antes ter criado socket (porta) que aguarda contato do cliente
Cliente contacta servidor para:
criar socket TCP local ao cliente
especificar endereço IP, número de porta do processo servidor
Quando cliente cria socket: TCP cliente cria conexão com TCP do servidor
Quando contatado pelo cliente, o TCP do servidor cria socket novo para que o processo servidor possa se comunicar com o cliente permite que o servidor
converse com múltiplos clientes
Endereço IP e porta origem são usados para distinguir os clientes
TCP provê transferência confiável, ordenada de bytes
(“tubo”) entre cliente e servidor
ponto de vista da aplicação
Programação com sockets usando TCP
Comunicação entre sockets
TCP: Gerenciamento de Conexões
Lembrete: Remetente, receptor TCP estabelecem “conexão” antes de trocar segmentos de dados
inicializam variáveis TCP: nos. de seq. buffers, info s/ controle
de fluxo (p.ex. RcvWindow)
cliente: iniciador de conexão servidor: contatado por
cliente
Inicialização em 3 tempos:
Passo 1: sistema cliente envia segmento de controle SYN do TCP ao servidor especifica no. inicial de seq não envia dados
Passo 2: sistema servidor recebe SYN, responde com segmento de controle SYNACK aloca buffers especifica no. inicial de seq.
servidor-> receptor
Passo 3: receptor recebe SYNACK, responde com segmento ACK que pode conter dados.
TCP: Gerenciamento de Conexões (cont.)
Encerrando uma conexão:
cliente fecha soquete:
Passo 1: sistema cliente envia segmento de controle FIN ao servidor
Passo 2: servidor recebe FIN, responde com ACK. Encerra a conexão, enviando FIN.
cliente
FIN
servidor
ACK
ACK
FIN
fechar
fechar
fechada
esp
era
te
mpori
zada
TCP: Gerenciamento de Conexões (cont.)
Passo 3: cliente recebe FIN, responde com ACK.
Entre em “espera temporizada” - responderá com ACK a FINs recebidos
Passo 4: servidor, recebe ACK. Conexão encerrada.
cliente
FIN
servidor
ACK
ACK
FIN
fechando
fechando
fechada
esp
era
tem
pori
zada
fechada
TCP: Gerenciamento de Conexões (cont.)
Ciclo de vidade cliente TCP
Ciclo de vidade servidor TCP
Conceitos Importantes da Linguagem C e UNIX
Argumentos de Valor/Resultado Chamadas de Sistema de E/S Descritores de Arquivo 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) */}
Chamadas de Sistema de E/S UNIX usa para toda E/S o paradigma open-
read-write-close. Por exemplo, open prepara um arquivo e retorna um
descritor, que será utilizado depois para operações read/write. O close fecha o arquivo.
#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 );
Descritores São números inteiros não negativos que
são ponteiros para estruturas de dados do sistema que representam os arquivos/dispositivos abertos por um processo.
7 26 15 50 39 21x
Descritoresde Arquivo y
z
Imagem do Processo Estruturas do S.O.
z
z
A comunicação por sockets também usa descritores. Quando um socket é aberto, retorna um descritor, que pode ser utilizado por outras funções, como p-ex read e write.
• Define 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.
146 164 10 2 39990
92 A4 0A 02 9C 36
Notaçã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
Processos20
23
146.164.41.10 : 80
25
41
146.164.41.10 : 21
146.164.41.10 : 20
EndereçoAssociadoS
ocke
ts
602789
1026
102
Processos20
23
146.164.41.10 : 80
25
41
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
accept( )
close( )
connect( )
socket( )
socket( )
bind( )
listen( )
close( )
Estabelecimentoda Conexão TCP
Cliente
Servidor
writen( )
readn( )
readn( )
writen( )
readn( )
Sockets em uma aplicação TCPFunções da API de sockets utilizadas para um cliente e um servidor se comunicarem usando TCP:
ter telefone
associar número ao telefone
ligar campainha
atender chamada
desligar
conversar
fazer chamada
desligar
conversar
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 para endereçamento IPv4.
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 );
#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
Programação com sockets usando UDPUDP: não tem “conexão”
entre cliente e servidor não tem “handshaking” remetente coloca
explicitamente endereço IP e porta do destino
servidor deve extrair endereço IP, porta do remetente do datagrama recebido
UDP: dados transmitidos podem ser recebidos fora de ordem, ou perdidos
UDP provê transferência não confiável de grupos de bytes (“datagramas”) entre cliente e servidor
ponto de vista da aplicação
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
2b: Camada de Aplicação
Bibliografia Recomendada
Stevens, W. R. – UNIX Network Programming:
Volume I – Networking APIs: Sockets and XTI
Comer, D.E.; Stevens, D.L. – TCP/IP Client-Server
Programming and Aplications, vol.3, Linux POSIX
Socket Version
Frost J. – BSD Sockets: A quick and dirty primer.
http://www.frostbytes.com/~jimf/papers/sockets/sock
ets.html).