Você está na página 1de 43

Introduo Programao com Sockets

Fernando Jorge Silveira Filho fernando@land.ufrj.br Daniel Sadoc Menasch sadoc@land.ufrj.br

Viso Geral
Objetivos:
Realizar comunicao entre processos. Entender como usar as funes da API sockets.

Histrico da API sockets: desenvolvida na poca da implementao dos protocolos TCP/IP pelo grupo da UC, Berkeley. Inicialmente iremos (re)ver alguns conceitos que sero importantes.

API Sockets
Application Program Interface elemento de ligao entre a aplicao e um sistema de mais baixo nvel.
Aplicao Final Aplicao Sockets Transporte Sistema Operacional (Kernel) Rotinas do S.O. Rede Rotinas do Driver Enlace de Dados Drivers e Hardware Fsica Comandos no Hardware

Conceitos Importantes
Argumentos de Valor/Resultado Descritores de Arquivo Chamadas de Sistema de E/S Byte Ordering Portas e Endereos IP Associaes & Conexes Servios de Transporte

Argumentos de Valor/Resultado
possvel (e comum) na linguagem C, utilizar o recurso de passagem de argumentos por referncia para passar valores para uma funo ao mesmo tempo em que se espera um valor de retorno na mesma varivel. Exemplo: imagine uma funo is_prime() que retorna 1 se um nmero apontado por um ponteiro n primo e 0 (zero) se composto. Neste ltimo caso, a funo tambm retorna o nmero primo mais prximo do valor passado. Este segundo valor de retorno pode ser colocado no prprio endereo de memria referenciado por n.
int is_prime( int * n ) { /* verifica se (*n) primo e em caso negativo coloca o primo mais prximo em (*n) */ }

Descritores de Arquivos
Nmeros inteiros no negativos que so ponteiros para estruturas de dados do sistema que representam os arquivos abertos por um processo.
Imagem do Processo Descritores de Arquivo Estruturas do S.O. x
7 26 15 50 39 21

z
z z

Chamadas de Sistema de E/S


Operaes bsicas 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 );

Byte Ordering
Como um valor armazenado na memria? Exemplo: 16909060 = 0x01020304

Big-Endian
Endereo Memria Endereo

Little-Endian
Memria

n
n+1 n+2 n+3

01
02 03 04

n
n+1 n+2 n+3

04
03 02 01

Para evitar conflitos entre hosts de arquiteturas diferentes convencionado que informaes de controle na rede so armazenadas em Big-Endian.

Endereos IP e Portas
Endereos IP identificam hosts na rede TCP/IP e so formados por 4 bytes, normalmente chamado octetos por razes histricas. Portas so identificadores dos processos interessados em usar os recursos de rede em um host. Portas ocupam 2 bytes na memria. Ambos tipos de variveis so armazenadas no byte order da rede (Big-Endian).
Notao Padro Hexadecimal 146 92 164 A4 10 0A 2 02 39990 9C 36

Associaes
Um processo precisa associar um socket a um endereo para avisar ao sistema operacional que deseja receber dados que chegam ao host com o endereo de destino especificado.
Sockets
Endereo Associado

Sockets

Processos 602 789 102 1026

20 146.164.41.10 : 80 23 25 146.164.41.10 : 21 41 146.164.41.10 : 20

Processos 602 789 102 1026

Endereo Associado

20 146.164.41.10 : 80 23 146.164.41.10 : 56 25 146.164.41.10 : 21 41 146.164.41.10 : 20

Conexes
Um socket pode ser conectado a um endereo diferente do seu prprio para que pacotes possam ser enviados por ele diretamente ao processo parceiro.
Socket Processo 602
Endereo Associado Endereo Conectado 200.128.40.12 : 10020

20 146.164.41.10 : 13003

Servios de Transporte
A camada de transporte dos protocolos TCP/IP fornece duas opes de tipos de servio:
Servio orientado a conexo: utiliza o protocolo TCP (Transmission Control Protocol) e garante entrega e ordenao. Servio datagrama: utiliza o protocolo UDP (User Datagram Protocol) e no faz nenhuma garantia.

Esquema de uma Aplicao TCP


Um par de aplicaes que se comunicam por TCP em Servidor geral tem a seguinte forma:
socket( ) Cliente socket( ) connect( )
Estabelecimento da Conexo TCP

bind( )
listen( )

accept( ) readn( )
writen( )

writen( )

readn( ) readn( ) close( ) close( )

Esquema de uma Aplicao TCP


Um par de aplicaes que se comunicam por TCP em Servidor geral tem a seguinte forma:
socket( ) Cliente socket( ) ter telefone

bind( )
listen( )
Estabelecimento da Conexo TCP

associar nmero ao telefone ligar alarme responder chamada

fazer chamada

connect( )

accept( ) readn( )
writen( )

