Comunicação entre processos
usando Sockets
Disciplina: Linguagem de Programação IV
Revisão da aula anterior
Revisão do programa fork.
Quais são os problemas em relação aos processos pesados ?
Comunicação entre processos
send / receive (troca de mensagens)
RPC (Remote Procedure Call)
C. 1984, ex.: rpcinfo/portmapper.
Objetos distribuídos. Maior nível de abstração.
API: Application Programming interface
API: Modelo de Programação
disponível em muitos sistemas operacionais
ex: Windows NT, Unix, etc.
Facilita a Programação
Torna as aplicações mais flexíveis
Revisão Sockets
O que é um Socket?
Socket é uma ponta de uma comunicação ponto-a-ponto entre dois
programas rodando em uma rede.
A API SOCKET
Introduzido em 1981 no UNIX BSD(Berkeley
Software Distribution) 4.1
A API SOCKET
A API SOCKET - cont.
Cada aplicação conhece apenas o seu próprio Socket;
Os sockets são explicitamente criados, usados e
liberados pela própria aplicação;
Baseado no paradigma cliente/servidor;
dois tipos de serviço de transporte via API socket:
datagramas não confiáveis
confiáveis, orientado a conexão
Sockets: visão conceitual
cada socket possui o seu próprio buffer para envio
e recepção de dado, número de porta, parâmetro;
As operações com o socket são construídas como
chamada a funções do sistema operacional.
Endereçamento na Internet
Cada máquina na Internet possui um ou mais
endereços IP 32-bit únicos e globais;
A máquina pode ter 1 ou mais endereços
cada endereço está associado a cada placa de rede
Notação de ponto decimal:
4 inteiros decimais , cada grupo representando um byte de
endereço IP.
Endereçamento na Internet
A função inet_addr() converte da notação ponto
decimal para o endereço de 32-bit;
A função gethostbyname() converte o nome
textual para o ponto decimal.
DNS: Sistema de nomes de Domínio na
Internet
uma base de dados distribuída usada por
aplicações TCP/IP para mapear de/para nomes
de máquina para/de endereços IP.
servidor de nomes:
as funções de usuário gethostbyname() e
gethostbyaddress() contactam o servidor de nome
local através da porta 53
o servidor de nomes retorna um endereço IP do nome
da máquina solicitada
Criando um socket
Mesmo ponto comum (socket) usado para enviar/receber
dados
não existe, a priori, associação do socket com a rede
deve especificar a família de protocolo, bem como o nível
do serviço a ser usado com o socket:
Tipo de serviço:
Datagrama (SOCK_DGRAM) = UDP
Confiável (SOCK_STREAM) = TCP
Criando um socket - cont.
int socket (int family, int service, int
protocol)
family ₫ um nome simbólico para a família de protocolos
service ₫ um nome simbólico para o tipo de serviço
Protocol ₫ para uso futuro. Esse valor será 0.
Obs.: O código de retorno da função socket() é um descritor, usado em todas as
chamadas ao socket criado.
Exemplo:
#include <sys/types.h>
#include <sys/socket.h>
int sockfd;
if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) <
0)
{ /* handle error */ }
Atribuindo um endereço de rede ao socket:
bind()
Cada socket deve ser associado a uma porta local
que deve ser única.
A porta é uma abstração do protocolo TCP/IP para
distinguir entre múltiplos destinos dentro de uma
determinada máquina
Precisa especificar o endereço de rede
O S.O. sabe que as messagens recebidas naquele
endereço e porta devem ser entregues para o socket.
Endereço de portas. Ver arquivos /etc/services.
Endereçamento de Socket: estruturas de
endereço pré-definidas
Especificando endereços de socket: algumas estruturas de
dados usadas na implementação:
struct sockaddr_in {
short sin_family; /* default AF_INET */
u_short sin_port; /* número de 16 bit */
struct in_addr sin_addr; /* endereço de 32
da máquina */
char sin_zero[8]; /* não usado */
};
struct in_addr {
u_long s_addr; /* end. 32 bit da máquina */
};
bit
A chamada ao bind()
int bind ( int sockfd, struct sockaddr *myaddr, int addresslen)
sockfd: é o número do socket obtido anteriormente.
*myaddr: especifica o end. local associado ao
socket(inclusive a porta).
addresslen: é o tamanho da estrutura de endere₤o
Obs.: se a função retornar um erro então o número da porta já
está em uso ou fora do limite.
A chamada ao bind() - cont.
#include <sys/types.h>
#include <sys/socket.h>
#include <inet.h>
int sockfd;
struct sockaddr_in myaddr;
if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) <
0)
{ /* Manipulador de erro */ }
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(5100);
myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
/* INADDR_ANY = SO determina o hostid */
if ( bind(sockfd, (struct sockaddr *) &myaddr,
sizeof(myaddr)) < 0) {
/* Manipulador de erro*/
}
Serviço Orientado a Conexão
SERVIDOR
CLIENTE
Cria o descritor:
socket()
para receber pedidos
cria o descritor:
socket()
Atribui um endere₤o
ao descritor:bind()
Atribui ao descritor
um endere₤o
(opcional) :bind()
Avisa que está aceitando
conexões: listen()
Determina o end. servidor
Troca de msg
bloquea/espera
novas conexões.:
accept()(novos
sockets criados na volta)
Conectar o servidor
via socket: connect()
envia msg: send()
espera pelo pkt:recv()
envia resposta:send()
Libera o descritor:
close()
resposta
espera a resp:recv()
Libera o descritor:
close()
Serviço Orientado a Conexão
aperto de mão cliente/servidor:
cliente deve se conectar ao servidor antes de enviar ou
receber dados
cliente não passará do connect() até o servidor aceitá-lo
servidor deve aceitar o cliente antes de enviar ou receber
dados
servidor não passará do accept() até o cliente usar
connect()
serviço orientado a conexão: serviço confiável
oferecido pela camada de transporte.
conexão cliente-para-servidor :
connect()
cliente usa connect() para requisitar conexão junto ao
servidor
protocolo da camada de transporte (ex: TCP) inicia
procedimento para conexão através do aperto de mão
cliente/servidor
connect() retorna quando o servidor aceita a conexão ou
time-out (não há resposta)
usado com protocolos confiáveis, mas também com
datagramas
Conexão cliente-para-servidor :
connect() - cont.
int connect ( int sockfd, struct sockaddr *toaddrptr, int
addresslen)
sockfd: número do socket atribuído anteriormente. Os
processos o usam para enviar conexões aceitas.
*toaddrptr: especifica o end. Remoto (inclusive a porta).
Addresslen : é o tamanho da estrutura de endereço.
A chamada listen()
Usado por servidores orientados a conexão.
avisa a rede/S.O. que o servidor aceitará requisições para
conexão.
Não bloqueia e não espera por requisições!
int listen ( int sockfd, int maxwaiting)
sockfd: número do socket atribuído anteriormente.
maxwaiting: número máximo de conexões que podem ser
enfileiradas, enquanto aguardam o servidor executar um
accept(). O valor típico é 5.
Conexão servidor-para-cliente : accept()
Executado pelo servidor após listen().
servidor irá aceitar as novas conexões via socket
novo, isto é, retornará um novo número de socket
para usar na comunicação de volta ao cliente.
Accept() -cont.
int accept ( int sockfd, struct sockaddr *fromaddrptr, int *addresslen)
sockfd número do socket atribuído anteriormente.
*fromaddrptr estrutura que contém o endereço do cliente onde
enviar as respostas.
Addresslen é o tamanho da estrutura de endereço.
Accept() -cont
struct sockaddr_in other_app_addr;
int sockid, newsockid, addrsize;
addrsize = sizeof(other_app_addr));
newsockid = accept(sockid, (struct sockaddr *)
&other_app_addr, &addrsize);
/* newsockid to communicate with client,
sockid to accept more connections */
Enviando e recebendo dados
Os dados são enviados/recebidos usando chamadas
E/S do Unix ou chamadas de rede
send/recv para sockets
write/read para qualquer operação de I/O
send()
int send (int sockfd, char *buff, int bufflen,
int flags)
sockfd número do socket .
*buff é o endereço do dado a ser enviado. O conteúdo abriga a
mensagem.
bufflen é número de bytes a enviar.
flags controla a transmissão. Para nós será sempre 0 (zero) .
Obs.: retorna o número de bytes efetivamente enviado.
Exemplo: usando send()
char buffer[50];
struct sockaddr_in other_app_addr;
int retcode
/* suppose we have done socket() and
bind() calls, filled in other_app_addr,and
put 23 bytes of data into buffer */
retcode = send(sockfd, buffer, 23, 0)
recv()
int recv (int sockfd, char *buff, int
bufflen, int flags)
sockfd é o número do socket obtido anteriormente.
*buff é o endereço onde o dado será armazenado.
bufflen número máximo esperado
flags controla a recepção. Esse valor será sempre 0.
Obs.: recv() retorna o número de bytes efetivamente recebidos
Exemplo: usando recv()
char buffer[50];
struct sockaddr_in other_app_addr;
int nread, addrlen;
/* suppose we have done socket(), bind() */
nread = recv(sockfd, buffer, 23, 0)
Sockets - outras funções
Sockets - estruturas
Sockets - funções
Ordenação de bytes e conversões
Serviço não orientado a conexão
Sockets - estruturas
struct sockaddr {
// estrutura parâmetro de connect()
unsigned short sa_family; // AF_xxx
char sa_data[14]; // IP + porta
};
Estrutura paralela para a Internet:
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8]; // pad
};
Sockets - estruturas
struct in_addr {
unsigned long s_addr;
}
sin_port e sin_addr devem estar em Network byte
order. (htonx()), isto é, colocar os bytes em ordem de
rede antes de mandá-los pela rede.
Motivo: são os campos enviados pela rede.
Ordenação de bytes e conversões
Ordenação de Bytes
Byte mais significante
chamado de Network byte order (NBO)
BigEndian
Byte menos significante
chamado de Host byte order (HBO)
LittleEndian
Rotinas de Ordenação de Byte
Rotinas de ordenação de bytes: converte bytes de inteiros
para/de 16 e 32-bits de/para “Network Byte Order''
números inteiros devem ser convertido explicitamente para/de
Network Byte Order.
computadores diferentes podem armazenar os bytes de inteiros em uma
ordem diferente na memória.
Network Byte Order é dita big-endian
Big Endian/Little Endian
Sockets – funções
Se tivermos: struct sockaddr_in ina;
ina.sin_addr.s_addr = inet_addr(“132.241.5.10”);
inet_addr() retorna o endereço em NBO.
printf(´´%s´´, inet_ntoa(ina.sin_addr));
Seqüência de código para aceitar conexões:
socket();
bind();
listen();
accept();
Procedures do UNIX
htonl converte formato de host de 32 bit para nbo;
ntohl converte nbo para o formato host de 32 bit;
htons converte formato de host de 16 bit para nbo;
ntohs converte nbo para o formato host de 16 bit.
Serviço não Orientado a Conexão
Serviço não orientado a conexão
Serviço de datagrama: o protocolo da camada de
transporte não garante a entrega do pacote;
Não há identificação explícita de quem é o servidor e
quem é o cliente;
Ao iniciar o contato com o outro lado precisamos
saber:
o endereço IP;
número da porta onde contactar o outro lado.
Ao esperar ser contactado pelo outro lado, precisa
declarar
número da porta que está esperando o outro lado
SERVIDOR
1.cria o descritor:
socket()
2. atribui ao descritor
um endereço:
bind()
3. Aguarda pkt
chegar: recvfrom()
4. Envia resposta(se houver):
sendto()
5. Libera o descritor:
close()
CLIENTE
1. cria o descritor:
socket()
2. Atribui ao descritor
um endereço:
(opcional) bind()
3. determina endereço
do servidor
4. envia msg: sendto()
5. Aguarda chegada
do pkt : recvfrom()
6. Libera o descritor:
close()
Exemplo: Servidor não orientado
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define MY_PORT_ID 6090 /* numero > 5000 */
main() {
int sockid, nread, addrlen;
struct sockaddr_in
my_addr, client_addr;
char msg[50];
Exemplo: Servidor não orientado - cont.
printf("Servidor: criando o socket\n");
if ( (sockid = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
printf("Servidor: erro no socket: %d\n",errno);
exit(0);
}
printf("Servidor: Bindando socket local\n");
bzero((char *) &my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_addr.s_addr = htons(INADDR_ANY);
my_addr.sin_port = htons(MY_PORT_ID);
Exemplo: Servidor não orientado - cont.
if ( (bind(sockid, (struct sockaddr *) &my_addr,
sizeof(my_addr)) < 0) ){
printf("Servidor: falha no binding:
%d\n",errno);
exit(0);
}
printf("Servidor: iniciando bloqueio de mensagem
lida\n");
nread = recvfrom(sockid,msg,11,0,
(struct sockaddr *) &client_addr, &addrlen);
printf("Servidor: cod retorno lido é
%d\n",nread);
if (nread >0) printf("Servidor: mensagem é:
%.11s\n",msg);
close(sockid);
}
Exemplo: Cliente não orientado
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define MY_PORT_ID 6089
#define SERVER_PORT_ID 6090
#define SERV_HOST_ADDR "128.119.40.186"
main() {
int sockid, retcode;
struct sockaddr_in my_addr, server_addr;
char msg[12];
Exemplo: Cliente não orientado - cont.
printf("Cliente: criando socket\n");
if ((sockid = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
printf("Cliente: falha no socket: %d\n",errno);
exit(0);
}
printf("Cliente: amarrando socket local\n");
bzero((char *) &my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
my_addr.sin_port = htons(MY_PORT_ID);
if ( ( bind(sockid, (struct sockaddr *) &my_addr,
sizeof(my_addr)) < 0) ){
printf("Cliente: falha no bind: %d\n",errno);
exit(0);
}
Printf("Cliente: criando estrutura addr para
o servidor\n");
bzero((char *) &server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr =
inet_addr(SERV_HOST_ADDR);
server_addr.sin_port = htons(SERVER_PORT_ID);
printf("Cliente: iniciando mensagem e
enviando\n");
sprintf(msg, “Ola para todos");
retcode = sendto(sockid,msg,12,0,(struct
sockaddr *)&server_addr,
sizeof(server_addr));
if (retcode <= -1){
printf("cliente: falha no sendto: %d\n",errno);
exit(0);
}
/* close socket */
close(sockid);
}
Resumo do Fluxograma
TCP / UDP
Uma aplicação de tempo simples:
Cliente:
conecta ao servidor
envia ao servidor a sua hora local
lê de volta a hora do servidor
Servidor:
recebe conexões dos clientes
imprime a hora local do cliente
envia ao cliente a sua hora local
Uma aplicação de tempo simples: código
cliente
1
2
3
4
5
6
7
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <errno.h>
#define SERV_HOST_ADDR "128.119.40.186"
/* Don's host machine */
8 main()
9 {
10 int sockid;
11 struct sockaddr_in ssock_addr;
12 struct timeval tp;
13 struct timezone tzp;
15
16
/* create a socket */
if ((sockid = socket(AF_INET,SOCK_STREAM,
0)) < 0){
17
printf("erro criacao=%d",errno);
18
exit(0);
19 }
20
21 printf("Criando struct addr p/ server…");
22 bzero((char *) &server_addr,
sizeof(server_addr));
23 server_addr.sin_family = AF_INET;
24 server_addr.sin_addr.s_addr =
inet_addr(SERV_HOST_ADDR);
25 server_addr.sin_port =
htons(SERVER_PORT_ID);
26 if (connect(sockid, (struct sockaddr *)
&server_addr, sizeof(server_addr)) <
0){
27
printf("error connecting to server,
error: %d \n",errno);
28
exit(0);
30
/* send time of day */
31
gettimeofday(&tp,&tzp);
32
/* convert from host byte order to
network
byte order */
33
printf("client: local time is %ld
\n",tp.tv_sec);
34
tp.tv_sec = htonl(tp.tv_sec);
35
tp.tv_usec = htonl(tp.tv_usec);
38
/* send time of day to other side */
39
write(sockid, &tp, sizeof(tp));
40
/* get time of day back fro other
side and display */
41
if ( (read(sockid, &tp, sizeof(tp)))
< 0){
42
printf("error reading new socket
\n");
43
exit(0);
44
}
45
46 /* convert from network byte order to
host byte order */
47 tp.tv_sec = ntohl(tp.tv_sec);
48 tp.tv_usec = ntohl(tp.tv_usec);
49 printf("client: remote time is %ld
\n",tp.tv_sec);
50 close(sockid);
51 }
Aplicação de tempo: código servidor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <errno.h>
#define MY_PORT_ID 6090
main()
{
int sockid, newsockid, i,j;
struct sockaddr_in ssock_addr;
struct timeval tp;
struct timezone tzp;
17
18
19
20
21
22
23
24
25
26
29
30
31
if ( (sockid = socket (AF_INET,
SOCK_STREAM, 0)) < 0)
{ printf("erro criacao= %d \n",errno);
exit(0);} /* man errno */
/* name the socket using wildcards */
bzero((char *) &ssock_addr,
sizeof(ssock_addr));
ssock_addr.sin_family = AF_INET;
ssock_addr.sin_addr.s_addr =
htonl(INADDR_ANY);
ssock_addr.sin_port = htons(MY_PORT_ID);
/* bind the socket to port address */
if ( ( bind(sockid, (struct sockaddr *)
&ssock_addr,sizeof(ssock_addr))< 0))
{ printf("error binding socket, error:
%d \n",errno); exit(0); }
/* start accepting connections */
if ( listen (sockid, 5) < 0)
{ printf("erro listening: %d \n",errno);
exit(0); }
33
34
35
36
37
38
39
40
41
42
43
44
for (i=1; i<=50000 ;i++) {
/* accept a connection */
newsockid = accept(sockid, (struct
sockaddr *)0, (int *)0);
if (newsockid < 0)
{ printf("error accepting socket,
error: %d \n",errno); exit(0);
}
/* le tempo remoto */
if ( (read(newsockid, &tp,
sizeof(tp))) < 0)
{ printf("error reading new socket
\n"); exit(0); }
/* convert from network byte order to
host byte order */
tp.tv_sec = ntohl(tp.tv_sec);
tp.tv_usec = ntohl(tp.tv_usec);
printf("server: remote time is
%ld \n",tp.tv_sec);
46 /* get local time of day and send to
client*/
47 for (j=0; j<1000000; j++);
48 /* delay */
49 gettimeofday(&tp,&tzp);
50 /* convert from host byte order to
network byte order */
51 printf("server: local time is
%ld \n",tp.tv_sec);
52 tp.tv_sec = htonl(tp.tv_sec);
53 tp.tv_usec = htonl(tp.tv_usec);
54 write(newsockid, &tp, sizeof(tp));
55 close(newsockid);
56 }
57 close(sockid);
58 }
Bibliografia
COMER, Douglas E. InternetWorking with TCP/IP -VOL
1.
Tutorial sobre sockets: http://beej.us/guide/bgnet/
http://www.newdevices.com/tutoriales/index.html
Sockets em Java:
http://www.javasoft.com/docs/books/tutorial/networking/sockets/d
efinition.html