writen( )

readn( ) readn( ) close( ) close( )

Sockets - Criao
Utiliza-se a chamada de sistema socket():
#include <sys/types.h> #include <sys/socket.h>
int socket( int domain, int type, int protocol );

Parmetros:

domain o tipo de rede do socket. Usamos AF_INET (Address Family Internet). Outros tipos: AF_LOCAL, AF_INET6. type o tipo de servio de transporte. Para sockets TCP usamos SOCK_STREAM, para UDP usamos SOCK_DGRAM. protocol o protocolo utilizado. Passando 0 (zero) utilizado o padro.

A funo retorna um descritor do socket criado ou 1 em caso de erro.

Sockets - Endereos
Endereos IP e portas so armazenados em estruturas do tipo struct sockaddr_in. sin_family o tipo do endereo. AF_INET deve ser usado. sin_port a porta associada ao endereo. sin_addr uma estrutura que contm o endereo IP (os 4 octetos). O endereo IP e a porta devem ser armazenados no byte order da rede (BigEndian).
#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 - Associao
Utiliza-se a chamada de sistema bind():
#include <sys/types.h> #include <sys/socket.h> int bind( int sockfd, struct sockaddr *my_addr, socklen_t addrlen );

Parmetros:

sockfd o descritor do socket. my_addr a estrutura com o endereo para ser associado. addrlen o tamanho da estrutura do endereo.

A funo retorna 0 (zero) em caso de sucesso ou 1 no caso de um erro. Erro comum: EADDRINUSE (Address already in use)

Sockets Traduzindo Endereos IP


Para converter um endereo IP entre as formas de string e binria:
#include <arpa/inet.h> int inet_aton(const char * str, struct in_addr * addrptr); char * inet_ntoa(struct in_addr addr);

A funo inet_aton() converte um endereo na notao do ponto (1.2.3.4) para o formato binrio em byte order de rede (como a struct sockaddr_in espera) e retorna 0 (zero) em caso de sucesso. A funo inet_ntoa() faz a converso oposta.

Sockets Resolvendo Nomes com DNS


Para resolver nomes de hosts (www.land.ufrj.br) para endereos IP (146.164.47.193) usamos as rotinas de acesso ao servio DNS:
#include <netdb.h> struct hostent * gethostbyname(const char * str);

struct hostent { int h_length; char **h_addr_list; char *h_addr; /* ... outros campos }

/* tamanho do endereo */ /* lista de endereos */ /* primeiro endereo */ */

Sockets TCP
Sockets orientados a conexo com garantias de entrega e ordenao. preciso estabelecer a conexo antes da troca de dados. O servidor cria um socket especial para escutar pedidos de conexo do cliente. Cada vez que o servidor aceita um pedido de conexo recebido no socket de escuta, um novo socket criado para realizar a comunicao. Assim possvel para o servidor voltar a aceitar novos pedidos de conexo mais tarde (usando o socket de escuta).

Sockets TCP listen()


Para por um socket em modo de escuta usamos a chamada de sistema listen():
#include <sys/socket.h> int listen( int sockfd, int backlog );

Parmetros:

sockfd o descritor do socket. backlog a soma das filas de conexes completas e incompletas. Este parmetro extremamente dependente da implementao do Sistema Operacional.

O valor de retorno da funo 0 (zero) em caso de sucesso ou 1 caso contrrio.

Sockets TCP accept()


Esta funo aceita pedidos de conexo pendentes ou fica bloqueada at a chegada de um.
#include <sys/socket.h>
int accept( int sockfd, struct sockaddr * cliaddr, socklen_t * addrlen );

Parmetros:

sockfd o descritor do socket. cliaddr a estrutura onde ser guardado o endereo do cliente. addrlen argumento valor/resultado com o tamanho da estrutura do endereo.

O valor de retorno da funo um novo descritor (no negativo) em caso de sucesso ou 1 caso contrrio. Cada novo descritor retornado por accept() est associado mesma porta do socket de escuta.

Sockets TCP send()


Usada para enviar dados por um socket conectado.
#include <sys/socket.h> int send( int sockfd, void * buffer, size_t n_bytes, int flags );

Parmetros:

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 opes para essa operao.

O valor de retorno da funo a quantidade de bytes enviados em caso de sucesso ou 1 caso contrrio.

Sockets TCP recv()


Recebe dados por um descritor conectado, ou bloqueia a execuo at que algum dado chegue ao socket:
#include <sys/socket.h> int recv( int sockfd, void * buffer, size_t n_bytes, int flags );

Parmetros:

sockfd o descritor do socket. buffer um ponteiro para a rea de memria onde devem ser armazenados os dados recebidos. n_bytes quantidade mxima de bytes a serem recebidos. flags opes para essa operao.

O valor de retorno da funo a quantidade de bytes recebidos em caso de sucesso ou 1 caso contrrio.

Ateno para uma Armadilha!


As funes
read, recv, recvfrom, write, send e sendto

podem retornar menos bytes do que a quantidade requisitada, e nenhuma mensagem de erro retornada neste caso! Soluo: ao usar as funes recv, recvfrom, send e sendto pode-se (devese!) passar como parmetro a flag MSG_WAITALL.

Ateno para uma Armadilha!


As funes read e write no devem ser utilizadas! Ao invs disto, deve-se usar as funes 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); }

Exemplo Um Cliente TCP


#include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> Conecta o socket memset( &servaddr, 0, sizeof(servaddr) ); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(12345); Criar um socket para rede inet_aton( argv[1], &servaddr.sin_addr ); TCP/IP (AF_INET ),

com o endereo do servidor int Inicializar a estrutura servaddr main( int argc, char ** argv ) (servaddr { com zeros. ). Isto ir int sockfd; a conexo osdo campos da Preencher L estabelecer 30 bytes socket. Esta char recvline[30]; TCP entre o cliente e o estrutura servaddr : funo pode ler menos bytes struct sockaddr_in servaddr; servidor. o sin_family com o tipo de se outro lado (o servidor)

endereo fechar a2 conexo. if( argc != ) (AF_INET) return sin_port com a porta (12345) 1; Imprime os dados recebidos

orientado a conexo connect( sockfd, (SOCK_STREAM ), (struct sockaddr *)&servaddr, usando TCP (o protocolo); sizeof(servaddr) padro dado por 0).
readn( sockfd, recvline, 30 ); fputs( recvline, stdout ); close( sockfd ); return 0;

na sada padro. sin_addr com o endereo sockfd = socket( AF_INET, Fecha o socket. passado como argumento SOCK_STREAM,
para o programa. 0 );

no byte order da rede.

Exemplo Um Servidor TCP


#include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <netinet/in.h>
Aguarda Associar um o ** endereo pedido int main( int argc, char argv ) de { conexo. preenchido com o int listenfd, connfd, size; Envia socket, uma para mensagem informar Colocar o socket struct sockaddr_in myaddr,em cliaddr;

bind( listenfd, (struct sockaddr *)&myaddr, Preencher a estrutura do sizeof(myaddr) );

endereo local (myaddr) para associao com o socket: for( ;;)


listen( listenfd, 5 ); {

de o 10 S.O. bytes de que pelo osocket. modo de escuta. Isto listenfd = socket( AF_INET, Fecha processo o socket deseja da cria uma fila de SOCK_STREAM, 0 ); conexo. receber pacotes para pedidos de conexo Volta este a endereo. aceitar novos memset( &myaddr, 0, sizeof(myaddr) ); para o socket. myaddr.sin_family pedidos. = AF_INET;
myaddr.sin_port = htons(12345); myaddr.sin_addr.s_addr = INADDR_ANY;
}

memset( &cliaddr, 0,pelo qual o endereo IP sizeof(cliaddr) ); processo deseja size = sizeof( cliaddr ); pacotes. connfdreceber = accept( listenfd,Como uma sockaddr mquina*)&cliaddr, pode ter (struct vrios &size ); IPs, mais writen( connfd, Alo Mundo, 10 ); prtico especificar que close( connfd ); receber se deseja

sin_addr contm o

pacotes para qualquer return 0;um deles. Para isso usamos a constante } INADDR_ANY.

Servidores Concorrentes
Muitas vezes necessrio para um servidor lidar com vrios clientes de uma nica vez. Para conseguir isto preciso, de alguma maneira, voltar a aceitar conexes, sem esperar que um cliente seja completamente servido. Isto normalmente feito atravs da criao de novas threads ou novos processos. Um servidor, aps o retorno da funo accept(), se divide em dois, e enquanto uma linha de execuo se dedica a atender o cliente, outra volta a esperar por novos pedidos de conexo.

Servidores concorrentes Esquema de um Servidor Tpico


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); }

Concluses sobre sockets TCP


Um par de aplicaes que se comunicam por TCP em geral tem a seguinte forma:
Servidor socket( ) Cliente socket( ) bind( )

listen( )
Estabelecimento da Conexo TCP

connect( )

accept( )

Servidores Concorrentes

Troca de Dados
close( ) close( )

Sockets UDP
Sockets sem conexo e sem garantias de entrega ou ordenao. S preciso saber o endereo de destino antes de enviar dados. possvel receber dados a qualquer momento e de qualquer um. Usamos as funes sendto() e recvfrom(). Pode-se ainda usar a funo connect() com sockets UDP, mas o seu significado aqui diferente, uma vez que NO faz sentido falar em uma conexo UDP. Para sockets UDP, a funo connect() apenas fixa o endereo conectado. Assim possvel usar as funes send() e recv() como para sockets TCP.

Sockets UDP sendto()


Envia dados por um socket NO conectado.
#include <sys/socket.h> int sendto( int sockfd, void * buffer, size_t n_bytes, int flags, const struct sockaddr * to, socklen_t addrlen );

Parmetros:

sockfd o descritor do socket. buffer um ponteiro para os dados a serem enviados. n_bytes quantidade de bytes a serem enviados. flags opes para essa operao. to endereo de destino dos dados. addrlen tamanho em bytes do endereo de destino.

O valor de retorno da funo a quantidade de bytes enviados em caso de sucesso ou 1 caso contrrio.

Sockets UDP recvfrom()


Recebe dados por um descritor NO conectado, ou bloqueia a execuo at que algum dado chegue:
#include <sys/socket.h> int recvfrom( int sockfd, void * buffer, size_t n_bytes, int flags, struct sockaddr * from, socklen_t * addrlen );

Parmetros:

sockfd o descritor do socket. buffer um ponteiro para a rea de memria onde devem ser armazenados os dados recebidos. n_bytes quantidade mxima de bytes a serem recebidos. flags opes para essa operao. from ponteiro para a estrutura onde ser escrito o endereo de origem. addrlen argumento valor/resultado com o tamanho do endereo de origem.

O valor de retorno da funo a quantidade de bytes recebidos em caso de sucesso ou 1 caso contrrio.

Exemplo Um Cliente UDP


#include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h>
intInicializar a estrutura servaddr main( int argc, char ** argv )

Envia 13 bytes de zeros. { com sockfd; para dados servidor. int Preencher os o campos da struct sockaddr_in servaddr; estrutura servaddr :
if( argc != 2 ) endereo (AF_INET) return 1;

memset( &servaddr, 0, sizeof(servaddr) ); servaddr.sin_family = AF_INET; htons(12345); Criar servaddr.sin_port um socket para = rede inet_aton( argv[1], TCP/IP (AF_INET ), &servaddr.sin_addr );

sin_family com o tipo de

orientado a datagramas (SOCK_DGRAM ), Alo usando sendto( sockfd, Servidor, 13, UDP (o protocolo padro MSG_WAITALL, (struct sockaddr *)&servaddr, dado por 0 ).
sizeof(servaddr) ); close( sockfd ); } return 0;

sin_port com a porta (12345) no byte order da rede. sockfd = socket( AF_INET, sin_addr SOCK_DGRAM, com o endereo passado como 0 ); argumento para o programa.

Exemplo Um Servidor UDP


#include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <netinet/in.h> Aguarda at 30 bind( sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr) ); for( ; ; ) { recvfrom( sockfd, recvline, 30, MSG_WAITALL, NULL, NULL ); fputs( recvline, stdout ); } } return 0;

bytes. possvel para esta int Associar o ** endereo int main( argc, char argv ) funo retornar antes { preenchido com o se int sockfd, size, n; enviar um o socket, outro lado para informar struct sockaddr_in myaddr; datagrama o S.O. de menor. que o Imprime os dados sockfd processo = socket( AF_INET, deseja recebidos na sada receber SOCK_DGRAM, pacotes para0 ); padro. este endereo.

memset( &myaddr, 0, sizeof(myaddr) ); myaddr.sin_family = AF_INET; myaddr.sin_port = htons(12345); myaddr.sin_addr.s_addr = INADDR_ANY;

Concluses sobre sockets UDP


Um par de aplicaes que se comunicam por UDP em geral tem a seguinte forma:
Cliente socket( ) bind( )

Servidor
socket( )

bind( )

Troca de Dados
close( ) close( )

Depurando: Passo 0, Exibindo mensagens de aviso


Esta a forma mais simples de depurar aplicaes que envolvem sockets, threads e mais de um processo. Ateno! Deve-se gerar as mensagens de aviso em stderr (que no possui buffer) e no stdout! Inconveniente desta tcnica de depurao: pode afetar o funcionamento do programa.

Depurando: Passo 1, A Mquina de Estados

Comando chave: netstat -a

Depurando: Passo 1, A Mquina de Estados do TCP


Comando chave: netstat -a
[sadoc@copa src]$ netstat -a | grep 32568 Proto Local Address Foreign Address State tcp *:32568 *:* LISTEN tcp localhost:47415 localhost:32568 ESTABLISHED tcp localhost:32568 localhost:47415 ESTABLISHED wildcard

[sadoc@copa src]$ netstat -a | grep 32568 tcp localhost:47415 localhost:32568 CLOSE_WAIT tcp localhost:32568 localhost:47415 FIN_WAIT2

Depurando: Passo 2, O comando tcpdump


tcpdump -x src host_name > log_file
Fonte dos pacotes Imprime o contedo de cada pacote, em hexadecimal

Programa equivalente, com interface grfica: ethereal

Lies 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).