Peer-to-peer com a utilizaç˜ao do SCTP para aplicativos de

Transcrição

Peer-to-peer com a utilizaç˜ao do SCTP para aplicativos de
Peer-to-peer com a utilização do
SCTP para aplicativos de
compartilhamento de arquivos
Gustavo Salvadori Baptista do Carmo
Dissertação apresentada como requisito parcial para obtenção do grau de Mestre pelo
Programa de Pós–graduação em Ciência da Computação da Universidade Federal de
Uberlândia
Programa de Pós–graduação em Ciência da Computação
Faculdade de Computação
Universidade Federal de Uberlândia
Minas Gerais – Brasil
Março de 2006
Gustavo Salvadori Baptista do Carmo
PEER-TO-PEER COM A UTILIZAÇÃO DO SCTP PARA
APLICATIVOS DE COMPARTILHAMENTO DE
ARQUIVOS
Dissertação apresentada ao programa de Pós-graduação
em Ciência da Computação da Universidade Federal de
Uberlândia, como requisito parcial para obtenção do tı́tulo
de Mestre em Ciência da Computação.
Área de pesquisa: Redes de Computadores.
Orientador: Prof. Dr. Jamil Salem Barbar
UBERLÂNDIA – Março de 2006
Gustavo Salvadori Baptista do Carmo
Peer-to-peer com a utilização do SCTP para aplicativos de
compartilhamento de arquivos
Dissertação apresentada ao Programa de Pósgraduação em Ciência da Computação da Universidade Federal de Uberlândia, para obtenção do tı́tulo
de Mestre.
Área de pesquisa: Redes de Computadores.
Banca Examinadora:
Uberlândia, 24 de Março de 2006.
Prof. Dr. Jamil Salem Barbar – UFU (orientador)
Prof. Dr. João Cândido Lima Dovicchi – UFSC
Prof. Dr. Pedro Frosi Rosa – UFU
Dedicado ao
Meu irmão.
Peer-to-peer com a utilização do SCTP para aplicativos de
compartilhamento de arquivos
Gustavo Salvadori Baptista do Carmo
Resumo
As redes peer-to-peer oferecem uma forma eficaz para o compartilhamento de recursos dos computadores. Sob essas redes está a camada de transporte. Na Internet,
os protocolos mais utilizados nessa camada são o TCP e o UDP. Outro protocolo da
camada de transporte é o SCTP, que oferece funcionalidades adicionais em relação
ao TCP e ao UDP que o tornam mais apropriado a determinados tipos de aplicações.
Este trabalho tem como objetivo mostrar a viabilidade da utilização do SCTP
como protocolo da camada de transporte em aplicativos peer-to-peer de compartilhamento de arquivos, fazendo uso das funcionalidades peculiares a esse protocolo
para satisfazer de forma mais eficaz as necessidades destes aplicativos.
Palavras-chave: Redes de computadores, Redes Peer-to-peer, SCTP, Camada de
transporte.
Gustavo Salvadori Baptista do Carmo
Abstract
Peer-to-peer networks provide an efficient way for sharing computer resources. The
transport layer underlies these networks. On the Internet, TCP and UDP are the
mostly used protocols in this layer. SCTP is another transport layer protocol:
related to TCP and UDP, SCTP offers additional functionalities that makes it more
suitable to certain kinds of applications.
This work aims to show the viability of the use of SCTP as the transport layer
protocol for peer-to-peer file sharing applications, using the peculiar functionalities
of this protocol to satisfy the needs of these applications in a more efficient way.
Keywords: Computer networks, Peer-to-peer Networks, SCTP, Transport Layer.
Copyright
c 2006 by GUSTAVO SALVADORI BAPTISTA DO CARMO.
Copyright “The copyright of this thesis rests with the author. No quotations from it should be
published without the author’s prior written consent and information derived from
it should be acknowledged”.
vii
Agradecimentos
Primeiramente a Deus, que me deu esta oportunidade e esteve ao meu lado durante
todo o percurso.
Ao meu orientador Prof. Dr. Jamil Salem Barbar, pela dedicação e paciência.
Ao meu pai José Carlos, à minha mãe Bernadete, ao meu irmão Rodrigo, e à
minha namorada Graziela, pelo imensurável apoio.
Aos meus amigos do mestrado, pela força, incentivo e, muitas vezes, pelo suporte
técnico.
Aos amigos do pensionato e da república, pelos momentos de descontração e
companhia.
Aos professores e funcionários do mestrado.
À CAPES - Coordenação de Aperfeiçoamento de Pessoal de Nı́vel Superior, pelo
apoio financeiro concedido.
viii
Sumário
Resumo
v
Abstract
vi
Agradecimentos
viii
Lista de Figuras
xii
Lista de Tabelas
xv
Lista de Acrônimos
xvi
1 Introdução
1
2 O modelo peer-to-peer
6
2.1
Definição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
2.2
Aplicabilidade das redes peer-to-peer . . . . . . . . . . . . . . . . . .
7
2.3
Caracterı́sticas das redes peer-to-peer . . . . . . . . . . . . . . . . . . 10
2.4
A classificação das arquiteturas de redes peer-to-peer
. . . . . . . . . 12
2.4.1
Arquitetura semi-centralizada intermediada . . . . . . . . . . 13
2.4.2
Arquitetura semi-centralizada não intermediada . . . . . . . . 15
2.4.3
Arquitetura descentralizada não estruturada pura . . . . . . . 16
2.4.4
Arquitetura descentralizada estruturada . . . . . . . . . . . . 20
2.4.5
Arquitetura descentralizada não estruturada baseada em ultranós . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.5
Protocolos para sistemas de compartilhamento de arquivos . . . . . . 23
2.5.1
Gnutella . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
ix
Sumário
x
2.5.2
Napster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.5.3
Gnutella2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.5.4
eDonkey . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2.5.5
FastTrack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
2.5.6
Kademlia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3 Os protocolos da camada de transporte
44
3.1
O protocolo UDP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.2
O protocolo TCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.3
O protocolo SCTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.4
3.3.1
Histórico do SCTP . . . . . . . . . . . . . . . . . . . . . . . . 51
3.3.2
Caracterı́sticas do SCTP . . . . . . . . . . . . . . . . . . . . . 51
A comparação entre o UDP, o TCP e o SCTP . . . . . . . . . . . . . 58
3.4.1
Limitações do UDP . . . . . . . . . . . . . . . . . . . . . . . . 58
3.4.2
Limitações do protocolo TCP . . . . . . . . . . . . . . . . . . 59
3.4.3
SCTP versus TCP versus UDP . . . . . . . . . . . . . . . . . 60
3.4.4
As similaridades entre o SCTP e o TCP . . . . . . . . . . . . 61
3.4.5
As diferenças entre o SCTP e o TCP . . . . . . . . . . . . . . 62
4 A utilização do protocolo SCTP por aplicativos de compartilhamento de arquivos em peer-to-peer
4.1
4.2
4.3
64
O comportamento de uma rede Gnutella . . . . . . . . . . . . . . . . 67
4.1.1
O tráfego gerado na fase de consulta em uma rede Gnutella
. 68
4.1.2
Os tamanhos das mensagens de uma rede Gnutella . . . . . . 72
4.1.3
Discussão a respeito do tempo médio da fase de consulta . . . 73
O problema de HOL em redes peer-to-peer . . . . . . . . . . . . . . . 75
4.2.1
O HOL causado pelo TCP . . . . . . . . . . . . . . . . . . . . 76
4.2.2
A utilização dos multi-fluxos . . . . . . . . . . . . . . . . . . . 78
4.2.3
A utilização da entrega desordenada de mensagens
. . . . . . 80
Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer . . . . . . . . . . . . . . . 81
4.3.1
Descrição do ambiente de simulação . . . . . . . . . . . . . . . 81
Sumário
4.3.2
xi
A simulação da transferência de mensagens numa rede peerto-peer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.3.3
4.4
O desempenho dos protocolos TCP e SCTP . . . . . . . . . . 91
A aplicação peer-to-peer de compartilhamento de arquivos Octopus
. 97
4.4.1
O protocolo Gnutella com o uso do SCTP . . . . . . . . . . . 97
4.4.2
O funcionamento do aplicativo Octopus . . . . . . . . . . . . . 98
4.4.3
A arquitetura do aplicativo Octopus
4.4.4
A negociação e a transferência de arquivos no Octopus . . . . 109
5 Conclusão
. . . . . . . . . . . . . . 101
114
Referências Bibliográficas
119
Apêndice A
125
Apêndice B
150
Anexo A
187
Lista de Figuras
2.1
Representação das redes peer-to-peer e cliente-servidor . . . . . . . .
2.2
Classificação dos aplicativos peer-to-peer sob a perspectiva do gênero
7
de sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
2.3
Classificação dos aplicativos peer-to-peer sob a perspectiva de mercado
9
2.4
Arquiteturas de redes peer-to-peer . . . . . . . . . . . . . . . . . . . . 13
2.5
Arquitetura semi-centralizada intermediada . . . . . . . . . . . . . . . 14
2.6
Arquitetura semi-centralizada não intermediada . . . . . . . . . . . . 16
2.7
Arquitetura descentralizada não estruturada pura . . . . . . . . . . . 17
2.8
Arquitetura descentralizada estruturada . . . . . . . . . . . . . . . . 21
2.9
Arquitetura descentralizada não estruturada baseada em ultranós . . 22
2.10 Cabeçalho das mensagens Gnutella . . . . . . . . . . . . . . . . . . . 26
2.11 A mensagem PING . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.12 A mensagem PONG . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.13 A mensagem QUERY . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.14 A mensagem QUERYHIT . . . . . . . . . . . . . . . . . . . . . . . . 30
2.15 A estrutura de um resultado da QUERYHIT . . . . . . . . . . . . . . 30
2.16 A mensagem PUSH . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.17 Exemplo de Roteamento de PINGs e PONGs . . . . . . . . . . . . . 34
2.18 Exemplo de Roteamento de QUERYs, QUERYHITs e PUSHs . . . . 35
3.1
Comunicação lógica entre dois processos, em dois hosts diferentes . . 45
3.2
Cabeçalho do protocolo UDP . . . . . . . . . . . . . . . . . . . . . . 45
3.3
Cabeçalho do protocolo TCP . . . . . . . . . . . . . . . . . . . . . . 47
3.4
Iniciação de conexão TCP . . . . . . . . . . . . . . . . . . . . . . . . 48
3.5
Estados de uma conexão TCP . . . . . . . . . . . . . . . . . . . . . . 49
xii
Lista de Figuras
xiii
3.6
Encerramento de conexão TCP . . . . . . . . . . . . . . . . . . . . . 50
3.7
Formato do pacote SCTP . . . . . . . . . . . . . . . . . . . . . . . . 52
3.8
Chunk de dados do SCTP . . . . . . . . . . . . . . . . . . . . . . . . 53
3.9
Iniciação de uma associação SCTP . . . . . . . . . . . . . . . . . . . 55
3.10 Estados de uma associação SCTP . . . . . . . . . . . . . . . . . . . . 56
3.11 Encerramento de conexão SCTP . . . . . . . . . . . . . . . . . . . . . 57
3.12 Caracterı́stica de multi-homing do SCTP . . . . . . . . . . . . . . . . 57
3.13 A conexão TCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.14 A associação SCTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.1
As fases de funcionamento de um aplicativo peer-to-peer para compartilhamento de arquivos . . . . . . . . . . . . . . . . . . . . . . . . 65
4.2
A fase de consulta em uma rede Gnutella . . . . . . . . . . . . . . . . 68
4.3
Número de QUERYs por segundo em três diferentes monitores . . . . 69
4.4
Número de QUERYs em um monitor . . . . . . . . . . . . . . . . . . 70
4.5
Número de QUERYHITs por segundo em três diferentes monitores . . 70
4.6
Número de QUERYHITs em um monitor . . . . . . . . . . . . . . . . 71
4.7
Tempo de resposta a uma consulta . . . . . . . . . . . . . . . . . . . 74
4.8
Transmissão de mensagens utilizando o TCP . . . . . . . . . . . . . . 77
4.9
Transmissão de mensagens utilizando os multi-fluxos do SCTP . . . . 79
4.10 Transmissão de mensagens utilizando a entrega desordenada do SCTP 80
4.11 A emulação do comportamento esperado de uma rede de computadores por meio do NIST Net . . . . . . . . . . . . . . . . . . . . . . . . 82
4.12 As pilhas de protocolos utilizadas pelos aplicativos do ambiente de
simulação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.13 As fases de funcionamento dos aplicativos gerador e receptor de pacotes e echo server . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.14 O funcionamento da thread geradora de pacotes . . . . . . . . . . . . 86
4.15 O funcionamento da thread receptora de pacotes . . . . . . . . . . . . 87
4.16 A cálculo da latência de uma mensagem - Exemplo 1 . . . . . . . . . 87
4.17 O cálculo da latência de uma mensagem - Exemplo 2 . . . . . . . . . 88
4.18 O cálculo da latência de uma mensagem - Exemplo 3 . . . . . . . . . 89
4.19 Resultados da primeira etapa com taxa de perda de 0% . . . . . . . . 92
Lista de Figuras
xiv
4.20 Resultados da primeira etapa com taxa de perda de 10% . . . . . . . 92
4.21 Resultados da primeira etapa com taxa de perda de 20% . . . . . . . 93
4.22 Média dos resultados da primeira etapa - variação da taxa de perda
de 0% a 20%
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
4.23 Resultados da segunda etapa com taxa de perda de 0% . . . . . . . . 94
4.24 Resultados da segunda etapa com taxa de perda de 10% . . . . . . . 95
4.25 Resultados da segunda etapa com taxa de perda de 20% . . . . . . . 95
4.26 Média de resultados da segunda etapa - variação da taxa de perda de
0% a 20% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
4.27 Novo cabeçalho para o protocolo Gnutella . . . . . . . . . . . . . . . 97
4.28 O fluxograma de funcionamento do Octopus . . . . . . . . . . . . . . 99
4.29 Um aplicativo peer-to-peer utilizando o TCP na camada de transporte 100
4.30 O aplicativo Octopus utilizando o SCTP na camada de transporte . . 101
4.31 A arquitetura do aplicativo Octopus . . . . . . . . . . . . . . . . . . . 103
4.32 A impressão do cache de associações no shell do Linux . . . . . . . . 104
4.33 A impressão do host cache no shell do Linux . . . . . . . . . . . . . . 104
4.34 A utilização dos multi-fluxos pelo aplicativo Octopus . . . . . . . . . 106
4.35 A interface gráfica do aplicativo Octopus . . . . . . . . . . . . . . . . 108
4.36 O fluxograma do servidor de arquivos do Octopus . . . . . . . . . . . 111
4.37 O fluxograma do cliente de arquivos do Octopus . . . . . . . . . . . . 112
Lista de Tabelas
2.1
Descritores de payload do Gnutella . . . . . . . . . . . . . . . . . . . 26
2.2
Exemplos de protocolos de compartilhamento de arquivos e suas arquiteturas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.1
Os chunks do SCTP . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.2
Comparação entre o SCTP, o TCP e o UDP . . . . . . . . . . . . . . 60
4.1
Número de QUERYs por segundo - média em uma hora . . . . . . . . 70
4.2
Número de QUERYHITs por segundo - média em uma hora . . . . . 71
4.3
Uma mensagem QUERY na rede Gnutella . . . . . . . . . . . . . . . 72
4.4
Uma mensagem QUERYHIT na rede Gnutella . . . . . . . . . . . . . 72
4.5
Parâmetros dos experimentos da primeira etapa . . . . . . . . . . . . 90
4.6
Parâmetros dos experimentos da segunda etapa . . . . . . . . . . . . 91
xv
Lista de Acrônimos
ADSL - Asymmetric Digital Subscriber Line
AIMD - Additive-increase-multiplicative-decrease
AOL - America Online
API - Application Programming Interface
APS - Adaptive Probabilistic Search
ASCII - American Standard Code for Information Interchange
BFS - Breadth-first Search
CAD - Computer-aided Design
CAE - Computer-aided Engineering
CAN - Content Addressable Network
DFS - Depth-first Search
DHT - Distributed Hash Table
DNS - Domain Name Server
DoS - Denial of Service
DRLP - Distributed Resource Location Protocol
FTP - File Transfer Protocol
GPL - GNU General Public Licence
GNU - GNU’s not Unix
HOL - Head-of-line Blocking
HTL - Hops to Live
xvi
Lista de Tabelas
HTTP - Hypertext Transfer Protocol
ICQ - “I seek you”
IETF - Internet Engineering Task Force
IP - Internet Protocol
IRC - Internet Relay Chat
ISO - International Organization for Standardization
JXTA - Juxtapose
MDTP - Multi-Network Datagram Transmission Protocol
MP3 - MPEG Audio Layer 3
MSN - The Microsoft Network
MSS - Maximum Segment Size
MTU - Maximum Transmission Unit
NIST - National Institute of Standards and Technology
OSI - Open Systems Interconnection
PDA - Personal Digital Assistant
PPP - Point-to-point Protocol
PSNT - Public Switched Telephone Network
P2P - Peer-to-peer
QoS - Quality of Service
RAM - Random-access Memory
RFC - Request for Comments
RTT - Round-trip Time
SACK - Selective Acknoledgement
SCTP - Stream Control Transmission Protocol
SETI - Search for Extra-terrestrial Intelligence
SIGTRAN - Signaling Transport Working Group
SMTP - Simple Mail Transfer Protocol
xvii
Lista de Tabelas
TCP - Transmission Control Protocol
TSN - Transmission Sequence Number
TTL - Time to Live
UDP - User Datagram Protocol
UTF - Unicode Transformation Format
VoIP - Voice over Internet Protocol
XML - Extensible Markup Language
xviii
Capı́tulo 1
Introdução
Atualmente, os computadores tornaram-se fundamentais como ferramentas de desenvolvimento, e até mesmo de diversão. Permitem a execução de diferentes tipos
de aplicativos, tais como CAD (Computer Aided Design), CAE (Computed Aided
Engineering), além daqueles destinados ao entretenimento.
Esses computadores são caracterizados por seus recursos disponı́veis, que podem
ser fı́sicos ou lógicos, de hardware ou de software, tais como microfones, webcams, impressoras, arquivos, capacidade de processamento e capacidade de armazenamento.
Possibilitam a comunicação e o estreitamento das relações humanas por meio de aplicativos que permitem a troca de mensagens e de informações, por exemplo, através
de e-mails ou VoIP (Voice over IP ).
Para que os computadores troquem informações entre si, é necessário que estejam,
de alguma forma, conectados fisicamente através de uma rede de computadores. As
redes de computadores podem ser categorizadas por suas topologias, tais como:
topologia estrela, ou centralizada, topologia em barra, ou Ethernet, e topologia em
anel, ou Token Ring, dentre outras. Uma rede é composta por nós, que podem
ser qualquer tipo de dispositivo conectado a ela. Os nós podem ser computadores,
PDAs (Personal Digital Assistants), telefones celulares, ou diversos utensı́lios com
algum tipo de processamento interno. Em uma rede IP (Internet Protocol ), um nó
é qualquer dispositivo que contenha um endereço IP1 .
As redes de computadores podem ser implementadas com o uso de uma variedade
1
Endereço IP é uma localização lógica, composto por 4 bytes ou 16 bytes, que designam onde um
determinado dispositivo se encontra na rede de computadores.
1
Capı́tulo 1. Introdução
2
de arquiteturas de pilhas de protocolos e topologias de redes de computadores. A
arquitetura de pilha de protocolos é uma abstração da divisão dos diferentes tipos
de serviços disponı́veis, em um sistema computacional, na forma de camadas.
O protocolo é a descrição formal dos formatos de mensagens e de regras que dois
nós obedecem para a troca de informações. Cada mensagem é composta basicamente
pelos campos de cabeçalho, corpo e rodapé, onde o corpo acomoda a informação,
que por sua vez pode ser tratada também como uma mensagem, a ser encaminhada.
O protocolo é baseado em um conjunto de serviços que são normalmente oferecidos à
camada superior, ou a uma aplicação, utilizando os serviços das camadas inferiores.
A pilha de protocolos utilizada na Internet, denominada TCP/IP (Transmission
Control Protocol/Internet Protocol ), inclui o IP e outros protocolos de camadas
superiores que utilizam seus serviços, tais como TCP e UDP (User Datagram Protocol ) na camada de transporte, e HTTP (Hyper Text Transfer Protocol ) e FTP
(File Transfer Protocol ) na camada de aplicação.
As redes peer-to-peer, ou redes overlay, são aquelas que atuam na camada de
aplicação e são definidas por um protocolo. Sua finalidade é o compartilhamento dos
recursos dos computadores, também denominados nós. Exemplos destes recursos são
os discos rı́gidos para armazenamento de dados, o poder de processamento, a largura
de banda ou até mesmo a presença humana. Essas redes são também tipicamente
utilizadas para conectar nós de maneira descentralizada, sem a necessidade de um
controle central.
A maior diferença, quando se compara o modelo cliente-servidor com o modelo
peer-to-peer, é que este último possui apenas um elemento atuante, denominado de
servente, enquanto que o modelo cliente-servidor possui dois elementos, denominados
cliente e servidor. O servente tem as mesmas funções do cliente e do servidor do
modelo cliente-servidor.
Clientes são entidades que demandam serviços dos servidores, ao passo que servidores recebem as requisições dos clientes e retornam com os dados ou resultados
obtidos. A comunicação entre cliente e servidor é feita por meio de protocolos especı́ficos. Os serventes são entidades que atuam como cliente e servidor ao mesmo
tempo para um mesmo propósito [29].
Os nós de uma rede peer-to-peer podem apresentar, também, caracterı́sticas do
Capı́tulo 1. Introdução
3
modelo cliente-servidor. Dependendo do grau qualitativo de envolvimento dos nós
com este modelo, essas redes podem ser implementadas sob diferentes abordagens,
resultando em diferentes arquiteturas, podendo ser classificadas dentro de um espectro que varia de um pólo mais centralizador até um pólo descentralizador.
Um dos objetivos deste trabalho é apresentar uma nova classificação para as
diversas arquiteturas de redes peer-to-peer existentes [11]. A classificação das arquiteturas é feita a partir do levantamento de suas caracterı́sticas, considerando as
funcionalidades intrı́nsecas de cada nó, a organização dos nós, e o algoritmo de roteamento utilizado, definindo, desta maneira, o grau de centralização da arquitetura.
Um dos grandes desafios das redes peer-to-peer é garantir o bom desempenho do
roteamento de mensagens entre os nós participantes. Uma vez que os recursos estão
dinamicamente distribuı́dos pela rede, não se pode ter noções exatas sobre em que
local esses recursos se encontram; assim sendo, cada nó conta com um mecanismo
de busca para executar a tarefa de obter as informações referentes à localização
desses recursos na rede virtual. Esse mecanismo é implementado pelos protocolos da
camada de aplicação do modelo de referência OSI (Open Systems Interconnection),
que definem os algoritmos de roteamento das mensagens.
As redes peer-to-peer atuantes na Internet utilizam, sob a camada de aplicação,
a pilha de protocolos TCP/IP. A escolha de qual protocolo deve ser utilizado na
camada de transporte é um fator determinante para prover e melhorar as funcionalidades especı́ficas a uma determinada aplicação peer-to-peer.
Os protocolos da camada de transporte mais utilizados na Internet são o UDP
e o TCP. O UDP é um protocolo orientado à mensagem, não orientado à conexão
e não confiável, isto é, não garante que os dados sejam entregues ao destinatário e
nem que cheguem na ordem correta. O UDP oferece os serviços de multiplexação
e demultiplexação dos datagramas e carrega em seu cabeçalho uma “soma de verificação”, utilizada no controle de integridade dos dados. Já o TCP oferece outros
serviços. Este protocolo garante que o segmento será entregue ao destinatário, garante a ordem correta de entrega e ainda implementa os controles de fluxo e de
congestionamento. Diferentemente do UDP, o TCP é um protocolo orientado à
conexão.
Um outro protocolo da camada de transporte é o SCTP (Stream Control Trans-
Capı́tulo 1. Introdução
4
mission Protocol ). O SCTP é um protocolo confiável, orientado à conexão, e orientado à mensagem. Pode ser utilizado concorrentemente com o TCP pois implementa,
de forma similar, os controles de fluxo e de congestionamento.
A camada de transporte pode oferecer à camada de aplicação um canal de relacionamento, criado entre dois sistemas finais, que conecta os processos de maneira
virtual, isto é, eles não estão conectados fisicamente, mas do ponto de vista da
aplicação, é como se estivessem. No TCP, este relacionamento é chamado de conexão, e possui duas vias, uma em cada sentido, através das quais informações
podem ser enviadas e recebidas de maneira full-duplex. No SCTP, o relacionamento
criado entre dois sistemas finais é chamado de associação. Esta denominação, diferentemente do TCP, é utilizada pois um número arbitrário de vias, chamadas de
fluxos, pode ser criado para ambos os sentidos.
O SCTP apresenta funcionalidades adicionais, em relação ao TCP e ao UDP,
que o tornam mais adequado para determinados tipos de aplicações, como, por
exemplo, para as aplicações peer-to-peer, que podem fazer uso da funcionalidade
que provê múltiplos fluxos para a transmissão de dados, e da entrega desordenada
de mensagens.
Assim, este trabalho tem como objetivo principal mostrar a viabilidade da utilização do protocolo SCTP como protocolo da camada de transporte para aplicativos
peer-to-peer de compartilhamento de arquivos [9, 10]. Primeiramente, é feita uma
análise sobre o ganho de desempenho na utilização do SCTP, em vez do TCP, na
transferência das mensagens entre os nós de uma rede peer-to-peer.
Para efetuar esta análise, um ambiente de simulação é desenvolvido. Este ambiente é composto basicamente por dois nós e um gerador de perda e atraso de
pacotes. Os nós representam entidades que fazem parte de uma rede peer-to-peer e
que trocam mensagens entre si, enquanto que o gerador de perda e atraso de pacotes, implementado por meio do aplicativo NIST Net [3], emula o comportamento
que uma rede real provoca nas mensagens trafegadas de um nó a outro, em que
parâmetros, tais como perda e atraso de pacotes, podem ser configurados.
A partir deste ambiente usa-se o TCP num dado momento, e o SCTP em outro,
como protocolos da camada de transporte, em que experimentos práticos são executados baseados na dinâmica de uma rede peer-to-peer real em funcionamento. Com
Capı́tulo 1. Introdução
5
o SCTP, as funcionalidades dos multi-fluxos e da entrega desordenada de mensagens
são exploradas, a fim de se efetuar a comparação com o protocolo TCP, que não
apresenta essas funcionalidades.
Este trabalho tem, ainda, como objetivo, mostrar um protótipo de aplicação
peer-to-peer que faz uso das funcionalidades do SCTP. Para isso, foi desenvolvido o
aplicativo de compartilhamento de arquivos Octopus, que utiliza o SCTP na camada
de transporte e o protocolo Gnutella reestruturado na camada de aplicação. São
descritas as suas camadas e os seus algoritmos, mostrando a forma que este aplicativo
faz uso de algumas funcionalidades do protocolo SCTP em um aplicativo peer-to-peer
de compartilhamento de arquivos.
Esta dissertação, além deste capı́tulo introdutório, organiza-se como mostrado a
seguir.
No Capı́tulo 2 são apresentadas informações sobre o modelo peer-to-peer, tais
como definições, taxonomias, arquiteturas e protocolos existentes para aplicativos
de compartilhamento de arquivos.
No Capı́tulo 3, faz-se um levantamento das caracterı́sticas dos protocolos da
camada de transporte UDP, TCP e SCTP, e, em seguida, é apresentada uma comparação entre eles.
O Capı́tulo 4 apresenta, inicialmente, um estudo sobre o comportamento de uma
rede peer-to-peer real em funcionamento. Em seguida, é feita uma discussão sobre
a transmissão de mensagens com o uso dos protocolos TCP e SCTP. É descrito,
então, o ambiente de simulação utilizado para a análise comparativa entre o TCP e
o SCTP. Por fim, apresenta-se os detalhes sobre o desenvolvimento do aplicativo de
compartilhamento de arquivos Octopus.
O Capı́tulo 5 encerra o trabalho com as considerações finais, incluindo o levantamento da contribuição deste trabalho à comunidade cientı́fica e as propostas para
trabalhos futuros.
O Apêndice A apresenta os códigos fonte dos aplicativos utilizados no ambiente
de simulação e o Apêndice B apresenta o código fonte do aplicativo Octopus. O
Anexo A mostra a interface gráfica do aplicativo NIST Net.
Capı́tulo 2
O modelo peer-to-peer
Este capı́tulo apresenta as definições, as caracterı́sticas e alguns detalhes sobre o
modelo peer-to-peer e as redes que o implementam. É apresentada também uma
nova classificação das diferentes arquiteturas de redes peer-to-peer. Em seguida, o
protocolo Gnutella é mostrado em detalhes, por ser o protocolo de referência para
as implementações descritas no Capı́tulo 4. Por fim, são citadas as caracterı́sticas
de outros protocolos de redes peer-to-peer mais utilizados na Internet.
2.1
Definição
O modelo peer-to-peer define regras para o desenvolvimento de redes descentralizadas, que funcionam sem a necessidade de uma entidade central. O nó, neste modelo,
se comparado ao modelo cliente-servidor, deixa de ser somente um cliente ou servidor e passa a ser cliente e servidor simultaneamente; além disso, em vez de apenas
consumir recursos ou oferecer recursos, realiza as duas tarefas simultaneamente [46].
As redes peer-to-peer, ou redes overlay, implementam o modelo peer-to-peer. São
redes virtuais que têm como finalidade principal o compartilhamento dos recursos
dos nós. Estes recursos podem ser os discos rı́gidos para armazenamento de arquivos,
o poder de processamento, a largura de banda ou, ainda, a presença humana [36].
Na Internet, as funcionalidades dessas redes residem na camada de aplicação,
atuando, assim, sobre as camadas de rede, implementada pelo protocolo IP, e de
transporte, implementada, em sua maioria, pelos protocolos UDP ou TCP, da arquitetura de protocolos TCP/IP [20].
6
2.2. Aplicabilidade das redes peer-to-peer
7
As redes peer-to-peer são compostas por nós, também denominados peers, que
podem estar localizados nas bordas ou no centro da rede, possuindo conectividade
variável e temporária. Seus endereços, utilizados para definir a localização de um nó
na rede virtual, são também variáveis, pois os nós podem conectar-se ou desconectarse da rede conforme necessidade ou falha, sendo assim atribuı́do um novo endereço,
quando de sua nova conexão. A Figura 2.1 (a) representa uma topologia de rede
que implementa o modelo peer-to-peer, e a Figura 2.1 (b) representa uma topologia
de rede que implementa o modelo cliente-servidor.
Figura 2.1: Representação das redes peer-to-peer e cliente-servidor
Para efetuar algumas de suas tarefas, as redes peer-to-peer podem implementar
funcionalidades que apresentam caracterı́sticas do modelo cliente-servidor. Assim
sendo, à medida que diferentes caracterı́sticas são incorporadas, diferentes arquiteturas de redes peer-to-peer são abordadas [1]. Essas caracterı́sticas são definidas
pelos protocolos de redes peer-to-peer que, além da arquitetura, definem os formatos
de mensagens e as regras que dois nós devem obedecer para a troca de informações.
2.2
Aplicabilidade das redes peer-to-peer
Os aplicativos peer-to-peer podem ser utilizados para diversas finalidades. Assim
sendo, há como classificá-los em diferentes sistemas: sistemas de comunicação, sistemas de compartilhamento de arquivos, sistemas colaborativos e sistemas de tarefas distribuı́das. As plataformas de desenvolvimento dão suporte à implementação
destes sistemas, oferecendo frameworks, protocolos e documentações de auxı́lio ao
desenvolvimento [29].
Os sistemas de comunicação são os aplicativos peer-to-peer mais populares na
Internet. Eles permitem que usuários se comuniquem por texto, áudio ou vı́deo, e
2.2. Aplicabilidade das redes peer-to-peer
8
ainda identifiquem a disponibilidade de outros pares para comunicação. Aplicativos
mais elaborados oferecem o serviço de conferência, em que mais de dois usuários
podem, simultaneamente, compartilhar uma sessão de comunicação. Exemplos de
tais aplicativos têm-se o MSN, o ICQ e o Skype.
Os sistemas de compartilhamento de arquivos afetaram com grande impacto a
indústria fonográfica, cinematográfica, e todas as outras que trabalham com conteúdos
digitalizáveis protegidos por lei. Eles permitem que usuários utilizem e forneçam
espaço em disco para o compartilhamento de arquivos de música, áudio, livros digitais ou vı́deos, acessı́veis a todos os nós. Nessa classe de sistemas, os usuários
têm acesso irrestrito ao disco rı́gido dos nós compartilhantes, que disponibilizam
arquivos para download. Uma busca deve ser feita de modo a encontrar o arquivo
requerido. Exemplos de tais aplicativos têm-se o Morpheus, o Shareaza e o Kazaa.
Os sistemas colaborativos permitem que usuários troquem informações a respeito
de uma determinada tarefa comum, em tempo real, sem a necessidade de um servidor central. Aplicações que implementam esta forma de cooperação organizam as
informações de maneira inteligente e permitem que pessoas utilizem seus próprios
dados e recuperem dados dos demais. Exemplos de tais aplicativos têm-se o Groove
e o Microsoft Netmeeting.
Os sistemas de tarefas distribuı́das têm como principal objetivo fazer com que
atividades complexas sejam divididas em outras menos complexas e distribuı́das
pelos nós que compõem a rede, para que sejam executadas em paralelo. Assim que
os resultados de cada tarefa menos complexa estiverem disponı́veis, são retornados
à entidade responsável por combiná-los, para resolver o problema da tarefa mais
complexa. Exemplos de tais aplicativos têm-se o SETI@Home e o Condor.
As plataformas de desenvolvimento oferecem ferramentas para facilitar a construção de sistemas peer-to-peer, e especificam não só protocolos para a troca de
mensagens, mas também funções que podem ser utilizadas para a elaboração do
código-fonte. A utilização destas plataformas para o desenvolvimento dos sistemas
peer-to-peer é opcional.
Além dos exemplos citados, há muitos outros softwares disponı́veis na Internet,
freeware ou não, que se enquadram em algum gênero desses sistemas. A Figura
2.2 posiciona alguns destes aplicativos em três eixos [29]: o eixo dos sistemas de
2.2. Aplicabilidade das redes peer-to-peer
9
comunicação e colaboração, que possuem funcionalidades similares, o dos sistemas
de tarefas distribuı́das e o dos sistemas de compartilhamento de arquivos. No ponto
de encontro destes três eixos estão representadas as plataformas de desenvolvimento,
que podem ser usadas para a implementação de qualquer gênero de sistema. A
Figura 2.2 traz como exemplos de plataformas o .NET e o JXTA.
Figura 2.2: Classificação dos aplicativos peer-to-peer sob a perspectiva do gênero de sistema
A classificação dos aplicativos peer-to-peer pode ser feita, ainda, sob outra perspectiva [29], além daquela apresentada na Figura 2.2. Esta classificação é mostrada
na Figura 2.3.
Figura 2.3: Classificação dos aplicativos peer-to-peer sob a perspectiva de mercado
Em termos de desenvolvimento, a plataforma JXTA [2] provê infra-estrutura para
2.3. Caracterı́sticas das redes peer-to-peer
10
a criação de vários sistemas peer-to-peer. Logo acima da camada das plataformas,
está a camada de aplicações horizontais, que são aquelas que podem ser empregadas
em mais de um ramo de mercado, em contraste com as verticais, que são mais
especı́ficas.
Logo acima da camada de aplicações horizontais são citadas exemplos de aplicativos que podem ser criadas a partir de cada classe de sistema, e logo acima,
as indústrias que fariam uso destes aplicativos. Para exemplificar o diagrama, de
acordo com a Figura 2.3, pode-se usar a plataforma JXTA para auxiliar a criação
de um sistema de comunicação, tal como o de mensagem instantânea, para uso no
mercado de comunicação.
2.3
Caracterı́sticas das redes peer-to-peer
Os aplicativos peer-to-peer causaram impactos estruturais na Internet. Um destes
impactos refere-se ao consumo da largura de banda. Estes aplicativos requerem mais
banda de upload do que os aplicativos desenvolvidos sob o modelo cliente-servidor,
que vinham sendo os mais utilizados pela grande massa de usuários da Internet.
O modelo cliente-servidor é utilizado para implementar redes em que os nós são
divididos em dois nı́veis: um nı́vel contendo os clientes, que demandam recursos,
e outro, contendo os servidores, que oferecem recursos. Nesse modelo, um mesmo
computador pode ser, ao mesmo tempo, cliente e servidor, porém não para um
mesmo propósito ou objetivo. Por sua vez, o modelo peer-to-peer implementa redes
em que os nós estão em um mesmo nı́vel, que contém os serventes, que atuam
simultaneamente como cliente e servidor para um mesmo propósito.
Por exemplo, um computador pode ser servidor FTP, e ser cliente HTTP simultaneamente, não implementando o modelo peer-to-peer, pois é cliente e servidor ao
mesmo tempo, porém, para propósitos diferentes. No entanto, um computador que
atua como cliente, procurando por arquivos e, ao mesmo tempo, oferece os arquivos
que possui a outros usuários, atuando assim como servidor, implementa o modelo
peer-to-peer, pois é cliente e servidor ao mesmo tempo, para um mesmo propósito.
Cada um dos modelos apresenta caracterı́sticas distintas, como descritas a seguir:
• Tolerância a falha: Quanto maior a descentralização, maior é a tolerância a
2.3. Caracterı́sticas das redes peer-to-peer
11
falhas. No modelo cliente-servidor, se o servidor falha, o serviço e os recursos
oferecidos são gravemente prejudicados. No modelo peer-to-peer, a falha de um
nó pouco afeta o bem-estar geral da rede, resultando em um ambiente mais
tolerante a falhas, ao implementar a redundância dos recursos;
• Segurança: Os usuários das redes peer-to-peer deixam de somente consumir
recursos passando, também, a disponibilizar recursos. Isso faz com que os nós
estejam mais expostos a falhas de segurança do que no modelo cliente-servidor,
em que a exposição é mais restrita ao servidor de recursos.
• Redução de custos: Os custos de compra e de manutenção de um servidor de
grande porte, que oferece, por exemplo, alto poder de processamento e grande
espaço de armazenamento, são muito elevados. O modelo peer-to-peer faz com
que esses custos sejam distribuı́dos através dos nós, ao agregar os recursos dos
usuários;
• Escalabilidade: As redes implementadas segundo o modelo cliente-servidor possuem escalabilidade limitada pois, caso o número de clientes cresça de maneira
descontrolada, um determinado servidor pode vir a não suportar a demanda
pelos recursos. No modelo peer-to-peer, existe uma melhora em relação à escalabilidade, pois o aumento do número de nós afeta o desempenho do sistema
de forma positiva;
• Autonomia e privacidade: A presença de servidores pode ser detectada com
maior facilidade do que a de usuários de um sistema peer-to-peer. Assim sendo,
os usuários podem escolher quais arquivos compartilhar, mesmo que estes arquivos sejam legalmente restritos. Além disso, o usuário escolhe o momento de
sua entrada e saı́da da rede, garantindo, assim, a sua autonomia.
• Gerenciabilidade: O gerenciamento é o planejamento sistematizado da implementação, monitoramento, e utilização dos serviços oferecidos por intermédio
de um sistema computacional. Os sistemas baseados no modelo cliente-servidor
são gerenciáveis, enquanto os sistemas baseados no modelo peer-to-peer são
auto-organizáveis.
Uma rede é dita auto-organizável quando, enquanto o
número de conexões cresce ou diminui dentro da malha de nós, a organização
2.4. A classificação das arquiteturas de redes peer-to-peer
12
desses nós se altera de forma independente de uma entidade externa. Essa
auto-organização depende de vários fatores tais como interesses comuns dos
participantes, estabilidade da rede ou implementação do protocolo da camada
de aplicação;
• Dinamismo: Os sistemas baseados no modelo cliente-servidor permitem que as
buscas por recursos sejam feitas de forma centralizada. Os sistemas baseados
no modelo peer-to-peer, devido à sua natureza dinâmica, devem buscar recursos
que estão distribuı́dos na rede e que estão sob constantes modificações;
• Componentes: Diferentes componentes são utilizados em cada um dos modelos. O DNS (Domain Name Server ), por exemplo, é utilizado como recurso
para a descoberta de servidores na rede, substituindo os endereços IP por
seqüência de strings mais simples para o usuário. No ambiente peer-to-peer,
outros componentes, tais como o web caching e o host caching, são utilizados
para o descobrimento de nós;
• Protocolos: O HTTP, o SMTP (Simple Mail Transfer Protocol ), e outros protocolos, são utilizados para implementar os serviços oferecidos pelos sistemas
baseados no modelo cliente-servidor. Os sistemas baseados no modelo peerto-peer utilizam protocolos especı́ficos, tais como Gnutella, FastTrack, entre
outros.
As caracterı́sticas de um ou de outro modelo podem fundir-se em um mesmo aplicativo, dependendo de como ele é implementado. Essa conjunção de caracterı́sticas
permite que diferentes arquiteturas de redes peer-to-peer sejam desenvolvidas, dependendo do grau qualitativo de envolvimento dos nós em cada um dos modelos
peer-to-peer e cliente-servidor [29].
2.4
A classificação das arquiteturas de redes peer-to-peer
As arquiteturas de redes peer-to-peer são divididas em semi-centralizadas e descentralizadas1 . As arquiteturas semi-centralizadas são subclassificadas em semi1
Essa classificação foi elaborada tendo-se a arquitetura centralizada como implementação do modelo
cliente-servidor.
2.4. A classificação das arquiteturas de redes peer-to-peer
13
centralizadas intermediadas e semi-centralizadas não intermediadas. As arquiteturas
descentralizadas são subclassificadas em descentralizadas estruturadas e descentralizadas não estruturadas.
As arquiteturas descentralizadas não estruturadas são divididas em descentralizadas não estruturadas puras e descentralizadas não estruturadas baseadas em
ultranós. Ambas podem usufruir de uma abordagem pura ou guiada [15, 21, 38, 46].
A classificação proposta pode ser melhor representada pela Figura 2.4.
Figura 2.4: Arquiteturas de redes peer-to-peer
A Figura 2.4 posiciona as arquiteturas dentro de um pólo que representa as
centralizadas, e outro pólo que representa as descentralizadas. Arquiteturas centralizadas são aquelas que dependem de uma entidade central para seu funcionamento.
A arquitetura cliente-servidor, por exemplo, é totalmente centralizada, pois depende
do funcionamento do servidor, como entidade central, para que suas tarefas sejam
executadas e seus serviços oferecidos aos clientes. As arquiteturas descentralizadas,
por sua vez, não dependem de uma entidade central e organizam-se de maneira
independente de uma única entidade.
2.4.1
Arquitetura semi-centralizada intermediada
A arquitetura semi-centralizada intermediada é a que mais se aproxima, em termos de papéis e responsabilidades dos nós que a compõem, de uma arquitetura
2.4. A classificação das arquiteturas de redes peer-to-peer
14
centralizada. Os nós que desejam enviar mensagens, transferir arquivos, ou trocar
qualquer fluxo de dados com outros nós devem, antes de iniciar sua comunicação,
autenticar-se em uma entidade central, registrando, assim, sua presença.
Toda comunicação vai ser controlada pela entidade central, ou seja, ao enviar
dados para o outro nó, estes dados passam por esta entidade central, que os encaminha para o destinatário. A resposta, de maneira análoga, é enviada primeiramente
à entidade central, que a encaminha para o nó de origem.
Esta arquitetura possui como vantagem a possibilidade de filtrar pacotes, armazenar informações sobre a troca de dados, tais como horário, datas e textos. No
entanto, é uma arquitetura pouco escalável, uma vez que o aumento no número de
clientes pode facilmente levar à sobrecarga na entidade central.
A diferença básica desta arquitetura para uma totalmente centralizada, é que
os recursos não estão localizados na entidade central, mas nos nós serventes que o
cercam. Esta arquitetura pode, ainda, ser utilizada como solução para o compartilhamento de recursos entre dois nós que estão atrás de firewalls. A arquitetura
semi-centralizada intermediada pode ser vista na Figura 2.5.
Figura 2.5: Arquitetura semi-centralizada intermediada
2.4. A classificação das arquiteturas de redes peer-to-peer
15
A utilização de uma entidade central mostra que o modelo cliente-servidor é
utilizado em conjunção com o modelo peer-to-peer para executar a tarefa de mediar
a comunicação entre dois serventes. Isso faz com que esta arquitetura tenda ao
pólo centralizador dentro do espectro de classificação, sob a perspectiva do grau de
centralização.
2.4.2
Arquitetura semi-centralizada não intermediada
Essa arquitetura caracteriza-se pela presença de um servidor central, onde são disponibilizadas informações sobre os recursos que os nós compartilham, mais comumente
arquivos, além da localização destes recursos na rede [50].
Todas as consultas, que têm como objetivo buscar recursos, são direcionadas ao
servidor central que, ao recebê-las, escolhe os nós mais adequados para satisfazê-las.
Em aplicativos de compartilhamento de arquivos, por exemplo, o nó requerente envia
uma consulta por um determinado arquivo ao servidor central. Assim que este nó
recebe a resposta do servidor, ele se conecta diretamente ao nó que oferece o arquivo,
para efetuar a transferência, sem a intervenção do servidor central, caracterizando,
então, nesta etapa, o modelo peer-to-peer.
É necessário, portanto, que os usuários da rede se autentiquem antes de começar a
requisitar ou servir arquivos. Como na arquitetura semi-centralizada intermediada,
a escalabilidade desta arquitetura é limitada pois, quando o número de nós aumenta,
e mais espaço de armazenamento para os ı́ndices dos recursos são necessários, exigese mais carga de trabalho por parte dos servidores centrais. Contudo, a experiência
do Napster mostrou que essa arquitetura é bastante robusta e eficiente [14, 36]. A
Figura 2.6 representa a arquitetura em questão.
2.4. A classificação das arquiteturas de redes peer-to-peer
16
Figura 2.6: Arquitetura semi-centralizada não intermediada
Uma vantagem peculiar dessa arquitetura é a qualidade dos resultados. As respostas são fiéis, pois o servidor central armazena informações de arquivos em nós
atualmente participantes na rede. Além disso, caso o servidor não esteja sobrecarregado, a resposta é imediata e não é necessário o envio de muitas mensagens para
atingir a um número razoável de respostas. Se o servidor falha, o sistema todo é
interrompido. No entanto, esta arquitetura pode ser organizada de várias formas, a
fim de melhorar a tolerância a falhas e o desempenho [50].
2.4.3
Arquitetura descentralizada não estruturada pura
A arquitetura descentralizada não estruturada pura é caracterizada por não possuir
um servidor central para autenticar usuários e auxiliar na busca de arquivos, como
mostrado na Figura 2.7, assim como não apresenta nenhum tipo de estrutura de
organização da rede. Com isso, dois problemas devem ser tratados: como encontrar
2.4. A classificação das arquiteturas de redes peer-to-peer
17
nós para se conectar à rede e como encontrar recursos, por exemplo, arquivos, na
malha de nós.
Figura 2.7: Arquitetura descentralizada não estruturada pura
O primeiro problema é causado pela caracterı́stica dinâmica das redes peer-topeer. Nós se conectam e desconectam de uma maneira não previsı́vel. Com isso,
diferentes técnicas devem ser utilizadas para encontrar os nós disponı́veis na rede,
tais como web caching e pong caching [17, 18, 44].
Para a solução do segundo problema, pode-se utilizar algoritmos sob a estratégia
cega, ou sob a estratégia guiada [48]. A estratégia cega propaga uma consulta para
um número suficiente de nós, sem a utilização de informação de consultas prévias.
Já a guiada utiliza informações sobre consultas passadas para escolher a melhor rota
para as mensagens.
O exemplo tradicional de uma estratégia cega é o algoritmo utilizado pela rede
Gnutella [17,18]. Esse algoritmo executa inundação baseado em BFS (Breadth First
2.4. A classificação das arquiteturas de redes peer-to-peer
18
Search), ou seja, cada consulta é enviada a todos os nós diretamente conectados ao
nó requerente. O envio de mensagens é feito por cada nó que recebe a consulta, até
que ocorra um número máximo de encaminhamentos, definido pelo parâmetro TTL
(Time To Live), decrementado a cada iteração.
Esse algoritmo apresenta um alto grau de consumo de largura de banda. A partir
da boa manipulação do número de conexões de cada nó e da configuração apropriada
do valor do parâmetro TTL das mensagens, esta arquitetura pode ser utilizada por
centenas de milhares de nós, requerendo alta capacidade dos enlaces de comunicação
para proporcionar desempenho razoável, apresentando problemas de escalabilidade,
quando o objetivo é alcançar todos os nós em uma rede [35].
O DFS (Depth First Search) processa a consulta localmente e, aleatoriamente,
escolhe um de seus nós vizinhos para encaminhar a mensagem de busca. Este algoritmo também pode ser referenciado como chain mode, em contraste com o broadcast
mode do BFS. Há, também, a possibilidade de se implementar esse algoritmo sob a
abordagem guiada [5].
O BFS modificado [49] é uma variação do algoritmo de inundação, em que os
nós escolhem aleatoriamente uma parcela de seus vizinhos para encaminhar as mensagens de requisições. Esse algoritmo reduz a produção média de mensagens em
comparação com o BFS tradicional, mas, mesmo assim, causa um alto consumo de
largura de banda.
O algoritmo de aprofundamento iterativo [21, 51] usa buscas BFS consecutivas com profundidades cada vez maiores. Alcança melhores resultados quando a
condição de término de propagação de consulta está relacionada com o número de
resultados definido pelo usuário, sendo possı́vel que poucas iterações satisfaça a consulta. Em casos diferentes deste, o desempenho do algoritmo pode ser ainda pior do
que o BFS tradicional.
No algoritmo de caminhos aleatórios [51], o nó requerente envia k mensagens
para um número k de nós vizinhos escolhidos aleatoriamente. Cada uma destas
mensagens segue seu próprio caminho; os nós intermediários encaminham-as para
nós aleatórios a cada iteração. Essas consultas podem também ser chamadas de
“andarilhos”. Cada “andarilho” pode encerrar seu trajeto a partir de uma falha,
ou de um sucesso. A falha pode ser dada por esgotamento de TTL ou pelo método
2.4. A classificação das arquiteturas de redes peer-to-peer
19
de checagem, quando o “andarilho” confere com o nó raiz se a sua consulta já foi
satisfeita, ou um número definido de respostas já foi atingido.
A grande vantagem desse algoritmo é a redução significativa no número de mensagens propagadas. Ele produz k * TTL2 mensagens no pior caso, e ainda alcança
um bom balanceamento de carga, uma vez que nenhum nó é favorecido no processo
de encaminhamento de mensagens. A desvantagem é que apresenta um desempenho
variável, uma vez que depende da topologia da rede e das decisões aleatórias feitas
pelos “andarilhos”.
Alguns exemplos de algoritmos de estratégia guiada são o BFS inteligente, o APS
(Adaptative Probabilistic Search), o algoritmo baseado em ı́ndices locais, baseado em
ı́ndices de roteamento e o que se fundamenta no protocolo de localização de conteúdo
distribuı́do, ou DRLP (Distributed Resource Location Protocol ).
O BFS inteligente [49] é um modelo “informado” do BFS modificado, pois armazena informações sobre consultas já efetuadas pelos seus vizinhos, ou através de
seus vizinhos. Primeiramente, um nó identifica uma consulta similar àquela que está
sendo processada, de acordo com alguma métrica de similaridade. Então o nó encaminha a consulta ao nó que retornou resultados mais “parecidos” com a consulta
atual.
No algoritmo APS [47], o nó armazena uma entrada para cada arquivo que
ele solicitou a seus vizinhos. O valor desta entrada reflete a probabilidade de um
determinado vizinho ser escolhido como próximo salto em uma consulta futura por
um arquivo especı́fico. A busca é baseada em k “andarilhos” independentes e com
encaminhamento probabilı́stico. Cada nó intermediário encaminha a consulta para
um nó vizinho, com a probabilidade dada por sua entrada. Os valores dos ı́ndices
são atualizados por feedback dos “andarilhos”. Se um deles falha, a probabilidade
relativa do nó diminui, enquanto se for bem sucedido, a probabilidade relativa do
nó aumenta.
No algoritmo de ı́ndices locais [51], cada nó indexa todos os arquivos armazenados
em nós localizados a um raio de r saltos do nó considerado, e pode responder a
consultas em nome destes nós. A consulta é efetuada de uma maneira similar ao
2
O TTL é definido como o número de saltos, ou hops, como normalmente utilizado em redes peer-to-peer,
e não como um espaço de tempo.
2.4. A classificação das arquiteturas de redes peer-to-peer
20
BFS, no entanto, os nós acessı́veis ao raio definido podem processar a consulta.
Com isso, diminui-se o número de saltos necessários para se atingir um dado nó,
reduzindo o número de mensagens propagadas na rede.
O algoritmo de ı́ndices de roteamento [7] faz com que os arquivos, ou documentos,
sejam divididos em categorias. Cada nó sabe um número aproximado de documentos
de cada categoria que podem ser recuperados através de cada conexão. A condição
de parada está sempre relacionada com um número definido de respostas. Um nó
que não pode satisfazer à condição de parada com seu repositório local encaminha
a mensagem para o seu vizinho que possa “melhor” satisfazer à consulta.
Por fim, o algoritmo DRLP [26] estabelece que nós sem informação sobre a
localização de um arquivo encaminhem a consulta para vizinhos com alguma probabilidade de resolver essa consulta. Se o arquivo é encontrado, a consulta é revertida
para o requerente, armazenando a localização deste documento nos nós do caminho
de volta. Em consultas futuras, nós com informações sobre a localização do recurso
entram em contato diretamente com o nó indicado, sem encaminhar a mensagem
pelos nós intermediários.
De maneira geral, a arquitetura descentralizada não estruturada pura resulta em
um sistema extremamente tolerante a falhas, pois, pouco ou nada será modificado
referente ao bem-estar da rede caso um nó sofra uma falha. Uma outra caracterı́stica
que essa arquitetura possui é a boa escalabilidade, em que o funcionamento do
sistema não se degrada quando mais computadores são adicionados à rede.
Alguns aplicativos peer-to-peer implementam essa descentralização pura, permitindo que arquivos legalmente restritos sejam compartilhados e transferidos. Em
uma arquitetura descentralizada é mais difı́cil de se identificar um usuário especı́fico
do que um servidor em uma arquitetura mais centralizada, o que dificulta o bloqueio da transmissão de arquivos com conteúdos protegidos e garante privacidade
ao usuário, mantendo-o anônimo por não precisar se autenticar em uma entidade
central.
2.4.4
Arquitetura descentralizada estruturada
A arquitetura descentralizada estruturada, também referenciada por arquitetura de
tabelas de hash distribuı́das, ou simplesmente DHT (Distributed Hash Table), é a
2.4. A classificação das arquiteturas de redes peer-to-peer
21
mais recente proposta para a busca de recursos em sistemas peer-to-peer. Nesta
arquitetura, um número identificador é associado a cada nó da rede, e cada um
deles conhece uma determinada quantia de outros nós.
Quando um documento, ou arquivo, é publicado, um número identificador é
associado a ele baseado em um hash do seu conteúdo, ou do seu nome. Cada nó,
então, o encaminha ao nó cujo identificador é mais próximo do identificador do
documento, até que se atinja o local mais adequado para o seu armazenamento.
Quando um nó efetua uma consulta, a requisição é transferida até o nó com
identificador mais semelhante ao do documento buscado. Esse processo continua
até que uma cópia do documento seja encontrada para que seja, então, transferido
ao nó que originou a requisição. Cópias desse documento podem ser feitas em cada
nó que participou do roteamento, para melhorar o desempenho da busca.
Apesar de ser eficiente para comunidades grandes e globais, a arquitetura baseada
em DHT apresenta um problema relacionado ao identificador do documento: este
identificador precisa ser conhecido antes que uma consulta seja realizada. Assim
sendo, é mais difı́cil implementar um esquema de busca nesta arquitetura do que nas
demais. Outra desvantagem é que, quando um documento é publicado, o algoritmo
força a replicação deste documento em um nó especı́fico. Pode não ser do gosto do
usuário ter de armazenar esse documento em seu espaço compartilhado. A Figura
2.8 representa esta arquitetura.
Figura 2.8: Arquitetura descentralizada estruturada
2.4. A classificação das arquiteturas de redes peer-to-peer
22
Quatro principais algoritmos foram desenvolvidos para esta arquitetura: Chord
[43], CAN (Content Addressable Network ) [34], Tapestry [53] e Pastry [37]. Os
objetivos desses algoritmos são similares: reduzir o número de saltos necessários
para encontrar um determinado arquivo e minimizar a quantidade de informação
armazenada em cada nó para auxı́lio de roteamento das mensagens.
2.4.5
Arquitetura descentralizada não estruturada baseada em ultranós
A arquitetura baseada em ultranós [39], ou supernós, ou, ainda, nós hub, divide
a rede overlay em dois nı́veis: o nı́vel de ultranós, e o nı́vel de nós folha, como
apresentado na Figura 2.9.
Figura 2.9: Arquitetura descentralizada não estruturada baseada em ultranós
Os ultranós vão ter a responsabilidade de atuar como servidores proxy para os demais nós, os nós folha, que estão abaixo na hierarquia da rede overlay. Estes ultranós
vão “proteger” os nós folha do fluxo de mensagens, evitando, assim, a sobrecarga
em nós com enlace, com memória ou com poder de processamento limitados.
A eleição dos nós que devem fazer parte do nı́vel de ultranós é dependente da implementação do protocolo de camada de aplicação utilizado. O tempo de resposta, a
média de tempo conectado na rede overlay, a capacidade do enlace, o poder de processamento e a quantidade de memória disponı́vel podem ser alguns dos parâmetros
2.5. Protocolos para sistemas de compartilhamento de arquivos
23
utilizados para a eleição [52].
O algoritmo GUESS [8] foi uma das primeiras propostas de implementação desta
arquitetura. Nesse algoritmo uma busca é feita por meio do contato interativo com
os diferentes ultranós, não necessariamente vizinhos. Como os ultranós armazenam
informações de arquivos compartilhados por nós folha a ele conectados, uma única
consulta apresenta alto grau de abrangência. A ordem que os ultranós são contatados
não é especificada.
Outro algoritmo que implementa a noção de ultranós é aplicado às redes Gnutella2 [44]. Quando um ultranó recebe uma consulta, aquele processa localmente a
consulta em nome de seus nós folha e a encaminha para seus nós hub vizinhos relevantes. Os nós hub repetem o processo até que o TTL da mensagem se esgote. O nós
hub que são vizinhos trocam tabelas de informações para filtrar tráfego desnecessário
entre eles.
A grande vantagem do uso desta arquitetura é que, por um lado, ela alcança a
robustez da arquitetura cliente-servidor e, por outro, adquire a caracterı́stica descentralizada, dinâmica e anônima das arquiteturas puramente descentralizadas. A
grande desvantagem é a sobrecarga que se pode causar em um nó eleito hub. No
entanto, com boas polı́ticas de eleição de ultranós e de escolha do número de nós
folha permitidos por nó hub, pode-se atingir um bom balanceamento de carga [52].
As redes descentralizadas não estruturadas baseadas em ultranós também podem
ser implementadas sob diferentes abordagens. A abordagem cega, tal como GUESS,
faz consultas aleatoriamente aos ultranós, ou seja, não usa nenhuma informação de
consultas passadas para efetuar as suas. Com o uso de informações de consultas
prévias, como feito pelo protocolo Gnutella2, as mensagens são enviadas a nós que
possuem uma maior probabilidade de responder a uma dada consulta, evitando,
assim, o envio de mensagens desnecessárias a nós com baixa probabilidade.
2.5
Protocolos para sistemas de compartilhamento de arquivos
Para que os nós em uma rede overlay possam comunicar-se, um protocolo deve ser
especificado. O protocolo define as regras que governam as sintaxes, semânticas
2.5. Protocolos para sistemas de compartilhamento de arquivos
24
e a sincronização da troca de informação entre os sistemas finais, e permite a conexão, comunicação e transferência de dados entre os nós na rede. Nesta seção
são apresentados alguns dos protocolos mais utilizados na Internet em sistemas de
compartilhamento de arquivos.
2.5.1
Gnutella
O protocolo Gnutella utiliza, para a propagação das mensagens, o algoritmo BFS que
utiliza a arquitetura descentralizada não estruturada pura e cega. Esse protocolo
provê uma interface sob a qual os usuários podem executar consultas e ver seus
resultados, e ao mesmo tempo permite que os aplicativos respondam às consultas
de outros usuários. As versões 0.4 e 0.6 foram definidas e consideradas estáveis, e
sua documentação é aberta [17, 18].
Pode-se dizer que o Gnutella é um dos primeiros protocolos a ser considerado puramente peer-to-peer. Foi desenvolvido pela NullSoft e foi disponibilizado sob a GPL
(GNU Public License) por volta de março de 2000. A AOL (America Online) adquiriu a NullSoft após o surgimento do WinAmp e, devido a problemas com gravadoras
a partir da experiência do Napster, a AOL suspendeu qualquer desenvolvimento
formal do Gnutella [31].
No entanto, uma reação ocorreu por parte de usuários da Internet, que uniram
esforços individuais e corporativos para inovar o Gnutella e mantê-lo funcionando.
Várias versões do protocolo foram implementadas e há diversos aplicativos que o
utilizam em diferentes sistemas operacionais, tais como Windows, Macintosh e Linux.
A versão 0.6 do protocolo adota a eleição de alguns nós potenciais para fazer
função de ultranós, ou nós hub, atuando como proxy para outros nós, chamados de
nós folha, com o objetivo de diminuir o tráfego de mensagens de consulta entre nós
com deficiência em termos de largura de banda, processamento e memória. Dessa
maneira, o balanceamento de carga faz com que a maior parte do trabalho seja feita
por nós com mais capacidade.
O protocolo Gnutella define a maneira pela qual os serventes se comunicam
através da rede. Ele consiste de mensagens, ou descritores, e um conjunto de regras
que governam a troca de informação entre os nós, ou serventes. A versão 0.4 já
2.5. Protocolos para sistemas de compartilhamento de arquivos
25
define as cinco principais mensagens:
• PING: Utilizado para descobrir nós Gnutella na rede. Um servente que recebe
uma mensagem do tipo PING deve responder com uma ou mais mensagens do
tipo PONG;
• PONG: É a mensagem de resposta ao PING. Inclui o endereço de um servente
Gnutella e informações sobre a quantidade de dados que ele disponibiliza à
rede;
• QUERY: É a mensagem que implementa o mecanismo de busca na rede. Um
servente que recebe a mensagem do tipo QUERY deve responder com uma
mensagem do tipo QUERYHIT, caso o arquivo procurado seja encontrado;
• QUERYHIT: Utilizada como resposta à mensagem do tipo QUERY. Esta mensagem provê informações suficientes para que o arquivo procurado possa ser
recuperado pelo servente que originou a consulta;
• PUSH: Esta mensagem implementa o mecanismo que permite que serventes
atrás de firewalls contribuam com seus arquivos aos demais nós.
Um nó se conecta à rede Gnutella ao estabelecer uma conexão com outro já
participante. A maneira pela qual os endereços de outros serventes são adquiridos
não faz parte da definição do protocolo.
Estabelecimento de conexão e negociação do protocolo
Uma vez que o endereço IP de outro servente é obtido, uma conexão TCP é criada,
e a seguinte string ASCII (American Standard Code for Information Interchange)
deve ser enviada:
GNUTELLA CONNECT\<vers~
ao do protocolo>\n\n
O indicador de “versão do protocolo” é definido como a string (ASCII) “0.4”,
nessa versão, seguidos de dois caracteres line-feed (0xA). Um servente que deseja
aceitar a conexão deve responder com a seguinte mensagem:
2.5. Protocolos para sistemas de compartilhamento de arquivos
26
GNUTELLA OK\n\n
Qualquer outra resposta indica que o servente não deseja efetivar a conexão.
Vários motivos podem justificar esta situação, entre eles o número de conexões
máximo permitido foi atingido, ou a versão do protocolo que não é suportada.
As mensagens Gnutella
Uma vez que dois serventes obtiveram sucesso na conexão, eles se comunicam enviando e recebendo mensagens Gnutella. Cada mensagem é precedida por um
cabeçalho com o formato mostrado pela Figura 2.103 4 .
Figura 2.10: Cabeçalho das mensagens Gnutella
• ID da mensagem: String de 16 bytes que identifica unicamente a mensagem
na rede. Este valor deve ser preservado ao se encaminhar as mensagens entre
os serventes. Permite a detecção de ciclos e auxilia na redução de tráfego
desnecessário;
• Descritor de payload : Indica o tipo de carga que a mensagem carrega. Os
valores são definidos como mostra a Tabela 2.1:
Tabela 2.1: Descritores de payload do Gnutella
0x00
PING
0x01
PONG
0x80
QUERY
0x81 QUERYHIT
0x40
PUSH
3
Todas as estruturas são utilizadas com ordenação de bytes little-endian, a não ser nas exceções especificadas.
4
Todos os endereços IP das estruturas são do formato IPv4, definidos como big-endian.
2.5. Protocolos para sistemas de compartilhamento de arquivos
27
Este protocolo não define meios para o rastreamento de padrões dentro da
string recebida, e como os dados são transferidos através de uma conexão
TCP, que é um fluxo contı́nuo de bytes, este campo deve ser rigorosamente
validado para manter o sincronismo. Uma desincronização pode ser detectada
pela presença de um descritor de payload inválido;
• TTL (Time to live): Indica o número máximo de encaminhamentos da mensagem permitido até que seja retirada da rede. Cada servente deve decrementar o
TTL antes de a transferir para outro servente. Quando o TTL alcança o valor
zero, a mensagem não pode mais ser encaminhada. Os serventes devem cuidadosamente averiguar o campo TTL e atualizá-lo adequadamente. Os abusos
na utilização deste campo podem levar ao excesso de tráfego na rede;
• Hops: Indica o número de vezes que uma mensagem foi encaminhada. Enquanto as mensagens são encaminhadas de servente a servente através da rede,
os valores de TTL e Hops devem satisfazer os seguintes critérios:
– T T L(i) + Hops(i) =T T L(0) ;
– T T L(i+1) < T T L(i) ;
– Hops(i+1) > Hops(i) .
Os valores de T T L(i) e Hops(i) são os valores dos campos TTL e Hops no
cabeçalho das mensagens no i -ésimo encaminhamento, para i >= 0;
• Tamanho do payload : Representa o tamanho da mensagem imediatamente após
o cabeçalho. O cabeçalho da próxima mensagem é encontrado exatamente após
o número de bytes indicado por este campo.
Imediatamente após o cabeçalho das mensagens há o payload especı́fico, cujo
conteúdo e estrutura dependem do valor do “Descritor de payload ”. Esses descritores de payload e suas respectivas estruturas são apresentados a seguir.
PING
As mensagens do tipo PING não costumam possuir nenhum payload adicional e
apresentam tamanho nulo, sendo simplesmente um cabeçalho com valor 0x00 no
2.5. Protocolos para sistemas de compartilhamento de arquivos
28
campo de “Descritor de payload ”. No entanto, caso houver dados após o cabeçalho,
o seu payload pode ser como mostrado na Figura 2.11.
Figura 2.11: A mensagem PING
Um servente utiliza a mensagem PING para conseguir dados de outros nós disponı́veis na rede virtual. Ao receber esta mensagem, o servente pode responder com
uma mensagem PONG, que contém o endereço de um nó Gnutella participante, podendo ser ele próprio, enviando também os dados sobre os arquivos que compartilha
na rede. O campo “Dados opcionais” consiste de um número variável de bytes que
são reservados para futuras extensões do protocolo.
Não há a necessidade de se encaminhar mensagens PING para outros serventes, ou, ainda, encaminhá-los com altos valores de TTL e Hops. A maioria das
implementações incluem uma polı́tica para limitar o tráfego de PINGs.
PONG
O payload da mensagem de tipo PONG é como mostrado na Figura 2.12.
Figura 2.12: A mensagem PONG
• Porta: Indica a porta TCP através da qual o nó pode receber conexões Gnutella;
• Endereço IP: Indica o endereço IP do host;
• Número de arquivos compartilhados: Representa o número de arquivos que o
host disponibiliza na rede;
2.5. Protocolos para sistemas de compartilhamento de arquivos
29
• Kbytes compartilhados: Representa a quantidade, medida em Kbytes, de dados
que o host disponibiliza na rede;
• Dados opcionais: Um campo opcional, de tamanho variável, reservado para
extensões do protocolo.
As mensagens do tipo PONG são somente enviadas em resposta a uma mensagem PING. Múltiplas PONGs podem ser enviadas, permitindo, assim, que dados
em cache sobre outros serventes sejam transmitidos através dos nós.
QUERY
A mensagem do tipo QUERY possui o payload representado pela Figura 2.13.
Figura 2.13: A mensagem QUERY
• Velocidade mı́nima: A velocidade mı́nima, em kbits/segundo, de serventes que
respondem à essa mensagem. Um servente que recebe uma mensagem QUERY
deve responder com uma QUERYHIT somente se for possı́vel comunicar-se a
uma velocidade maior ou igual à indicada.
• String de busca: A string de busca é encerrada por um NULL, sendo o tamanho máximo restringido pelo campo “Tamanho do payload ” presente no
cabeçalho. Deve-se usar uma codificação compatı́vel com ASCII. Nenhuma
delas foi especificada, mas a maioria dos serventes utilizam a ISO-8859-1 ou a
UTF-8. Essa consulta deve consistir de uma lista de palavras separadas por um
espaço ASCII (0x20=32) que podem, opcionalmente, carregar a extensão do
formato do arquivo buscado, depois do caracter “ponto” em ASCII (0x2e=46);
• NULL: O campo que encerra a string de busca;
2.5. Protocolos para sistemas de compartilhamento de arquivos
30
• Dados de consulta opcionais: Este campo é opcional e de tamanho variável, e
é reservado para extensões do protocolo. Alguns serventes utilizam este campo
para consultas baseadas em metadados, tal como o XML Extensible Markup
Language.
QUERYHIT
A mensagem do tipo QUERYHIT possui o payload representado pela Figura 2.14.
Figura 2.14: A mensagem QUERYHIT
• Número de hits: Número de arquivos que satisfazem à consulta;
• Porta: Indica a porta em que o host que responde pode receber conexões para
a transferência dos arquivos;
• Endereço IP: Indica o endereço IP do host;
• Velocidade: Indica a velocidade máxima, em kbits/s do host;
• Conjunto de resultados: Um conjunto de respostas para a QUERY recebida,
contendo “Número de hits” resultados, cada uma contendo uma estrutura como
representada pela Figura 2.15
Figura 2.15: A estrutura de um resultado da QUERYHIT
O campo “Índice de arquivo” é um valor associado ao arquivo que o unicamente
identifica dentro do conjunto de respostas enviado. O “Tamanho do arquivo”
indica o tamanho do arquivo em bytes. O “Nome do arquivo” indica o nome
2.5. Protocolos para sistemas de compartilhamento de arquivos
31
do arquivo compartilhado, terminado em NULL. Os “Dados opcionais” podem
conter metadados sobre o arquivo compartilhado.
• Dados opcionais: Campo opcional para dados de QUERYHITs estendidos;
• Identificador do servente: Este campo de 16 bytes identifica unicamente o servente na rede. Tipicamente é calculado por alguma função sobre seu endereço
IP. Este identificador é instrumento para a operação efetuada pela mensagem
PUSH.
As mensagens do tipo QUERYHIT são enviadas somente em resposta a uma
QUERY. Um servente deve responder somente se contém arquivos que podem satisfazer o critério de busca. Devem ser gerados inicialmente com o valor de Hops em
zero, e o valor TTL igual ao número de Hops efetuados pela mensagem QUERY correspondente. O “ID do descritor” deve conter o mesmo valor associado à QUERY,
permitindo, assim, que um servente possa rotear as QUERYHITs adequadamente.
A mensagem QUERYHIT, com sua estrutura complexa, é aquela que pode apresentar o payload de maior tamanho. Para a melhor eficiência, um servente que recebe
uma QUERY deve limitar a quantidade de dados que envia como resposta em uma
QUERYHIT. Quando muitos hits são detectados, os serventes devem dividi-los em
um subconjunto de resultados, e enviá-los separadamente com atrasos. O processo
de roteamento dos QUERYHITs de volta ao servente que originou a consulta é chamado de backtrack.
PUSH
A mensagem do tipo PUSH possui o payload representado pela Figura 2.16.
Figura 2.16: A mensagem PUSH
2.5. Protocolos para sistemas de compartilhamento de arquivos
32
• ID do servente: String de 16 bytes que identifica unicamente o servente na rede.
Este servente está sendo requisitado para “empurrar” o arquivo indicado por
“Índice do arquivo”. O servente que inicia o PUSH deve atribuir a este campo
o mesmo valor dado ao campo “ID de servente” da mensagem QUERYHIT;
• Índice do arquivo: Esse valor identifica unicamente o arquivo a ser “empurrado” pelo servente. O nó que inicia o PUSH deve atribuir a este campo o valor
dado em “Índice do arquivo” de um arquivo contido no conjunto de resultados
do QUERYHIT correspondente;
• Endereço IP: Indica o endereço IP do host para o qual o arquivo deve ser
“empurrado”;
• Porta: Indica a porta do host para a qual o arquivo deve ser “empurrado”;
• Dados opcionais: Este campo é reservado para futuras extensões do protocolo.
Um servente deve enviar uma mensagem PUSH caso receba uma QUERYHIT de
um servente que não pode receber pedidos de conexão. Isso pode ocorrer em casos
de nós que estão atrás de firewalls. Quando um servente recebe uma PUSH, ele deve
dar continuidade ao processo somente se o seu identificador de servente é igual ao
valor contido em “ID do servente” da mensagem recebida.
Roteamento de mensagens
A natureza descentralizada das redes Gnutella requer que mensagens sejam roteadas através dos nós de maneira apropriada. Um servente Gnutella deve rotear as
mensagens segundo os seguintes critérios:
• PONGs só podem ser enviadas através do mesmo caminho que a PING correspondente atravessou. Um servente que recebe uma PONG cujo identificador
não está armazenado no seu cache de identificadores de PINGs enviadas ou
roteadas deve remover a PONG da rede;
• QUERYHITs só podem ser enviadas através da mesma rota que a QUERY
correspondente atravessou. Um servente que recebe uma QUERYHIT cujo
2.5. Protocolos para sistemas de compartilhamento de arquivos
33
identificador não está armazenado no seu cache de identificadores de QUERYs
roteadas ou enviadas deve remover a QUERYHIT da rede;
• PUSHs só podem ser enviadas através da mesma rota que a QUERYHIT correspondente atravessou. Um servente que recebe uma PUSH cujo identificador
de servidor não está armazenado no seu cache de identificadores de servidor
enviados ou roteados deve remover a PUSH da rede;
• Um servente deve encaminhar PINGs e QUERYs a todos os seus nós vizinhos,
exceto àquele que enviou a mensagem;
• Um servente deve decrementar o campo TTL e incrementar o Hops antes de
encaminhar qualquer mensagem aos nós vizinhos. Se, após decrementado, o
TTL apresentar o valor zero, a mensagem não deve ser mais roteada através
das conexões, sendo removida da rede;
• Um servente que recebe uma mensagem com “Descritor de payload ” e “ID de
descritor” iguais a algum já antes recebido, deve descartar a mensagem;
As Figuras 2.17 e 2.18 apresentam exemplos de roteamento em uma pequena
rede Gnutella.
2.5. Protocolos para sistemas de compartilhamento de arquivos
Figura 2.17: Exemplo de Roteamento de PINGs e PONGs
34
2.5. Protocolos para sistemas de compartilhamento de arquivos
Figura 2.18: Exemplo de Roteamento de QUERYs, QUERYHITs e PUSHs
35
2.5. Protocolos para sistemas de compartilhamento de arquivos
36
Para visualizar como o Gnutella trabalha, pode-se imaginar uma grande malha de
nós, representando vértices de um grafo, e suas conexões representadas por arestas.
O nó precisa, no inı́cio, encontrar pelo menos um outro nó já participante na rede,
para poder propagar suas mensagens e rotear mensagens de outros nós.
O grande problema da rede Gnutella é a maneira como seu protocolo implementa
o roteamento de mensagens. Dada uma consulta, o nó envia uma mensagem do
tipo QUERY a todos os nós diretamente a ele conectados. Estes nós, em seguida,
processam a consulta localmente, enviando um QUERYHIT em caso de sucesso, e
propagam a consulta a todos os nós a eles diretamente conectados, com exceção do
nó que a enviou a.
A consulta vai ser propagada de nó-a-nó até que o valor de TTL no cabeçalho
dos pacotes atinja zero. Este mecanismo é chamado de query flooding e, devido à
natureza broadcast da consulta, o sistema não possui uma boa escalabilidade [35].
A demanda de largura de banda cresce exponencialmente com um acréscimo
linear do número de nós, portanto, aumentar o número de nós pode causar uma
rápida saturação na rede. A manipulação do valor de TTL pode diminuir o número
de mensagens, no entanto, implica em uma menor eficiência na busca, por atingir
um número menor de nós.
Na prática, a busca numa rede Gnutella é lenta e não confiável. Cada nó é um
computador comum, e, portanto, estão constantemente conectando e desconectando,
fazendo com que a rede nunca esteja completamente estável. Uma vez que conexões
de usuários individuais são quase sempre lentas, pode-se tomar muito tempo para a
consulta se transferir por grande parte da rede.
Esse protocolo oferece flexibilidade no processamento das consultas pois, cada
nó pode determinar como processar uma consulta e responder de acordo com ela.
Por outro lado, sistemas Gnutella são muito suscetı́veis a atividades maléficas. Os
nós com más intenções podem enviar muitas consultas, o que produz uma carga
significantemente grande na rede.
2.5. Protocolos para sistemas de compartilhamento de arquivos
37
Download de arquivos
Assim que um servente recebe um QUERYHIT, o usuário pode decidir iniciar o
download de um dos arquivos presentes no “conjunto de resultados”. Os arquivos
são transferidos por fora da rede Gnutella, através de uma conexão direta entre
os nós. Arquivos de dados nunca são transferidos dentro da rede Gnutella. O
protocolo utilizado para negociar a transferência é o HTTP. Na iniciação do download
o servente que requer o arquivo deve enviar a seguinte string:
GET /get/<Índice do arquivo>/<Nome do arquivo>/ HTTP/1.0\r\n
User-Agent: Gnutella/0.4\r\n
Range: bytes=<Offset de inı́cio>-\r\n
Connection: Keep-Alive\r\n
\r\n
O servidor do arquivo, então, envia uma resposta também compatı́vel com o
HTTP. O arquivo de dados segue esta resposta e deve ser lido por meio do socket
até que se esgote o tamanho especificado na resposta do servidor. O protocolo define
ainda um mecanismo para efetuar o resume de arquivos, caso haja algum problema,
permitindo que a transferência seja reiniciada por algum ponto do meio do arquivo.
2.5.2
Napster
O Napster é um aplicativo para o compartilhamento de arquivos, e seu protocolo,
proprietário, aqui também referenciado simplesmente como Napster, é o representante mais importante das arquiteturas semi-centralizadas não intermediadas, pois
utiliza um servidor central para o armazenamento da informação referente à localização dos arquivos na rede [14].
Sua tecnologia permitiu que usuários compartilhassem arquivos no formato MP3
com facilidade, e isso levou a indústria da música a impetrar massivas acusações
sobre violação de direitos autorais por parte dos usuários e dos proprietários do
aplicativo. Mesmo com a desativação do seu serviço original, o Napster pavimentou
o caminho às redes peer-to-peer descentralizadas, que são muito mais difı́ceis de
controlar.
2.5. Protocolos para sistemas de compartilhamento de arquivos
38
Shawn Fanning foi quem lançou o Napster em 1999. Sua motivação era criar um
sistema para busca de arquivos de música mais fáceis de utilizar do que os disponı́veis
na época, tal como o IRC (Internet Relay Chat). Isso causou um alvoroço grande
o bastante para ı́cones da cena musical mundial e grandes empresas entrarem com
ações judiciais contra os utilizadores e criadores do sistema [31]. Com essa nova
ferramenta, a maneira pela qual as pessoas utilizavam a Internet mudou, e redes de
alta velocidade em muitas universidades passaram a ser sobre-utilizadas: 80% do
tráfego era para transferência de arquivos MP3 [31].
O formato de suas mensagens é proprietário, no entanto é possı́vel observar
algumas operações que são utilizadas. No momento da conexão de um nó na rede,
ele deve autenticar-se a um servidor central e enviar os metadados dos arquivos que
disponibiliza. Os dados ficam armazenados no servidor que responde às consultas
dos nós [1].
Um nó que procura um arquivo direciona-se ao servidor central enviando a consulta. O servidor responde, em caso positivo, com os endereços IP de nós conectados
que podem atender àquela consulta. De posse deste endereço, o nó efetua uma conexão direta com o outro que disponibiliza o arquivo e inicia a transmissão sem a
necessidade do servidor central, caracterizando aqui o modelo peer-to-peer.
Sistemas que são construı́dos de acordo com essa abordagem possuem um desempenho variável, dependendo de como são implementados [50]. Apesar de apresentarem um grande nı́vel de centralização, mostram ser uma opção bastante robusta,
como provado pelo Napster, registrando um pico de 26.4 milhões de usuários em
2001 [28].
2.5.3
Gnutella2
O protocolo Gnutella2 possui documentação aberta [44], sendo um retrabalho do
protocolo Gnutella, porém não reconhecido oficialmente como uma extensão do protocolo oficial. Ele elimina muitos dos aspectos do antigo protocolo Gnutella a não
ser o handshake de conexão, adotando um sistema novo e completo.
O protocolo Gnutella2 não é interoperável com nenhuma versão do protocolo
Gnutella, no entanto utiliza a mesma arquitetura da versão 0.6: a arquitetura descentralizada não estruturada baseada em ultranós.
2.5. Protocolos para sistemas de compartilhamento de arquivos
39
A eleição de quais nós vão atuar como hub é feita com base em vários critérios:
o sistema operacional que permite a abertura de vários sockets simultaneamente,
a quantidade de memória RAM (Random-access memory), a velocidade de processamento, o número de horas conectados à rede, a largura de banda adequada, e a
habilidade de aceitar conexões TCP e UDP sem intervenção de firewalls.
As conexões TCP são utilizadas entre os nós quando eles são eleitos para formarem uma conexão permanente, em uma rede com topologia baseada em ultranós, ou
hubs, fortemente conectados, servindo um denso cluster de nós folha.
Estabelecer uma conexão TCP para entregar um simples pacote de informação é
perda de volume de dados e tempo, especialmente quando considera-se uma grande
quantidade de nós. O UDP oferece uma solução para esse caso, por ser um protocolo
que apresenta um menor overhead.
Porém, como o UDP é um protocolo não confiável, os pacotes podem ser perdidos
ao longo da rota. O protocolo Gnutella2 resolve isto implementando uma camada de
confiabilidade sobre o protocolo básico UDP. Esta camada de confiabilidade divide
funcionalidades comuns ao TCP, mas não provê nenhum estado de conexão e, assim
sendo, aproveita o desempenho implı́cito ao UDP.
O protocolo Gnutella2, portanto, permite que haja comunicação através de três
vias distintas: para um volume significante de dados, ou para o caso de uma comunicação em que dados futuros vão ser trafegados, usa-se o TCP, neste último com
conexão persistente; para volumes pequenos de dados importantes, usa-se o UDP
confiável; para volumes de dados pequenos de dados não muito importantes, usa-se
o UDP não confiável.
O protocolo Gnutella2 permite, ainda, que seja feito o swarming download, em
que pedaços de arquivos podem ser obtidos, paralelamente, de diferentes hosts. Algumas mensagens que são definidas no protocolo Gnutella2 são parecidas com as
mensagens do protocolo Gnutella, gerando confusão ao se levantar aspectos de interoperabilidade.
2.5.4
eDonkey
Desenvolvido pela MetaMachine, o eDonkey, também chamado de eDonkey2000 ou
ed2k, é um protocolo desenvolvido para o compartilhamento de arquivos, especial-
2.5. Protocolos para sistemas de compartilhamento de arquivos
40
mente de músicas, filmes e software [25].
Assim como a maioria dos protocolos de compartilhamento de arquivos, utiliza a
arquitetura descentralizada: arquivos não são armazenados em um servidor central,
mas são trocados diretamente pelos pares, assim como não há indexação centralizada
de arquivos, e sim a utilização de ultranós. A utilização do algoritmo de hash MD4
sobre o conteúdo dos arquivos, permite uma identificação de arquivos idênticos com
nomes diferentes dentro do espaço de armazenamento.
O eMule foi um dos aplicativos mais populares na Internet [27]. O protocolo
utilizado por esse aplicativo é baseado no eDonkey. Cada nó folha é pré-configurado
com uma lista de nós hub e uma lista de arquivos compartilhados em seu sistema
local, que é transmitida ao nó hub no momento de sua conexão.
Uma fila é criada para cada arquivo de modo a controlar os downloads e uploads,
e é possı́vel fazer downloads simultâneos de diferentes localizações para acelerar o
processo e melhorar o balanceamento de carga. É possı́vel também disponibilizar
pedaços de arquivos que sequer tenham terminado seu download completamente.
O protocolo utilizado pelo eMule estende as capacidades do protocolo eDonkey
permitindo que os nós folha troquem informações sobre os nós hub, sobre outros nós
folha e sobre arquivos [19].
Os nós hub, usualmente, não armazenam arquivos, mas, sim, informações sobre
os arquivos localizados nos nós folha. Outra funcionalidade destes nós é intermediar
conexões entre nós folha que estão atrás de firewalls e não podem receber pedidos
de conexão. Essa intermediação de conexões acrescenta uma grande carga nos nós
hub.
Faz parte do protocolo um sistema de créditos que estimulam o usuário a compartilhar mais arquivos, aumentando, assim, a largura de banda disponı́vel a um
determinado nó para fazer downloads. Esse recurso é útil para eliminar os chamados
freeloaders, que utilizam recursos da rede virtual sem prover recursos [19].
2.5.5
FastTrack
O FastTrack é um protocolo proprietário. No entanto, algumas partes de sua implementação são conhecidas [16]. Em sua organização, os nós são divididos em nós
folha e ultranós. Ultranós são responsáveis por indexar os arquivos que os nós folha
2.5. Protocolos para sistemas de compartilhamento de arquivos
41
compartilham e também fazem trabalhos de estatı́sticas.
Os mecanismos de conexão e de distribuição de responsabilidades são similares
ao protocolo Gnutella2. A fase de transferência de arquivos é negociada utilizando
o protocolo HTTP. Os nós folha não se comunicam uns com os outros, a não ser
para a transferência de arquivos.
O aplicativo Kazaa, muito utilizado na Internet, utiliza a porta 1214 para efetuar as transferências das mensagens. No entanto, versões recentes utilizam portas
aleatórias, e algumas escutam na porta 80. Alguns formatos de pacotes e seus tipos
são conhecidos, mas pouco é sabido sobre a comunicação entre ultranós.
Alguns aplicativos que implementam esse protocolo utilizam um sistema de “reputação”, que visa incentivar os usuários a compartilhar arquivos e permitir que
uploads sejam efetuados. Inicia-se com nı́vel de participação de valor dez, e pode-se
atingir o valor 1000. Um maior nı́vel de participação significa que o usuário está
conectado por grandes perı́odos de tempo e permitiu que usuário tirassem proveito
disso, obtendo seus arquivos. Usuários com maiores nı́veis de participação são favorecidos em filas de espera de download e recebem melhor QoS (Quality of Service).
2.5.6
Kademlia
O protocolo Kademlia [24] implementa a arquitetura descentralizada estruturada.
Um identificador é associado a cada nó da rede, e um valor a cada documento. Cada
participante possui uma chave de identificação de 160 bits, e cada documento será
roteado até o nó de melhor posição dentro do sistema. Os pares (chave, documento)
são armazenados em nós com identificadores mais “próximos” à chave, para se ter
a noção de proximidade.
O Kademlia visa a minimizar o número de mensagens que os nós têm que enviar
uns aos outros para adquirir informações sobre a rede. A informação de configuração é espalhada automaticamente como efeito das buscas, e os nós possuem
conhecimento e flexibilidade para rotear consultas através de caminhos de menor
latência.
Um nó que deseja entrar na rede deve antes passar por um processo de iniciação.
Nesta fase, o nó deve descobrir o endereço IP de outro nó, obtido diretamente pelo
usuário, ou por uma lista armazenada, que já está participando da rede Kademlia.
2.5. Protocolos para sistemas de compartilhamento de arquivos
42
Um identificador randômico ainda não utilizado é calculado e atribuı́do ao novo nó.
O algoritmo do Kademlia é baseado no cálculo da “distância” entre dois nós.
Essa distância é calculada como um “ou exclusivo” de dois identificadores, tendo
como resultado um número inteiro. A “distância” não possui relações com condições
geográficas, mas designa a distância dentro da faixa de identificadores. Com isso,
pode ocorrer o caso de um nó na Alemanha e um nó na Austrália serem vizinhos
dentro do sistema.
O número de nós contactados durante uma busca é dependente do tamanho da
rede. Se o número de participantes na rede dobra, então um nó requerente deve
consultar somente um nó a mais por busca, e não duas vezes mais, provando a boa
escalabilidade do sistema.
Algumas vantagens ainda podem ser encontradas na redes descentralizadas estruturadas, que claramente aumentam a resistência contra ataques DoS (Denial of
Service). Mesmo que um conjunto de nós forem atacados, isso implicará um pequeno impacto na disponibilidade da rede, que se recuperará, pois o algoritmo faz
com que estes “buracos” sejam supridos por outros nós.
Uma vez que não há uma instância central para armazenar os ı́ndices dos arquivos
existentes, essa tarefa é dividida de maneira igual através de todas as entidades. O
nó que deseja compartilhar um arquivo efetua um processamento, aplicando um
hash no conteúdo do arquivo que, como resultado, vai identificá-lo dentro da rede
de compartilhamento. Por isso, o resultado dos hashes e os identificadores dos nós
devem possuir o mesmo tamanho, garantindo a compatibilidade entre os valores.
Para efetuar uma consulta, busca-se na rede o nó cujo identificador tem a menor
distância do hash do arquivo, com a utilização da lista de contatos que está armazenada no nó local. Os contatos armazenados na rede estão sob constante mudança
pois os nós conectam e desconectam aleatoriamente. A replicação das informações
a respeito da localização de nós faz com que o desempenho da busca aumente. Com
isso, quando um contato não está na lista local, o nó consulta o nó mais próximo
daquele que ele procura afim de recuperar o endereço final.
O Kademlia é utilizado pelo programa de compartilhamento Morpheus. Outros
protocolos possuem a mesma natureza, com variações em desempenho quando analisados sob a perspectiva do número de mensagens que requerem para uma busca,
2.5. Protocolos para sistemas de compartilhamento de arquivos
43
tempo de configuração de um novo nó, ou quantidade de dados que os nós devem
armazenar para efetuar o roteamento das mensagens.
A Tabela 2.2 mostra uma tabela que resume alguns protocolos utilizados por
aplicativos de compartilhamento de arquivos na Internet, mostrando a arquitetura
de rede peer-to-peer utilizada por cada um deles.
Tabela 2.2: Exemplos de protocolos de compartilhamento de arquivos e suas arquiteturas
Protocolo
Arquitetura
Gnutella
Descentralizada pura
Gnutella2
Baseada em ultranós
Freenet
Estruturada
Napster
Semi-centralizada não intermediada
Kademlia
Estruturada
eDonkey
Baseada em ultranós
FastTrack
Baseada em ultranós
BitTorrent Descentralizada pura
Chord
Estruturada
Pastry
Estruturada
A escolha de quais arquiteturas e protocolos devem ser utilizados depende das
caracterı́sticas do aplicativo e do ambiente em que será executado. Deve-se considerar todos os requisitos necessários de um aplicativo peer-to-peer, desde o seu
desenvolvimento, até a sua implantação, levando-se sempre em consideração o seu
desempenho para a sua especificidade.
Capı́tulo 3
Os protocolos da camada de
transporte
Os procedimentos utilizados para a implementação dos mecanismos que permitem
a troca de dados entre os elementos de uma rede de computadores são complexos.
Para reduzir esta complexidade, tarefas são divididas em camadas; cada camada executa um subconjunto de funções, utilizando serviços da camada inferior, e provendo
serviços à camada superior.
Os protocolos implementam os serviços que uma determinada camada oferece,
definindo os tipos e a sintaxe das várias mensagens, a semântica de seus campos e
as regras que determinam quando essas mensagens são enviadas e respondidas.
A camada de transporte é situada entre a camada de aplicação e a camada de
rede da arquitetura Internet, e tem como função principal oferecer uma “comunicação lógica” entre processos de aplicação em diferentes hospedeiros. Isso significa
que, embora os processos não estejam fisicamente conectados, do ponto de vista da
aplicação, é como se o estivessem. A Figura 3.1 ilustra essa “comunicação lógica”,
destacando as pilhas de protocolos utilizadas em uma comunicação entre dois processos, em dois hosts diferentes.
Os protocolos da camada de transporte mais utilizados na Internet são o UDP
(User Datagram Protocol ) e o TCP [45]. Um outro procolo da camada de transporte
é o SCTP. Este capı́tulo tem como objetivo mostrar as caracterı́sticas e os serviços
oferecidos por esses protocolos, e efetuar uma comparação entre eles.
44
3.1. O protocolo UDP
45
Figura 3.1: Comunicação lógica entre dois processos, em dois hosts diferentes
3.1
O protocolo UDP
O UDP [32] é um protocolo de transporte simples, leve, com um modelo de serviço
que apresenta as funções mı́nimas da camada de transporte: multiplexação, demultiplexação e verificação de erros. Os dados da camada de aplicação, quando
empacotados pelo UDP, são referenciados por datagrama. O seu cabeçalho é como
mostra a Figura 3.2.
Figura 3.2: Cabeçalho do protocolo UDP
Os campos “porta de origem” e “porta de destino”, de 16 bits cada um, identificam os processos nos sistemas finais que enviam e recebem os datagramas. Uma vez
que o UDP é um protocolo que não armazena estados, a “porta de origem” pode
ser opcional, pois um computador que envia dados pode não solicitar respostas. Se
3.2. O protocolo TCP
46
não utilizado, o valor deste campo deve ser configurado com o valor zero.
O campo “tamanho” designa o comprimento do datagrama em bytes, incluindo
ambos o cabeçalho e o campo de dados. O tamanho mı́nimo de um datagrama
é oito bytes, sendo, neste caso, o campo de dados vazio. O campo “checksum”
carrega a soma de verificação utilizada na detecção de erros no cabeçalho e nos
dados transmitidos.
É um protocolo orientado à mensagem, não orientado à conexão e não confiável,
isto é, não garante que os pacotes sejam entregues ao destinatário e nem que cheguem
de forma ordenada. No entanto, se o pacote chegar ao destinatário, o checksum
permite a verificação da existência de erros.
As aplicações construı́das sobre o UDP devem tratar perdas, erros, duplicação
e desordenação na entrega de datagramas. No entanto, muitas destas aplicações se
adaptam melhor ao UDP pois:
• Não há estabelecimento de conexão;
• Não há armazenamento de estados de conexão;
• Possui pouco overhead no cabeçalho do pacote;
• Possui taxa de envio não regulada.
O UDP não necessita de estabelecimento de conexão, pois nenhuma variável
necessita ser configurada no remetente, ou no destinatário. Assim sendo, é um
protocolo que não armazena estado de conexão, ou seja, não necessita de variáveis
para armazenar detalhes sobre a troca de dados entre sistemas finais.
O seu cabeçalho é pequeno, sendo considerado lightweigth, ou seja, adiciona
pouco overhead em relação ao campo de dados. A sua taxa de envio não é regulada, não havendo mecanismos para a detecção de transbordamento de buffer do
destinatário ou de congestionamento na rede.
3.2
O protocolo TCP
O TCP oferece outros serviços além da multiplexação, demultiplexação e verificação
de erros que o UDP oferece. Esse protocolo oferece o serviço de entrega de dados
3.2. O protocolo TCP
47
confiável e ordenado. É um protocolo orientado à conexão e a bytes, e ainda implementa o controle de fluxo e de congestionamento. Os dados da camada de aplicação
empacotados com o cabeçalho do TCP é denominado segmento. O seu cabeçalho
pode ser representado pela Figura 3.3.
Figura 3.3: Cabeçalho do protocolo TCP
Os campos “porta de origem” e “porta de destino”, assim como no UDP, determinam a quais processos encaminhar os dados nos sistemas finais. Os “número de
seqüência” e “número de reconhecimento” são utilizados para a implementação do
serviço confiável de transferência de dados.
O campo “tamanho do cabeçalho” designa o tamanho do cabeçalho do pacote
em palavras de 32 bits. O campo “flags” contém 6 bits, que são utilizados para
identificar inı́cio e fim de conexão, assim como entrega de dados urgentes, entre
outras finalidades. O campo “janela” é utilizado para o controle de fluxo.
O campo “checksum”, como no protocolo UDP, é utilizado para a verificação de
erros no cabeçalho e nos dados do usuário, e o campo “ponteiro urgente” auxilia na
entrega de dados urgentes à camada de aplicação. O campo “opções” é facultativo,
e dentre outras funções, pode ser utilizado para definir o MSS (Maximum Segment
Size) entre os sistemas finais.
Para efetuar uma conexão, o TCP implementa o chamado 3-way handshake. Este
mecanismo executa a troca de três mensagens entre os hospedeiros, que faz com que
buffers sejam criados para armazenar dados durante o tempo de vida da conexão.
3.2. O protocolo TCP
48
São definidos, nesta fase, os número de seqüência iniciais, sendo J o do cliente, e K
o do servidor, como mostra a Figura 3.4.
Figura 3.4: Iniciação de conexão TCP
O cliente efetua um pedido de conexão (active open) enviando um segmento
TCP do tipo SYN, e define o número de seqüência inicial para os dados que enviar.
Normalmente não há dados do usuário com este segmento; somente o cabeçalho IP e
TCP, e possı́veis opções do TCP. O servidor responde então com um ACK, enviando
também seu número de seqüência inicial. O cliente, então, responde com um SYN,
e a conexão está estabelecida.
Durante o tempo de vida de uma conexão TCP, o protocolo que roda em cada
um dos hospedeiros faz transições por vários estados TCP. A Figura 3.5 mostra estes
vários estados, desde o estabelecimento até o encerramento da conexão1 .
Há 11 diferentes estados para uma conexão TCP, e as regras do protocolo ditam como as transições acontecem. Por exemplo, se uma aplicação está no estado
CLOSED e decide conectar-se a um servidor (active open), o TCP envia um SYN e
passa para o estado SYN SENT. Se recebe em seguida um SYN conjugado com um
ACK, envia um ACK, e o novo estado passa a ser ESTABLISHED. É neste estado
que a maioria das transferência dos dados ocorrem.
Enquanto o TCP troca três mensagens para estabelecer uma conexão, ele troca
quatro para encerrá-la, como mostra a Figura 3.6. O lado que recebe um FIN efetua
1
Na figura apresentada, duas transições são omitidas: iniciação simultânea, em que SYNs percorrem a
rede em sentidos opostos ao mesmo tempo, e encerramento simultâneo de conexão, em que FINs percorrem
a rede em sentidos opostos ao mesmo tempo.
3.2. O protocolo TCP
49
Figura 3.5: Estados de uma conexão TCP
o encerramento passivo (passive close), sendo, então, enviado um reconhecimento
ao FIN. O recebimento de um FIN quer dizer que dados não serão mais recebidos
através da conexão. Algum tempo depois, a aplicação que recebeu o FIN também
decide encerrar a sua conexão. Isso faz com que o TCP envie um FIN ao outro par,
que envia o respectivo reconhecimento.
O TCP vê os dados como uma cadeia de bytes desestruturada, mas ordenada.
O uso que o TCP faz dos número de seqüência reflete essa visão, pelo fato de que
esses números são aplicados sobre a cadeia de bytes transmitidos, e não sobre a série
de segmentos. Do ponto de vista da aplicação, um fluxo de bytes é “empurrado”
através do socket, no qual uma determinada quantidade de bytes de dados vai ser
enviada ao destinatário.
O controle de fluxo é o mecanismo que os sistemas finais utilizam para evitar que
3.2. O protocolo TCP
50
Figura 3.6: Encerramento de conexão TCP
os dados enviados transbordem a capacidade do buffer de seus destinatários. Para
isso, o campo do cabeçalho “janela” é utilizado. A janela de recepção é utilizada
para dar ao remetente uma idéia de quanto espaço livre de buffer está disponı́vel no
destinatário.
Na conexão do TCP, que é full-duplex, cada remetente mantém uma janela de
recepção distinta, que varia dinamicamente durante o tempo de vida útil da conexão. A cada segmento que cada remetente envia ao outro, o campo “janela” é
atualizado, informando o outro par sobre quantos bytes estão disponı́veis a receber,
sem encontrar problemas na utilização de seu buffer.
O controle de congestionamento, diferentemente do controle de fluxo, visa ao
bem-estar geral da rede, e não somente ao do destinatário. O congestionamento
é causado, principalmente, pelo estouro na capacidade dos buffers dos roteadores da rede, em que demasiadas fontes estão tentando enviar dados a uma taxa
muito alta. O TCP utiliza para o controle de congestionamento um algoritmo chamado “aumento-aditivo-diminuição-multiplicativa”, ou AIMD (Additive-increasemultiplicative-decrease) [20].
O TCP é utilizado quando a aplicação necessita de confiabilidade sobre o serviço
não confiável da rede IP. No entanto, em alguns casos, o TCP não provê as exatas
funcionalidades necessárias à aplicação, ou provê mais que o necessário. No primeiro
caso a aplicação necessita fazer um trabalho extra para utilizar o TCP, enquanto no
último, a funcionalidade extra do TCP pode ser um entrave [33].
3.3. O protocolo SCTP
3.3
3.3.1
51
O protocolo SCTP
Histórico do SCTP
O desenvolvimento do SCTP foi motivado pela necessidade de sanar as deficiências
do TCP e do UDP ao lidar com algumas aplicações, em especial aquelas de telefonia sobre a rede IP. Em princı́pio, seu desenvolvimento visava a um protocolo que
operava sobre o UDP, oferecendo confiabilidade sobre um protocolo da camada de
transporte não confiável [41].
Muitas implementações foram feitas até que se chegasse ao precursor do SCTP,
chamado MDTP (Multi-Network Datagram Transmission Protocol ). Assim que a
primeira implementação utilizável foi concluı́da, seus autores a submeteram para a
IETF (Internet Engineering Task Force).
Uma iniciativa da IETF relativa à telefonia sobre IP estava sendo desenvolvida
paralelamente a essa submissão pelo grupo SIGTRAN (Signaling Transport Working
Group). Ao analisar o protocolo MDTP, o SIGTRAN considerou seus conceitos
interessantes e propôs várias modificações no protocolo, a fim de melhorar e refinar
seu desenvolvimento original.
Durante esse refinamento, o nome do protocolo mudou de MDTP para SCTP,
como sinal de expansão de escopo e de funcionalidade do protocolo. Essa expansão
permitiu que o SCTP passasse a ser considerado um protocolo da camada de transporte, ao invés de um protocolo atuando sobre o UDP, sendo assim utilizado diretamente sobre a camada de rede.
3.3.2
Caracterı́sticas do SCTP
O SCTP é um protocolo que, como o TCP e o UDP, pertence à camada de transporte
da pilha de protocolos da arquitetura TCP/IP [30, 41]. É um protocolo confiável,
orientado à conexão e à mensagem. Implementa o controle de fluxo e de congestionamento, e pode ser usado concorrentemente com o TCP, sem falhas na justiça de
utilização do canal de dados. Apresenta funcionalidades adicionais em relação ao
TCP e ao UDP, tais como o multi-homing, os multi-fluxos e entrega desordenada2
2
O termo “desordenada” aqui proposto, e doravante usado no decorrer do texto, está relacionado com
mensagens do SCTP, e refere-se ao tratamento de entrega das mensagens sem considerar a ordem ou
3.3. O protocolo SCTP
52
das mensagens, que torna este protocolo mais leve e, portanto, mais adequado para
determinados tipos de aplicações.
Os dados da camada de aplicação empacotados com o cabeçalho do protocolo
SCTP são denominados simplesmente pacotes. Os campos desse cabeçalho são
mostrados na Figura 3.7. Nas redes IP, esses pacotes são encapsulados no payload
do pacote IP. Um pacote SCTP é formado por um cabeçalho comum e chunks.
Múltiplos chunks podem ser multiplexados em um só pacote, até o limite definido
pelo MTU (Maximum Transfer Unit). Um chunk pode conter dados de controle ou
dados do usuário.
Figura 3.7: Formato do pacote SCTP
O cabeçalho comum consiste de 12 bytes. Para a multiplexação e demultiplexação
o SCTP utiliza os mesmos mecanismos do TCP e UDP, a partir dos campos “porta
de origem” e “porta de destino”. Para a detecção de erros de transmissão, cada
pacote SCTP é protegido com um valor de soma de verificação de 32 bits (Adler-32
algorithm).
O cabeçalho comum possui ainda um campo chamado “Verification tag”. Esse
campo é especı́fico para cada associação SCTP3 , e eles são trocados entre os sistemas
seqüência.
3
No SCTP o relacionamento entre os pares é denominado associação, em vez de conexão, como no TCP,
3.3. O protocolo SCTP
53
finais no momento do estabelecimento da associação.
Cada chunk possui um campo de “tipo”, para diferenciar chunk de dados e os
vários chunks de controle, seguidos de campos de flags, e de comprimento, este
último utilizado para definir o tamanho do chunk.
O chunk de dados deve apresentar um formato especı́fico, como mostra a Figura
3.8. O campo “tipo” deve conter o valor “0x00”, referente ao chunk de dados de
usuário. Os flags U, B e E indicam respectivamente, quando configurados com o
valor 1, chunk a ser entregue sem ordenação, chunk de inı́cio de mensagem e chunk
de fim de mensagem.
Figura 3.8: Chunk de dados do SCTP
O campo TSN (Transmission Sequence Number ) é utilizado pelo remetente e
pelo destinatário para identificação dos chunks enviados e recebidos, como auxiliar
para retransmissões e detecção de duplicação, assim como para a fragmentação e
remontagem de mensagens.
O campo “identificador de fluxo” indica para qual fluxo o dado do usuário é
destinado. O campo “número de seqüência de fluxo”, associado ao “identificador de
fluxo”, identifica a ordem de entrega da mensagem dentro de um fluxo especı́fico.
O campo “identificador de payload ” é ignorado pelo SCTP. No entanto, o usuário
pode querer usá-lo para determinar qual tipo de dado está sendo carregado dentro
da mensagem. Este valor pode ser utilizado também por monitores de rede e filtros
de pacotes.
pois pode utilizar um número arbitrário de vias de comunicação, denominadas fluxos, como explicado
adiante neste capı́tulo.
3.3. O protocolo SCTP
54
Outros tipos de chunks são definidos para controle da associação, tais como
chunks para inicialização e encerramento de associação e reconhecimento seletivo, e
podem ser identificados ao se variar o valor do campo “tipo” do cabeçalho do chunk.
Alguns deles são apresentados na Tabela 3.1.
Tabela 3.1:
Tipo do Chunk
DATA
INIT
INIT-ACK
COOKIE-ECHO
COOKIE-ACK
SACK
HEARTBEAT
HEARTBEAT-ACK
ABORT
ERROR
SHUTDOWN
SHUTDOWN-ACK
SHUTDOWN-COMPLETE
Os chunks do SCTP
Valor Utilização
0x00 Dados do usuário
0x01 Iniciação de associação
0x02 Iniciação de associação
0x0a Iniciação de associação
0x0b Iniciação de associação
0x03 Reconhecimento seletivo
0x04 Keep-alive
0x05 Keep-alive
0x06 Encerramento de associação
0x09 Relatório de erros
0x07 Encerramento de associação
0x08 Encerramento de associação
0x0c Encerramento de associação
O estabelecimento de associação do SCTP4 é similar ao estabelecimento de conexão do TCP, exceto pela utilização de quatro mensagens (4-way handshake) e pela
utilização de um cookie. O INIT carrega, junto com outros parâmetros, um tag de
verificação, Ta, e um número de seqüência inicial, J. O tag Ta deve estar presente
em todos os pacotes enviados pelo par durante o tempo de vida da associação. O
outro par também escolhe um tag de verificação, Tz, que deve estar presente em
todos os seus pacotes enviados.
Junto a este tag e ao número de seqüência inicial K, o receptor do INIT também
envia um cookie C. Este cookie contém todos os estados necessários para se configurar
uma associação SCTP, para que o servidor não precise armazenar informações sobre
o cliente que está efetuando a associação. A parte passiva da associação não aloca
recursos para a associação até que a terceira mensagem tenha chegado, e tenha sido
validada. Esse mecanismo visa a solucionar os problemas de ataques DoS. A Figura
3.9 representa a iniciação de uma associação SCTP.
O SCTP é um protocolo que implementa a noção de estados. As transições de
4
Este estágio pode, também, ser referenciado por estabelecimento de conexão
3.3. O protocolo SCTP
55
Figura 3.9: Iniciação de uma associação SCTP
um estado a outro são ditadas pelas regras do protocolo, baseados no estado atual
e nos chunks recebidos. Por exemplo, se uma aplicação está no estado CLOSED e
decide efetuar uma conexão (active open), ela envia uma mensagem INIT ao lado
servidor e passa para o estado COOKIE-WAIT. Se, a partir daı́, receber um INITACK, envia um COOKIE-ECHO e seu novo estado é COOKIE-ECHOED. Se recebe
um COOKIE-ACK, passa para o estado ESTABLISHED, estado no qual a maioria
das transmissões de dados são efetuadas. Esses estados são apresentados na Figura
3.10.
O SCTP não apresenta os estados half-closed e TIME WAIT do TCP, este último
devido à utilização do tag de verificação. Assim sendo, para o encerramento de
associação são trocadas somente três mensagens, como mostra a Figura 3.11.
Entre os vários serviços de transporte oferecidos pelo SCTP, de suma importância
é a provisão de múltiplos fluxos dentro uma associação. Os mecanismos de confiabilidade na transmissão de dados, tais como a detecção de perda de dados e as
retransmissões, é separado do mecanismo de ordenação de mensagens, este último
implementado com o auxı́lio da funcionalidade de multi-fluxos.
Essa funcionalidade permite que dois sistemas finais transmitam duas ou mais
seqüências separadas de mensagens, sem introduzir dependência entre elas. Cada
seqüência, ou fluxo de mensagens, será ordenada, mas as mensagens em diferentes
fluxos não interferem na ordem de entrega umas das outras.
O SCTP opera, portanto, em dois nı́veis. O primeiro nı́vel é considerado extrafluxo e o segundo nı́vel é considerado intra-fluxo. Dentro de uma associação, a
3.3. O protocolo SCTP
56
Figura 3.10: Estados de uma associação SCTP
transmissão confiável de pacotes é atingida pela utilização da soma de verificação,
os número de seqüência, os temporizadores e o mecanismo de retransmissão seletiva.
Cada chunk corretamente recebido é encaminhado ao segundo nı́vel de operação
do protocolo. O segundo nı́vel é responsável por realizar a ordenação parcial dos
datagramas. Isto é, a ordem é mantida entre cada fluxo, mas não entre diferentes
fluxos. O número de fluxos em cada sentido é configurado na iniciação da associação.
Uma outra funcionalidade que o SCTP oferece é o suporte a nós multi-endereçados
(multi-homing). Com esta funcionalidade, os nós configurados com diferentes interfaces de rede podem ser alcançados através de diferentes endereços IP, com a
utilização de uma única associação. Assim sendo, os dados podem trafegar através
3.3. O protocolo SCTP
57
Figura 3.11: Encerramento de conexão SCTP
de diferentes caminhos fı́sicos, podendo aumentar o desempenho e a tolerância a
falhas.
As informações a respeito dos múltiplos endereços são trocadas no momento do
estabelecimento da associação. Um dos endereços é selecionado como o caminho
primário, sobre os quais os pacotes vão ser transmitidos predefinidamente. No entanto, retransmissões podem ser feitas por um dos caminhos disponı́veis. Se um dos
caminhos falha, o outro pode ser utilizado para efetuar a transferência dos dados.
A funcionalidade do multi-homing pode ser representada pela Figura 3.12, que
ilustra dois computadores se comunicando através de diferentes meios fı́sicos, em
que cada interface de rede é atingida por um diferente endereço IP.
Figura 3.12: Caracterı́stica de multi-homing do SCTP
O SCTP utiliza um mecanismo de controle de fluxo baseado em janela de buffer,
similar àquele utilizado pelo TCP. O receptor especifica uma janela de recepção, e
retorna o seu tamanho corrente através dos chunks do tipo SACK.
O controle de congestionamento do SCTP derivou do controle de congestionamento do TCP, com algumas modificações feitas para o multi-homing. Para cada
3.4. A comparação entre o UDP, o TCP e o SCTP
58
endereço de destino, um conjunto de parâmetros para controle de fluxo e de congestionamento é mantido. Desta maneira, uma associação SCTP com mais de um
caminho se comporta de maneira similar a um mesmo número de conexões TCP.
O mecanismo de controle de congestionamento similar ao do TCP assegura que
o SCTP pode ser introduzido sem problemas na rede onde o TCP é amplamente
utilizado.
3.4
A comparação entre o UDP, o TCP e o SCTP
Para efetuar a comparação entre os protocolos UDP, TCP e SCTP, primeiramente
são levantadas as limitações do UDP e do TCP. Em seguida, são apresentadas as
semelhanças e as diferenças especı́ficas entre os protocolos TCP e SCTP, que são
dois protocolos orientados à conexão.
3.4.1
Limitações do UDP
Como descrito anteriormente, o UDP é um protocolo simples que oferece os serviços
mı́nimos de um protocolo da camada de transporte. A partir disto, algumas das
limitações do UDP podem ser levantadas [42]:
• O UDP não oferece o serviço de transferência confiável e ordenada de dados.
Por isso, a aplicação que envia os dados sobre UDP não pode ter certeza se o
nó de destino recebeu os seus datagramas, e também não sabe a ordem exata
em que foram entregues;
• O UDP não implementa mecanismos de controle de fluxo. Com isso, uma
aplicação pode transbordar os buffers de recebimento do nó destinatário;
• O UDP não implementa o controle de congestionamento. Os dados enviados
por uma rede congestionada, sem o controle de congestionamento, afetam os
dados enviados por outras aplicações, sendo descartados com maior probabilidade.
Com essas limitações, o UDP não pode encontrar o nı́vel de confiabilidade necessária a algumas aplicações. No entanto, pelo fato de o UDP ser orientado à
mensagem, e ser considerado, em geral, um protocolo que introduz pouco overhead,
3.4. A comparação entre o UDP, o TCP e o SCTP
59
houve várias tentativas de introduzir na camada de aplicação os serviços não oferecidos pela camada de transporte. Neste caso a aplicação precisa implementar seus
próprios mecanismos para oferecer os seguintes serviços:
• Detecção e retransmissão de mensagens perdidas;
• Correção de mensagens fora de ordem e duplicadas;
• Detecção e soluções para o congestionamento de redes.
A implementação desses serviços na camada de aplicação pode não ser a melhor
solução, pois são mecanismos com alto grau de complexidade de desenvolvimento.
Além disso, os serviços citados são normalmente oferecidos por outros protocolos da
camada de transporte, e desenvolvê-los na camada de aplicação pode ser considerado
um trabalho redundante.
3.4.2
Limitações do protocolo TCP
Como descrito anteriormente, o TCP é um protocolo que oferece confiabilidade, é
orientado à conexão, e oferece a entrega ordenada dos dados. No entanto, pode
apresentar algumas limitações ao ser utilizado em determinados tipos de aplicações
[42]:
• O TCP oferece ordenação estrita na entrega dos dados. Alguns aplicativos
não necessitam deste serviço, pois permitem a ordenação parcial na entrega
de informações, e a ordenação estrita poderia, neste caso, acrescentar atrasos
indesejáveis. A perda de um segmento TCP pode bloquear a entrega de todos
de segmentos subseqüentes já disponı́veis no destinatário, até que o segmento
perdido seja retransmitido e entregue. Este fenômeno é chamado de headof-line blocking, ou HOL, e será discutido em maiores detalhes no próximo
capı́tulo;
• Muitas aplicações necessitam de entrega confiável de “mensagens”, mas o TCP
é um protocolo que controla a sua transmissão de dados baseados em bytes.
Aplicações orientadas à mensagem alcançam essa funcionalidade delineando o
fluxo de bytes, ao adicionar seus próprios registros de limites;
3.4. A comparação entre o UDP, o TCP e o SCTP
60
• O TCP não possui suporte para hosts com múltiplas interfaces, os chamados,
multi-homed hosts. Isso traz uma imensa dificuldade a desenvolvedores que
desejam construir a redundância de enlace;
• O TCP é conhecido por ser relativamente vulnerável a alguns ataques DoS,
tais como SYN flooding.
Com essas limitações, o TCP pode ser um protocolo pouco adequado para determinados tipos de aplicações, tais como aquelas que necessitam da orientação às
mensagens, e que não precisam da ordem estrita na entrega dos dados.
3.4.3
SCTP versus TCP versus UDP
O SCTP foi desenvolvido para sanar as limitações do TCP e UDP. Seu desenvolvimento foi motivado diretamente pelo transporte de mensagens da PSNT (Public
Switched Telephone Network ) sobre a rede IP. No entanto, aplicações que necessitam de funcionalidades iguais ou semelhantes às da PSNT podem utilizar este
protocolo [6]. A Tabela 3.2 mostra um resumo das caracterı́sticas e serviços do
SCTP, do TCP e do UDP [42], já citadas no decorrer deste capı́tulo.
Tabela 3.2: Comparação entre o SCTP, o TCP
Caracterı́stica do protocolo
SCTP
Estados armazenados nos sistemas finais
sim
Transferência confiável de dados
sim
Controle de congestionamento
sim
Delimitação de limites das mensagens
sim
Descobrimento do MTU do caminho
sim
Fragmentação e remontagem de informação
sim
Multiplexação de informação no pacote
sim
Suporte a multi-homing
sim
sim
Entrega desordenada de dados
Segurança contra SYN flooding
sim
Heartbeat
sim
e o UDP
TCP UDP
sim
não
sim
não
sim
não
não
sim
sim
não
sim
não
sim
não
não
não
não
sim
não
não
não
não
A partir da Tabela 3.2 observa-se que o TCP é mais parecido com o SCTP do
que o UDP. Como ambos os protocolos TCP e SCTP são orientados à conexão e
apresentam propósitos parecidos, há algumas semelhanças e diferenças entre eles,
como descritas nas seções a seguir.
3.4. A comparação entre o UDP, o TCP e o SCTP
3.4.4
61
As similaridades entre o SCTP e o TCP
• Iniciação: Ambos os protocolos passam por uma fase de trocas de mensagens
iniciais para configurar o relacionamento fim-a-fim. Há diferenças nos formatos
dessas mensagens e na maneira pela qual são trocadas. No entanto, essa fase
possui o mesmo propósito em ambos os protocolos, que é o de estabelecer uma
conexão, ou associação;
• Confiabilidade: O SCTP e o TCP oferecem mecanismos para o envio de dados
de maneira confiável sobre a rede IP, que não é orientada à conexão e não
oferece confiabilidade;
• Ordenação: Ambos os protocolos oferecem o serviço de entrega ordenada de
mensagens. Algumas aplicações necessitam de ter certeza de que suas mensagens estão sendo entregues na ordem correta, tal como uma aplicação que
efetua funções de inclusão de dados em uma base remota, e em seguida deleta estes dados. Resultados inesperados podem ocorrer caso as mensagens
cheguem em ordem não prevista.
• Controle de congestionamento: Outra importante similaridade é a provisão
de controle de congestionamento. Ambos o SCTP e o TCP oferecem o mesmo
mecanismo de controle de congestionamento, baseado no AIMD. Sem esse controle, a Internet poderia ceder seus serviços pelo excesso de tráfego imposto
pelos sistemas finais. Uma vez que os dois protocolos utilizam o mesmo algoritmo de congestionamento, há a garantia de que haverá justiça na utilização
da largura de banda quando eles forem utilizados simultaneamente sobre a
mesma rede;
• Encerramento: Ambos os protocolos oferecem duas maneiras de encerrar seu
relacionamento, denominadas “maneira elegante” e “maneira não elegante”.
A maneira elegante pode ser utilizada para assegurar ao usuário que todos
os dados na fila de envio, no momento do encerramento, vão ser entregues
confiavelmente ao nó destinatário, antes que o relacionamento fim-a-fim seja
terminado. A maneira não elegante, por sua vez, pode acontecer em casos de
encerramentos inesperados, onde dados podem ser perdidos, por não haver o
esvaziamento das filas de envio.
3.4. A comparação entre o UDP, o TCP e o SCTP
3.4.5
62
As diferenças entre o SCTP e o TCP
• Diferenças na inicialização: Como mencionado, o TCP e o SCTP trocam mensagens de configuração antes de iniciar sua comunicação fim a fim. No entanto,
o SCTP diferencia-se do TCP por utilizar quatro vias, ou mensagens, em contraste com a iniciação de três vias do TCP. A justificativa para a utilização de
quatro vias na iniciação é a proteção contra ataques de SYN flooding;
• Head-of-line blocking: Há uma maneira diferenciada pela qual o SCTP permite
que dois sistemas finais se comuniquem, o que permite que o HOL seja evitado.
Uma conexão TCP é uma via full-duplex, análoga a dois canos que enviam
água, um no sentido oposto ao do outro. Esse relacionamento pode ser melhor
exemplificado pela Figura 3.13.
Figura 3.13: A conexão TCP
Uma associação SCTP é composta de um número arbitrário de vias entre os
dois pares, análogos a vários canos que enviam água, uns para um sentido, uns
para o outro. Esse número arbitrário é configurado no momento da iniciação do
relacionamento entre os sistemas finais. A associação SCTP pode ser melhor
exemplificada pela Figura 3.14.
Figura 3.14: A associação SCTP
Este número arbitrário de vias caracteriza a funcionalidade dos multi-fluxos,
que permite que as mensagens enviadas dentro de um mesmo fluxo vão ter
sua ordem preservada. No entanto, mensagens enviadas em diferentes fluxos
vão ser entregues à camada superior sem interferência da ordem de chegada
das mensagens atribuı́das a outros fluxos. Por outro lado, a desordenação na
entrega das mensagens é alcançada pela configuração de um flag do chunk que
3.4. A comparação entre o UDP, o TCP e o SCTP
63
indica que a ordem de entrega à camada superior deve ser feita sem qualquer
restrição, independentemente do fluxo através do qual foi enviada.
• Limites das mensagens: Para o TCP, os dados transportados entre dois sistemas finais através de uma conexão são considerados um fluxo5 ou seqüência
de bytes. No SCTP, os limites dos dados são preservados, implementando a
noção de mensagens, o que pode trazer benefı́cios para aplicações que necessitam delimitar seus dados para identificar as mensagens dentro de um fluxo de
bytes.
• Reconhecimento seletivo: Uma outra diferença importante entre o SCTP e o
TCP é a utilização de reconhecimento seletivo, que permite que o receptor
reporte a sua exata condição ao remetente sobre todos os pedaços de dados
que ainda não chegaram no destino. Para o TCP, isto é uma opção, e pode
estar presente em algumas implementações [23].
• Multi-homing: Uma caracterı́sticas importante do SCTP é a utilização de
multi-homing. O multi-homing pode ser utilizado quando mais de um endereço IP é associado a um host. Isto geralmente está relacionado com hosts
que possuem múltiplas interfaces de rede.
• Encerramento: Ambos os protocolos permitem o encerramento “elegante” e
“não elegante” do relacionamento. No entanto, o SCTP não permite o estado
half-closed como no TCP, no qual um dos lados da conexão permanece aberto
enquanto o outro se fecha.
A partir do levantamento das caracterı́sticas desses protocolos, percebe-se que o
SCTP provê algumas vantagens sobre o TCP, tendo em destaque o provimento de
múltiplos fluxos e a entrega desordenada de mensagens dentro de uma associação,
que pode ser utilizado para evitar o problema do HOL, normalmente causado pelo
TCP.
5
A noção de fluxo citada não deve ser confundida com a noção de fluxos empregada pelo SCTP
Capı́tulo 4
A utilização do protocolo SCTP
por aplicativos de
compartilhamento de arquivos em
peer-to-peer
As redes peer-to-peer permitem que computadores, ou nós, interconectem-se de
forma distribuı́da para compartilhar arquivos. Como apresentado no Capı́tulo 2,
essas redes podem utilizar diferentes arquiteturas, influenciando a maneira pela qual
os nós se organizam e se relacionam, assim como as suas funções na rede. Nas arquiteturas descentralizadas, a troca de mensagens entre os nós é necessária para o
funcionamento do sistema.
Os aplicativos peer-to-peer utilizados para o compartilhamento de arquivos usam
a troca de mensagens para a localização ou consulta dos arquivos na rede peer-topeer, e também com outros objetivos, tais como descobrir os nós nessa rede virtual.
Esses aplicativos implementam três fases para o seu funcionamento: a fase de estabelecimento de conexões, a fase de consulta e a fase de transferência de arquivos.
Estas fases podem sobrepor-se; por exemplo, enquanto um nó estiver efetuando a
transferência de um arquivo, pode, ao mesmo tempo, estar efetuando outra consulta
e, ainda, tentando estabelecer conexões com mais nós.
Na fase de estabelecimento de conexões, o nó que deseja entrar na rede peerto-peer, ou nó entrante, deve encontrar os outros nós já considerados participantes.
64
Capı́tulo 4. A utilização do protocolo SCTP por aplicativos de
compartilhamento de arquivos em peer-to-peer
65
Assim que um computador efetiva uma conexão com um desses nós, ele se torna,
então, um nó participante da rede, podendo também efetivar outras conexões com
outros nós.
Na fase de consulta, o nó participante envia mensagens a outros nós, a fim de
localizar arquivos na rede virtual. Esta fase de consulta consiste na propagação
das mensagens através dos nós e no processo de backtrack, em que as respostas são
geradas de acordo com uma métrica de similaridade entre a consulta e os arquivos
compartilhados, sendo retornadas aos nós que executaram a consulta.
A fase de transferência de arquivos ocorre logo que o nó solicitante recebe as
respostas, que contêm não só os dados sobre arquivo, tais como tamanho e nome,
mas também os dados sobre suas localizações na rede virtual. O nó solicitante, de
posse desses dados, conecta-se diretamente em outro nó, para efetuar a transferência
dos arquivos que procura. A Figura 4.1 mostra essas três fases.
Figura 4.1: As fases de funcionamento de um aplicativo peer-to-peer para compartilhamento de arquivos
Na fase de conexão, como mostrado na Figura 4.1 (a), o nó entrante é representado pelo cı́rculo com preenchimento de cor branca, e as setas pretas e pontilhadas
representam o pedido de conexão que o nó entrante solicita aos nós participantes,
que são representados pelos cı́rculos com preenchimento de cor cinza. As conexões
já estabelecidas entre eles são representadas pelas linhas cinzas.
A Figura 4.1 (b) representa a fase de consulta. Nessa fase, o nó entrante já deve
ter estabelecido pelo menos uma conexão com um nó participante para poder fazer
sua consulta. Dessa vez, o nó que na Figura 4.1 (a) era considerado um nó entrante,
é, agora, representado como um nó participante, junto aos outros nós, pelos cı́rculos
Capı́tulo 4. A utilização do protocolo SCTP por aplicativos de
compartilhamento de arquivos em peer-to-peer
66
com preenchimento preto, e suas conexões pelas linhas pretas. As setas de cor cinza
que partem deste nó mostram a propagação de suas consultas para os diversos nós
da rede.
Uma vez identificado um nó na rede que pode satisfazer à consulta, uma mensagem de resposta é enviada para o nó solicitante, fazendo uso do processo de backtrack,
isto é, essa mensagem percorre, em ordem inversa, os mesmos nós usados para a localização do nó que satisfaz à consulta. Esse processo pode ser visualizado na Figura
4.1 (b) pelas setas cinzas que retornam ao nó solicitante.
Na fase de transferência de arquivos, como mostra a Figura 4.1 (c), o nó, de
posse de informações sobre a localização do arquivo na rede, entra em contato com
o nó servente que possui o arquivo. A seta preta representa o sentido para o qual
o arquivo é transferido. Nota-se que, a partir deste momento, a transferência do
arquivo se dará exclusivamente a partir do nó servente ao nó requisitante, neste
caso, o nó entrante. Assim sendo, essa fase ocorre “por fora” da rede overlay, pois a
conexão estabelecida para a transferência do arquivo não faz parte da topologia da
rede virtual, mesmo que os nós sejam vizinhos.
Um aplicativo peer-to-peer, portanto, envia mensagens entre os nós da rede virtual, as quais são propagadas de acordo com um algoritmo de roteamento. Por sua
vez, essas mensagens são caracterizadas por possuı́rem tamanhos variáveis e não
apresentarem nenhuma restrição na ordem em que são entregues ao nó de destino.
As mensagens em uma rede peer-to-peer descentralizada são comumente transferidas fazendo o uso de um protocolo orientado à conexão, pois, dessa maneira,
garante-se a confiabilidade na entrega das mensagens, assim como a regulagem na
inserção de dados na rede, a partir dos controles de fluxo e de congestionamento.
O TCP, o protocolo comumente utilizado para a transmissão dessas mensagens,
impõe uma restrição: que as mensagens sejam entregues de forma ordenada. Essa
restrição não é um requisito das transferências das mensagens, e isso faz com que o
desempenho dessa tarefa seja degradado pela ocorrência de atrasos indesejados na
entrega dos dados à camada de aplicação.
O SCTP é um outro protocolo da camada de transporte que oferece a confiabilidade e os controles de fluxo e de congestionamento, porém, a ordenação estrita é
um serviço opcional. Esse protocolo pode satisfazer aos requisitos de uma aplicação
4.1. O comportamento de uma rede Gnutella
67
peer-to-peer de maneira mais eficaz que o TCP, levando a um melhor desempenho
na transferência de mensagens entre os nós da rede overlay.
A proposta principal deste capı́tulo é mostrar a viabilidade de usar o SCTP
como protocolo da camada de transporte para essas redes overlay, sem a intenção
de generalizar. Assim, algumas caracterı́sticas problemáticas referentes ao uso das
redes peer-to-peer sobre o protocolo de transporte TCP, tal como o HOL, poderiam
ser evitadas com o uso do SCTP, como pode ser visto neste capı́tulo.
Uma análise comparativa entre os protocolos TCP e SCTP é apresentada por
meio de experimentos práticos, em que duas aplicações são desenvolvidas para simular a transferência de mensagens de modo idêntico a uma rede peer-to-peer em
atividade. Estes experimentos mostram o ganho do uso do SCTP sobre o TCP como
protocolo da camada de transporte para redes peer-to-peer.
Para executar os experimentos práticos, faz-se uso das caracterı́sticas de uma rede
que implementa o protocolo Gnutella. Para tal, uma descrição do comportamento
de uma rede Gnutella em atividade é apresentada, destacando as caracterı́sticas
relevantes à camada de transporte.
Para complementar o capı́tulo, é mostrado o desenvolvimento da aplicação Octopus, que utiliza o protocolo Gnutella sobre o protocolo SCTP. Apresentam-se os
aspectos de implementação e de uso das funcionalidades do SCTP, assim como os
as camadas que compõem este aplicativo e o seu funcionamento.
Neste trabalho, optou-se em usar o protocolo Gnutella, versão 0.4, devido à sua
simplicidade e por possuir uma documentação ampla, aberta e de fácil acesso, que
permite implementar e demonstrar o ganho de desempenho do uso do SCTP, em
vez do TCP, como protocolo da camada de transporte em aplicações peer-to-peer de
compartilhamento de arquivos.
4.1
O comportamento de uma rede Gnutella
O protocolo Gnutella utiliza a arquitetura descentralizada pura e cega e, como
descrito no Capı́tulo 2, apresenta baixa escalabilidade, pois envia suas mensagens de
consulta a todos os nós diretamente conectados. No entanto, possui caracterı́sticas
bem definidas referentes a um protocolo de rede peer-to-peer em termos das sintaxes
e das funcionalidade das mensagens, bem como da definição da arquitetura utilizada
4.1. O comportamento de uma rede Gnutella
68
e dos algoritmos de roteamento.
Esta seção tem como objetivo mostrar a dinâmica da transferência de mensagens
em uma rede Gnutella real para a fase de consulta. Não são consideradas, portanto,
as mensagens de tipo PING, PONG e PUSH. As caracterı́sticas relevantes à camada
de transporte são destacadas, tais como o volume e o tamanho das mensagens que
um nó transfere em um perı́odo de tempo. Uma discussão relativa ao tempo médio
da fase de consulta também se faz presente. Os valores apresentados são utilizados
como referência nos experimentos descritos neste capı́tulo.
4.1.1
O tráfego gerado na fase de consulta em uma rede Gnutella
Numa rede Gnutella, a largura de banda é consumida pela troca de mensagens,
as quais possuem tamanhos variados, e são geradas de maneira aleatória. A fase
de consulta é caracterizada por propagar um grande volume de mensagens na rede
virtual. A Figura 4.2 representa essa fase em dois momentos distintos: o momento
de propagação de mensagens QUERY, e o momento de propagação de mensagens
QUERYHIT.
Figura 4.2: A fase de consulta em uma rede Gnutella
Como mostra a Figura 4.2 (a), a mensagem QUERY, representada pela letra Q,
será enviada a todos os nós diretamente conectados, exceto àquele que a enviou,
sendo retransmitida iterativamente até que o número máximo de encaminhamentos
permitido seja atingido.
A Figura 4.2 (b) mostra o processo de backtrack, em que os nós que satisfazem à
4.1. O comportamento de uma rede Gnutella
69
consulta enviam a mensagem QUERYHIT, representada pelas letras R, que percorrem o caminho de volta ao nó que originou a consulta, através do mesmo caminho
que as mensagens QUERY percorreram.
A estimativa de quantos bytes de mensagens que um nó transfere em média num
decorrer de tempo é difı́cil de se calcular, pois as redes peer-to-peer são dinâmicas, e
seu consumo de largura de banda depende de vários fatores, tais como a freqüência
que os usuários efetuam consultas, as regras de descarte de pacotes, a topologia da
rede virtual, entre outras variáveis.
Há uma forma eficaz para efetuar essa estimativa, a partir da utilização de monitores de rede. Os monitores são aplicativos que armazenam dados estatı́sticos
sobre as mensagens que atravessam um nó. Para exemplificar, e mostrar a dinâmica
de uma rede Gnutella real, a seguir, é apresentado um experimento prático encontrado na literatura [22]. Esse exemplo fornece uma visão da ordem de grandeza de
transmissão de QUERYs e QUERYHITs cujos valores são usados nos experimentos
descritos neste capı́tulo.
As estimativas foram armazenadas por três monitores em três diferentes localidades geográficas mundiais durante o tempo de uma hora [22]. O número de mensagens
QUERY transferidas em intervalos de um segundo podem ser vistos na Figura 4.3.
Figura 4.3: Número de QUERYs por segundo em três diferentes monitores
A Figura 4.3 mostra que a quantidade de mensagens QUERY que um nó transfere
a cada segundo varia aleatoriamente nos três monitores, de maneira que não há uma
forma de se calcular a distribuição exata dessas mensagens num decorrer de tempo.
Perı́odos de transferência mais intensos são intercalados com perı́odos menos intensos
de forma não previsı́vel.
A Figura 4.4 mostra a quantidade de QUERYs transferida através do monitor
de número dois, em intervalos de tempo diferentes da Figura 4.3. Desta vez, são
4.1. O comportamento de uma rede Gnutella
70
utilizados intervalos de dez segundos, um minuto e cinco minutos, respectivamente.
Figura 4.4: Número de QUERYs em um monitor
Percebe-se pela Figura 4.4 que a quantidade de QUERYs que é transferida no
decorrer do tempo varia aleatoriamente, mesmo que seja vista em intervalos de
tempo maiores. A Tabela 4.1 apresenta a média final para o rastreamento de uma
hora efetuado pelos monitores.
Tabela 4.1: Número de QUERYs por segundo - média em uma hora
Monitor QUERYs/seg (média geral)
1
45,9
2
47,9
3
52,3
Como mostra a Tabela 4.1, os três monitores apresentam uma média similar,
mostrando que a localização geográfica não influencia a média de QUERYs que são
transferidas.
Assim que mensagens de consulta atravessam a rede e são processadas pelos
nós, mensagens QUERYHIT podem ser geradas e propagadas como respostas às
consultas. A quantidade de mensagens QUERYHIT registrada pelos monitores em
intervalos de um segundo é mostrada na Figura 4.5.
Figura 4.5: Número de QUERYHITs por segundo em três diferentes monitores
Na Figura 4.5, os três monitores registram que a quantidade de QUERYHITs
4.1. O comportamento de uma rede Gnutella
71
que são transferidas é aleatória nos no decorrer do tempo. Isso ocorre, pois a quantidade dessas mensagens que são geradas depende de muitas variáveis, tais como as
consultas que são efetuadas, isto é, as buscas por arquivos mais populares tendem
a gerar mais respostas do que as buscas por arquivos raros.
A Figura 4.6 mostra a quantidade de QUERYHITs transferida através do monitor
de número dois, em intervalos de tempo diferentes da Figura 4.5. Desta vez, são
utilizados intervalos de dez segundos, um minuto e cinco minutos, respectivamente.
Figura 4.6: Número de QUERYHITs em um monitor
Percebe-se, pela Figura 4.6, que a quantidade de QUERYHITs que é transmitida
no decorrer do tempo é aleatória, mesmo que seja vista em intervalos de tempo mais
longos. A Tabela 4.2 apresenta a média final para o rastreamento de uma hora
efetuado pelos monitores.
Tabela 4.2: Número de QUERYHITs por segundo - média em uma hora
Monitor QUERYHITs/seg (média geral)
1
32,2
2
44,2
3
26,9
As médias mostradas pela Tabela 4.2 mostram que, apesar de diferentes, apresentam resultados com altas taxas de transferência de QUERYHITs, não havendo
um padrão na quantidade de transferências dessas mensagens no tempo.
A partir do exemplo apresentado, pode-se ter uma idéia da taxa de transferência
de mensagens de consulta em uma rede Gnutella real. No entanto, a magnitude
exata depende de muitas variáveis, tais como o número de usuários na rede virtual,
a topologia da rede, a freqüência que os usuários efetuam consultas, a popularidade
das consultas, a porcentagem de nós que compartilham arquivos, a porcentagem de
nós que respondem a cada consulta, a utilização da rede em diferentes horários e
4.1. O comportamento de uma rede Gnutella
72
dias da semana, entre outras.
4.1.2
Os tamanhos das mensagens de uma rede Gnutella
As mensagens geradas por um nó servente em uma rede Gnutella podem apresentar
tamanhos variados. No caso da mensagem QUERY, o seu tamanho varia de acordo
com a extensão da string que o usuário utiliza para efetuar a consulta. Considera-se
uma consulta com a seguinte string: Iron Maiden The Fugitive. A Tabela 4.3 mostra
como é a mensagem QUERY resultante.
Tabela 4.3: Uma mensagem QUERY na rede Gnutella
Cabeçalho Gnutella
23 bytes
Velocidade Mı́nima
2 bytes
String para a consulta 24 bytes
NULL
1 byte
Dados opcionais
0 byte
Total
50 bytes
A partir da string de consulta considerada, a mensagem QUERY resultante possui 50 bytes. Dos campos de uma mensagem QUERY, o único que não possui
tamanho fixo é o que carrega a string de consulta do usuário e, uma vez que os tamanhos das consultas dos usuários são variáveis, não é possı́vel estimar com precisão
o tamanho médio das mensagens QUERY em uma rede peer-to-peer.
Para saber o tamanho médio de uma mensagem QUERYHIT, duas variáveis
devem ser consideradas [35]: r, como o número médio de respostas que um nó oferece
a uma dada consulta, e t, como o tamanho médio da string de cada resposta. A
mensagem QUERYHIT resultante é mostrada na Tabela 4.4.
Tabela 4.4: Uma mensagem QUERYHIT na rede Gnutella
Cabeçalho Gnutella
23 bytes
Número de hits
1 byte
Porta
2 bytes
Endereço IP
4 bytes
Velocidade
4 bytes
r *(8+t+2) bytes
Conjunto de respostas
Identificador de servente 16 bytes
Total
50 + r *(10+t) bytes
Não há uma forma precisa para estimar os valores de r e t, pois variam dinami-
4.1. O comportamento de uma rede Gnutella
73
camente dependendo da consulta efetuada. Como exemplo de cálculo do tamanho
médio de uma mensagem QUERYHIT, considera-se uma rede em que os nós que
podem satisfazer às consultas segundo as seguintes condições: r =1,5, e t=10 bytes.
O tamanho da mensagem QUERYHIT, neste caso, é de 80 bytes.
Os exemplos apresentados mostram a dinâmica de uma rede Gnutella a partir
das quantidades de mensagens da fase de consulta transferidas através de três nós,
representando um momento de uma rede Gnutella real. Além dessa exemplificação,
uma breve discussão sobre os tamanhos médios dessas mensagens foi apresentada.
Assim sendo, a partir dos dados mostrados nos exemplos, considera-se que a taxa
média de envio de QUERYs em uma rede Gnutella é em torno de 40 mensagens por
segundo, e a taxa média de QUERYHITs é em torno de 25 mensagens por segundo.
O tamanho médio de uma QUERY é em torno de 50 bytes, e o tamanho médio da
QUERYHIT é em torno de 80 bytes. Estes valores são utilizados como referência
para o aplicativo, apresentado neste capı́tulo, que simula a transmissão de mensagens
nó-a-nó em uma rede peer-to-peer, com o objetivo de fazer uma análise comparativa
entre o TCP e o SCTP como protocolos da camada de transporte para aplicativos
peer-to-peer.
4.1.3
Discussão a respeito do tempo médio da fase de consulta
Diferentes parâmetros podem ser considerados para avaliar a fase de consulta [48,51]:
• Número de respostas obtidas;
• Precisão e revocação dos resultados obtidos;
• Abrangência da consulta, calculada pelo número de nós visitados;
• Tempo médio de chegada de respostas às ordens de busca;
• Largura de banda consumida pela propagação de mensagens.
Cada um desses parâmetros pode ser melhorado de maneiras diversas. O número
de respostas obtidas pode, por exemplo, ser variado dependendo da métrica de similaridade utilizada entre os arquivos e as consultas. O número de nós visitados pode
ser aumentado em se adotando uma abordagem baseada em arquitetura hı́brida, na
4.1. O comportamento de uma rede Gnutella
74
qual os chamados ultranós armazenarão os dados dos arquivos compartilhados por
outros nós [39].
O parâmetro referente ao tempo médio de chegada de respostas às ordens de
busca depende do desempenho na transmissão de mensagens nó-a-nó. O tempo da
fase de consulta para uma única consulta pode ser medido a partir das variáveis
apresentadas na Figura 4.7.
Figura 4.7: Tempo de resposta a uma consulta
As variáveis Ti representam o tempo de transmissão nó-a-nó de uma mensagem
QUERY, onde i representa o i -ésimo salto da mensagem. As variáveis Bi representam o tempo de transmissão de uma mensagem QUERYHIT nó-a-nó de volta ao
nó que originou a consulta. O tempo para uma consulta atingir um nó que pode
satisfazer à consulta é calculado pela soma de todos os Ti .
De maneira análoga, o tempo da resposta voltar ao nó originário é calculado pela
soma de todos os Bi . O tempo da fase de consulta é dado pela soma de todos os
Ti e Bi . A média aritmética da soma de todos os Ti com os Bi reflete um fator
importante, que é o tempo médio que uma mensagem gasta para ser transmitida de
um nó a outro.
Um mecanismo para melhorar o desempenho da transmissão de mensagens nóa-nó pode trazer benefı́cios para o tempo médio da fase de consulta. As mensagens
transmitidas pelos aplicativos peer-to-peer caracterizam-se por possuir tamanhos variados e por não apresentarem restrição na ordem de entrega ao nó de destino. Fazer
uso do protocolo TCP para executar essa tarefa pode causar atrasos desnecessários
na entrega das mensagens à camada de aplicação, degradando o tempo médio de
chegada de respostas às ordens de busca. A utilização do SCTP pode melhorar o
4.2. O problema de HOL em redes peer-to-peer
75
desempenho deste parâmetro, como pode ser visto na próxima seção.
4.2
O problema de HOL em redes peer-to-peer
Muitas aplicações necessitam que seus dados sejam entregues de maneira estritamente ordenada à camada de aplicação. Outras, no entanto, não necessitam desta
caracterı́stica, permitindo a ordenação parcial, ou, ainda, a desordenação na entrega
dos dados. Os aplicativos peer-to-peer são um exemplo que se enquadra no segundo
perfil, em que a transmissão das mensagens entre os nós, principalmente aquelas
relacionadas à fase de consulta, não requer que seja executada de forma ordenada.
Em um ambiente peer-to-peer, cada nó possui conexões com outros; essas conexões são utilizadas para enviar e receber mensagens de maneira dinâmica. As
mensagens não apresentam um relacionamento de ordenação entre si, não havendo
restrições na seqüência que são entregues à camada de aplicação. A ordenação estrita
na entrega das mensagens exige que a camada de transporte armazene mensagens
que chegam fora de ordem, e não sejam entregues à camada de aplicação até que
as mensagens de posições anteriores na seqüência sejam retransmitidas e estejam
disponı́veis nos buffers do destinatário.
Os aplicativos peer-to-peer requerem confiabilidade na entrega das mensagens,
porém a ordem estrita na entrega das mensagens não é uma caracterı́stica necessária,
ou, ainda, é um serviço que causa danos ao desempenho da transmissão de mensagens. Uma vez que a maioria dos aplicativos peer-to-peer são desenvolvidos com a
utilização do TCP, obtém-se, neste caso, a confiabilidade, que é um serviço que o
aplicativo demanda, e a ordem estrita na entrega das mensagens, que é um serviço
que a aplicação não precisa.
O protocolo SCTP oferece a confiabilidade, porém, permite que as mensagens
sejam entregues de maneira não ordenada. Essa caracterı́stica pode ser utilizada
para solucionar o problema do HOL e, com isso, é possı́vel melhorar o desempenho
na transmissão de mensagens entre os nós. Um dos ganhos mais importantes com
a melhora no desempenho da transmissão de mensagens nó-a-nó é o que se refere à
fase de consulta, que passa a apresentar um tempo de resposta médio inferior àquele
que utiliza o TCP na camada de transporte.
Nesta seção é visto em detalhes o problema do HOL causado pelo TCP e, em
4.2. O problema de HOL em redes peer-to-peer
76
seguida, são mostradas duas soluções para esse problema com a utilização do SCTP,
uma utilizando a funcionalidade dos multi-fluxos e outra, utilizando a funcionalidade
da entrega desordenada de mensagens.
4.2.1
O HOL causado pelo TCP
O TCP é um protocolo que apresenta ordenação estrita na entrega dos dados. As
aplicações que demandam este serviço encontram no TCP as caracterı́sticas necessárias para desenvolver o seu sistema. No entanto, as aplicações que não precisam dessa ordenação estrita e são implementadas com a utilização do TCP, podem
enfrentar o problema chamado HOL, ou head-of-line blocking.
O HOL ocorre, no TCP, quando um segmento de dados, pertencente a uma
mesma conexão, é perdido, e outros subseqüentes chegam antes do primeiro. Estes
segmentos são enfileirados, e armazenados nos buffers de recebimento da camada de
transporte até que o primeiro seja retransmitido e chegue ao receptor. Assim que
o primeiro segmento chega, ele e os demais são entregues à camada de aplicação.
O atraso na entrega de segmentos subseqüentes, causados pelo atraso de outros
segmentos que faltam, é resultado da necessidade da entrega dos bytes na mesma
ordem em que o remetente os enviou. A Figura 4.8 ilustra este cenário com um
servidor que envia ao cliente quatro mensagens através de uma única conexão TCP.
A Figura 4.8 representa a camada de aplicação e a camada de transporte, sendo
possı́vel observar o comportamento da mensagem desde o momento em que é enviada
da camada de aplicação à camada de transporte, assim como o momento em que a
camada de transporte libera os dados à camada de aplicação.
No servidor, a camada de aplicação entrega à camada de transporte os dados da
mensagem de número um, representada pela sigla Msg 1. Imediatamente, a camada
de transporte adiciona o seu cabeçalho à mensagem e a envia para a camada de
rede1 . Os dados partem, então, para o cliente.
A mensagem de número dois passa pelo mesmo processo, mas é perdida no
caminho até o cliente. Isso representa, na camada de transporte, a perda de um
segmento que carrega essa mensagem, ou parte dela2 . As mensagens de número três
1
A camada de rede não é mostrada pois, essa representação possui enfoque somente em como a camada
de transporte manipula a liberação de dados para a camada de aplicação.
2
O TCP é orientado a bytes, mas a noção de mensagem é implementada na camada de aplicação
4.2. O problema de HOL em redes peer-to-peer
77
Figura 4.8: Transmissão de mensagens utilizando o TCP
e quatro são enviadas, e chegam no cliente. No entanto, elas são enfileiradas nos
buffers da camada de transporte, não sendo liberadas à camada de aplicação.
O servidor detecta, então, que o segmento que carregava a mensagem de número
dois, ou parte dela, não chegou ao destino. Imediatamente, o TCP faz com que esse
segmento perdido seja retransmitido. Essa á uma caracterı́stica do TCP conhecida
como retransmissão seletiva [20]. Assim que o destinatário recebe a mensagem
de número dois por completo, ela e todas as outras que estavam aguardando sua
chegada são entregues à camada de aplicação. A espera das mensagens de número
três e quatro nos buffers da camada de transporte, causada pela perda da mensagem
de número dois, caracteriza o HOL.
As variáveis A1 e A2 representam os atrasos provocados pelo HOL. A variável
A1 representa o tempo desnecessário que a mensagem de número três ficou armazenada na camada de transporte, e a variável A2 o atraso desnecessário sofrido pela
mensagem de número quatro.
Se a aplicação precisa da ordem estrita na entrega das mensagens, o HOL é uma
delimitando o fluxo de bytes em partes, denominadas mensagens [42].
4.2. O problema de HOL em redes peer-to-peer
78
conseqüência do serviço que esta aplicação demanda. No entanto, como no caso dos
aplicativos peer-to-peer, esta ordenação não é necessária.
Embora a transmissão de mensagens através de várias conexões TCP evite o
HOL, do ponto de vista do uso da rede, essa opção não otimiza a utilização do canal
de dados. A perda de segmento de uma conexão, como um sinal de congestionamento
no caminho, não necessariamente faz com que as outras conexões diminuam sua taxa
de envio de dados à rede. Assim sendo, pode ser inviável para o sistema operacional
sustentar enormes quantidades de conexões TCP.
4.2.2
A utilização dos multi-fluxos
O SCTP oferece o suporte a multi-fluxos. No cabeçalho do SCTP, são reservados 16
bits para designar o número do fluxo, podendo-se atingir até 65536 fluxos distintos.
Este serviço permite que duas ou mais mensagens sejam enviadas através de canais
distintos, os quais são independentes entre si. Isto significa que a mensagem enviada
em um dos fluxos não afeta a ordem de entrega de uma outra mensagem enviada em
outro fluxo. No entanto, mensagens enviadas dentro de um mesmo fluxo mantém a
ordem em que foram enviadas.
Com esse mecanismo, é possı́vel aliviar o HOL enviando mensagens em diferentes
fluxos. As mensagens enviadas podem sofrer HOL dentro do fluxo, mas elas não
afetam mensagens de outros fluxos. Essa funcionalidade é aplicável em sistemas que
necessitam de ordenação em somente uma porção de seus dados, mas não quer que
outras seqüências de dados afetem a primeira porção, e vice-versa.
A utilização dos multi-fluxos é adequada para aplicações peer-to-peer, por exemplo, em que se pode ter diferentes canais para a transferência de arquivos. A
aplicação Octopus, desenvolvida neste capı́tulo, faz uso desse mecanismo. A Figura 4.9 representa a transmissão de quatro mensagens, desta vez, com a utilização
dos multi-fluxos do SCTP.
Nesse exemplo, o servidor envia a primeira mensagem, através do fluxo de número
um, que é perdida. A mensagem de número dois é enviada, também através do
fluxo de número um. No entanto, ao atingir o destinatário, ela é enfileirada na
camada de transporte, pois a mensagem de número um, que pertence ao mesmo
fluxo, ainda não chegou. As mensagens de número três e quatro são enviadas através,
4.2. O problema de HOL em redes peer-to-peer
79
Figura 4.9: Transmissão de mensagens utilizando os multi-fluxos do SCTP
respectivamente, dos fluxos de número dois e três, e confiavelmente entregues à
camada de aplicação, sem esperar a chegada da mensagem de número um ou dois,
aliviando o HOL.
Como mostrado na Figura 4.9, o SCTP detecta que a mensagem de número um
foi perdida, efetuando, assim, sua retransmissão. Quando essa mensagem chega ao
destino, ela e a mensagem de número dois são entregues à camada de aplicação.
A mensagem de número dois sofreu HOL, pois pertence ao mesmo fluxo que a
mensagem de número um, e precisava ser entregue ordenadamente. A variável A
representa o atraso sofrido pela mensagem de número dois.
Apesar deste mecanismo ter aliviado o HOL, não o resolve de maneira completa,
pois as mensagens intra-fluxo precisam ser entregues na seqüência. No entanto, se
uma aplicação precisar de ordenação em somente uma parcela de fluxos de mensagens
que envia, essa funcionalidade pode ser a solução.
4.2. O problema de HOL em redes peer-to-peer
4.2.3
80
A utilização da entrega desordenada de mensagens
O SCTP oferece, ainda, o suporte à entrega de mensagens de forma completamente
desordenada à camada de aplicação. As mensagens que forem marcadas como desordenadas, ou melhor, para serem tratadas sem considerar a ordem de chegada,
independentemente do fluxo através do qual foram enviadas, são entregues à camada de aplicação assim que estiverem disponı́veis no destinatário. Isso é atingido
pela marcação de um bit no chunk que carrega a mensagem. A Figura 4.10 representa a transmissão de quatro mensagens usando o SCTP, desta vez com a utilização
da entrega desordenada de mensagens.
Figura 4.10: Transmissão de mensagens utilizando a entrega desordenada do SCTP
Neste exemplo, a mensagem de número um, marcada como desordenada, é transmitida e perdida. As mensagens de número dois, três e quatro são enviadas, também
marcadas como desordenadas, e entregues à aplicação sem sofrer HOL. A mensagem
de número um é, então, retransmitida, e entregue ao destinatário.
Se as mensagens são marcadas como desordenadas, a identificação do fluxo é
irrelevante, pois a sua entrega à camada de aplicação será imediata à sua chegada.
É importante salientar que, mesmo assim, o remetente coloca o identificador de fluxo
no chunk, permitindo, desta maneira, que esse valor seja utilizado para algum outro
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
81
fim, como, por exemplo, para classificar diferentes mensagens. No caso da aplicação
Octopus, esse campo é utilizado para identificar qual o tipo de mensagem Gnutella
é recebida.
4.3
Análise comparativa entre o SCTP e o TCP para aplicativos de compartilhamento de arquivos em peer-to-peer
Para mostrar que o SCTP pode melhorar o desempenho da transmissão de mensagens, em relação ao TCP, em uma rede peer-to-peer, um ambiente de simulação
é utilizado para a execução de experimentos práticos. Esta seção descreve esse
ambiente e os aplicativos que implementam as suas entidades. Em seguida, são
apresentadas as formas pelas quais os experimentos foram executados, bem como os
resultados obtidos.
O ambiente de simulação tem como objetivo implementar, de maneira similar ao
comportamento de uma rede Gnutella real, a troca de mensagens entre dois nós e,
assim, efetuar uma comparação entre o TCP e o SCTP como protocolos da camada
de transporte para aplicações peer-to-peer de compartilhamento de arquivos.
4.3.1
Descrição do ambiente de simulação
O ambiente de simulação é composto por dois nós, “Nó 1” e “Nó 2”, e um gerador
de perda e atraso de pacotes. O “Nó 1” e o “Nó 2” representam nós quaisquer,
em uma rede peer-to-peer, que possuem uma conexão, ou associação, estabelecida.
O gerador de perda e de atraso de pacotes emula o comportamento que a Internet
provoca sobre as mensagens que trafegam do “Nó 1” ao “Nó 2”, e do “Nó 2” ao “Nó
1”. Nesta simulação, o “Nó 1”, o “Nó 2” e o gerador de perda e atraso de pacotes
são aplicações que são executadas em computadores.
O aplicativo utilizado para atuar como gerador de perda e atraso de pacotes é o
NIST Net [3]. O NIST Net é uma ferramenta para o sistema operacional Linux que
facilita a execução de testes e experimentos, emulando uma rede hipotética em um
computador, permitindo a definição de parâmetros tais como atraso, jitter, limitação
de largura de banda, perda e duplicação de pacotes, entre outros, semelhante ao que
acontece em uma rede de computadores real.
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
82
Figura 4.11: A emulação do comportamento esperado de uma rede de computadores por
meio do NIST Net
Como mostrado na Figura 4.11, uma rede hipotética, representada pela Internet,
sobre a qual o computador A se comunica com o computador C, e o computador B
se comunica com o computador D, tem seu comportamento emulado em laboratório.
Essa emulação é atingida pela configuração de valores esperados de comportamento
desta rede hipotética como parâmetros do aplicativo NIST Net. A utilização deste
aplicativo é bem simples, pois toda a configuração pode ser feita por meio de uma
interface gráfica, como mostrado no Anexo A.
O computador utilizado para executar o aplicativo que atua como o “Nó 1”
possui um processador Intel Celeron de 2.0 Ghz com 256 Mbytes de memória RAM,
rodando o sistema operacional Linux com kernel de versão 2.6.10, da distribuição
Slackware, versão 10.1. O computador utilizado para executar o aplicativo que atua
como o “Nó 2” possui um processador Intel Celeron de 1.2 Ghz com 128 Mbytes de
memória RAM, rodando o sistema operacional Linux com kernel de versão 2.6.9 da
distribuição Slackware, versão 9.0.
O computador utilizado para executar o aplicativo NIST Net, atuando como
gerador de perda e atraso de pacotes possui um processador Pentium III de 866
Mhz com 128 Mbytes de memória RAM, rodando o sistema operacional Linux com
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
83
kernel de versão 2.6.9 da distribuição Slackware, versão 9.0.
Os aplicativos que implementam as entidades do ambiente de simulação fazem
uso da arquitetura de protocolos TCP/IP. O aplicativo NIST Net atua na camada
de rede, e os aplicativo que implementam o “Nó 1” e o “Nó 2” atuam na camada de
aplicação em dois momentos distintos: primeiramente utilizando o protocolo TCP na
camada de transporte, e depois utilizando o protocolo SCTP. A Figura 4.12 mostra a
pilha de protocolos utilizada por cada entidade, assim como o relacionamento entre
as diversas camadas.
Figura 4.12: As pilhas de protocolos utilizadas pelos aplicativos do ambiente de simulação
A Figura 4.12 (a) representa a pilha de protocolos com a utilização do TCP. A conexão TCP é representada como a comunicação lógica entre dois processos rodando
em dois computadores diferentes, e as setas, uma em cada sentido, representam a
natureza full-duplex da conexão. O relacionamento entre a camada de aplicação,
implementando um aplicativo peer-to-peer, e a camada de transporte, implementando o TCP, é representada por uma única seta, pois há uma única via, full-duplex,
através da qual o processo se comunica com a camada de transporte.
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
84
Na Figura 4.12 (b), o destaque é dado pelo uso do SCTP na camada de transporte. A associação SCTP possui várias setas em cada sentido pois permite que
diversos fluxos sejam utilizados para a comunicação entre processos em diferentes
computadores, através de uma única associação. As setas indicando o relacionamento entre a camada de aplicação, implementando um aplicativo peer-to-peer, e a
camada de transporte, implementando o SCTP, representam as múltiplas vias pelas
quais o aplicativo peer-to-peer pode receber e enviar suas mensagens utilizando os
multi-fluxos.
A aplicação que implementa o “Nó 1”, ou aplicação geradora e receptora de
pacotes, cria duas threads, sendo que uma delas gera e transmite mensagens, e outra
recebe mensagens. A aplicação que implementa o “Nó 2” atua simplesmente como
echo server. O código fonte destas aplicações pode ser encontrado na ı́ntegra no
Apêndice A. Para essa implementação, fez-se uso da linguagem C, com utilização
das bibliotecas pthread lib, e da libsctp [12, 13, 40].
O “Nó 1” atua como gerador e receptor de pacotes, simulando o tráfego de
mensagens dentro de uma rede overlay. Os pacotes gerados por esse nó são enviados
ao “Nó 2”. O “Nó 2” atua como um echo server, enviando as mensagens de volta ao
“Nó 1”, assim que estiverem disponı́veis. Desta maneira, é possı́vel efetuar cálculos
de desempenho individualmente para cada mensagem, em que o trânsito de uma
mensagem do “Nó 1” ao “Nó 2”, e do “Nó 2” ao “Nó 1” representam dois hops
da mensagem na rede overlay. O funcionamento destas aplicações é mostrado nos
diagramas da Figura 4.13.
A Figura 4.13 (a) mostra o funcionamento do aplicativo gerador e receptor de
pacotes, ou “Nó 1”, e a Figura 4.13 (b) mostra o funcionamento do aplicativo que
atua como echo server, ou “Nó 2”.
O aplicativo que atua como echo server, mostrado na Figura 4.13 (b), simplesmente recebe o pacote e o devolve ao nó que o enviou. Caso esse pacote seja enviado
fazendo o uso do protocolo SCTP, o echo server devolve esse pacote através do
mesmo fluxo que o recebeu.
O aplicativo gerador e receptor de pacotes cria duas threads: uma que gera e envia
os pacotes, e outra que recebe os pacotes que foram devolvidos pelo echo server. A
primeira thread gera e envia os pacotes de acordo com parâmetros definidos pelo
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
85
Figura 4.13: As fases de funcionamento dos aplicativos gerador e receptor de pacotes e
echo server
usuário. Um desses parâmetros é o protocolo da camada de transporte, escolhido
entre o TCP e o SCTP. Os parâmetros comuns a ambos os protocolos são:
• Endereço local e porta local: Um único endereço e uma única porta local devem
ser especificados para iniciar o aplicativo gerador e receptor de pacotes;
• Endereço remoto e porta remota: Especifica o endereço e a porta do echo
server ;
• Número de mensagens a enviar: Para cada experimento, várias de mensagens
são encaminhadas ao echo server, e essa quantidade de mensagens é definida
pelo usuário por meio deste parâmetro;
• Intervalo de tempo entre as mensagens: Esse parâmetro, medido em milisegundos, representa o espaço de tempo entre cada nova mensagem gerada e enviada
ao echo server. Por meio dele, é possı́vel definir quantas mensagens o aplicativo
vai enviar em um espaço de tempo. Pretende-se, com esse parâmetro, simular
maiores e menores taxas de tráfego de mensagens entre os nós;
• Tamanho dos pacotes: As mensagens em uma rede peer-to-peer podem apresentar diferentes tamanhos. Esse parâmetro permite definir o tamanho, em
bytes, das mensagens que são encaminhada ao outro nó;
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
86
Quando o SCTP é selecionado como protocolo da camada de transporte, outros parâmetros podem ser configurados, os quais fazem uso de funcionalidades não
presentes no TCP:
• Número de fluxos: Esse parâmetro define a quantidade de fluxos que o aplicativo deve utilizar para enviar as mensagens. Cada uma delas vai ser enviada a
um fluxo diferente, segundo a polı́tica round-robin;
• Envio desordenado de mensagens: Se este parâmetro for habilitado, as mensagens serão enviadas de forma desordenada, independentemente do número de
fluxos utilizado.
Cada thread do aplicativo gerador e receptor de pacotes possui etapas de funcionamento distintas. A Figura 4.14 mostra o funcionamento da thread geradora
de pacotes, e a Figura 4.15 mostra o funcionamento da thread de recebimento de
pacotes.
Figura 4.14: O funcionamento da thread geradora de pacotes
Quando uma mensagem é gerada, um “tag de identificação” é definido de forma
que, no recebimento desta mensagem, seja possı́vel interromper o seu temporizador
especı́fico. O “manipulador de fluxos e ordenação” é utilizado pelo protocolo SCTP
a partir dos parâmetros “número de fluxos” e “envio desordenado de mensagens”.
É nesta entidade que é definido através de qual fluxo uma mensagem é enviada e,
também, se a mensagem vai ser enviada de forma desordenada.
O “gerador de atraso entre mensagens” suspende a execução da thread entre o envio de cada mensagem por um tempo definido pelo usuário. O objetivo desse atraso,
é simular um número determinado de mensagens que são enviadas num espaço de
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
87
tempo. Assim que a mensagem é liberada para ser enviada, um temporizador, associado ao seu identificador, é iniciado. A mensagem é, então, enviada ao echo server,
que, ao receber a mensagem, a envia de volta ao “Nó 1”.
Quando o SCTP é utilizado como protocolo da camada de transporte, as mensagens enviadas são devolvidas pelo echo server através do mesmo fluxo que as
receberam, e se forem enviadas de forma desordenada, devem ser devolvidas da
mesma maneira.
Figura 4.15: O funcionamento da thread receptora de pacotes
A thread receptora de pacotes, com suas etapas mostradas na Figura 4.15, confere o tag de identificação da mensagem e interrompe o temporizador ao qual está
associada. Com isso, é possı́vel obter a latência de de envio e recebimento para cada
mensagem, como exemplificado nas Figuras 4.16, 4.17 e 4.18.
Figura 4.16: A cálculo da latência de uma mensagem - Exemplo 1
Na Figura 4.16 a mensagem é enviada do “Nó 1”, que gera e recebe os pacotes,
para o “Nó 2”, que envia as mensagens de volta ao primeiro nó. A variável L1
representa a latência da mensagem de número um, representada por Msg 1. Observase pela Figura 4.16, que o tempo de latência é calculado tomando como base o
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
88
momento em que a camada de aplicação envia a mensagem, e o momento em que
essa mesma camada a recebe, ou seja, o temporizador só é interrompido quando a
camada de aplicação recebe a mensagem.
Figura 4.17: O cálculo da latência de uma mensagem - Exemplo 2
A Figura 4.17 mostra o comportamento de uma mensagem perdida no caminho
de ida, do “Nó 1” ao “Nó 2”, e no caminho de volta, do “Nó 2” ao “Nó 1”. A latência
final, representada pela variável L1, é computada a partir do momento em que a
camada de aplicação envia os dados para a camada de transporte, e não recomeça
a contagem caso haja uma retransmissão.
A Figura 4.18 mostra o envio de duas mensagens com o uso do TCP, em que
a primeira é perdida no caminho de ida. Isso faz com que a mensagem de número
dois sofra o HOL e fique armazenada na camada de transporte até que a primeira
mensagem seja retransmitida e chegue ao “Nó 2”. Assim que isso acontece, as duas
mensagens são enviadas de volta ao “Nó 1”. As variáveis L1 e L2 representam,
respectivamente, a latência final da mensagem de número um e da mensagem de
número dois.
Através deste ambiente, pretende-se simular uma rede peer-to-peer nó-a-nó, comparando os protocolos TCP e SCTP como protocolos da camada de transporte para
aplicativos peer-to-peer. Esta comparação é atingida por meio da reprodução das
caracterı́sticas de transferência de mensagens em uma rede peer-to-peer, no ambi-
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
89
Figura 4.18: O cálculo da latência de uma mensagem - Exemplo 3
ente em que o comportamento e a dinâmica das mensagens são obtidas através dos
parâmetros passados pelo usuário e, também, pela configuração do comportamento
que a Internet provoca nas mensagens transmitidas. A métrica de desempenho é a
média das latências de um dado número de mensagens.
4.3.2
A simulação da transferência de mensagens numa rede peer-topeer
Para efetuar os experimentos que simulam a transferência de mensagens numa rede
peer-to-peer, vários parâmetros devem ser configurados nos aplicativos que compõem
o ambiente de simulação, não só naqueles que implementam o “Nó 1” e “Nó 2”,
mas também naquele que atua como o gerador de atraso e perda de pacotes. Os
parâmetros configurados neste último aplicativo são:
• RTT: Os valores de RTT (Round Trip Time) entre os nós de uma rede peer-topeer são variados. Uma vez que eles podem se situar em diferentes localidades
geográficas, um valor médio de escala mundial para este parâmetro é difı́cil de
ser estimado. Para efetuar os experimentos aqui descritos, é utilizado o RTT
médio registrado entre dois computadores, um localizado no Brasil e outro nos
Estados Unidos, no mês de janeiro de 2006, que é de aproximadamente 200
milisegundos [4];
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
90
• Taxa de perda: Os valores de taxas de perda entre dois diferentes nós em
uma rede peer-to-peer são variáveis, pois os pacotes que trafegam de um nó a
outro podem atravessar caminhos que apresentam baixas ou altas taxas. Assim
sendo, os experimentos mostram resultados para taxas de perda de 0% a 20%,
permitindo, assim, uma análise geral do comportamento da transmissão de
mensagens sob diferentes condições, referentes à perda de pacotes.
Os parâmetros configurados nos aplicativos que implementam o “Nó 1” e “Nó
2” são:
• Intervalo de tempo entre mensagens: Os dados descritos na Seção 4.1.1 são
utilizados como referência para a configuração deste parâmetro. O valor médio
de transmissão de QUERYs em uma rede Gnutella é de, aproximadamente, 40
mensagens por segundo e de QUERYHITs é de, aproximadamente, 25 mensagens por segundo;
• Tamanho das mensagens: Os dados descritos na Seção 4.1.2 são utilizados
como referência para a configuração deste parâmetro. O tamanho médio das
mensagens QUERY é de 50 bytes e das mensagens QUERYHIT é de 80 bytes;
• Número de mensagens: Em cada experimento são transmitidas 100 mensagens.
Desta forma, é possı́vel obter a latência média da transmissão de mensagens,
calculada pela média da soma das latências de cada uma delas.
Os experimentos são executados em duas etapas; a primeira é executada fazendo
uso dos parâmetros mostrados na Tabela 4.5.
Tabela 4.5: Parâmetros dos experimentos da primeira etapa
P arâmetro
Valor
Quantidade de mensagens
100
Tamanho das mensagens
80 bytes
Quantidade de mensagens por segundo
25
Na primeira etapa, cada experimento consiste no envio de 100 mensagens de
80 bytes, com um intervalo de 40 milisegundos entre cada uma delas, enviando,
desta forma, 25 mensagens por segundo. Cada mensagem é enviada ao echo server,
que responde imediatamente. São armazenadas as latências de cada uma das 100
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
91
mensagens, e o resultado final é a média aritmética das latências de todas elas, que
representa o tempo médio que uma mensagem leva para ir e voltar do echo server.
São executados dez experimentos para cada valor de taxa de perda configurada,
que variam de 0% a 20%. Foram escolhidos dez experimentos pois, desta forma,
consegue-se mostrar a dinâmica da variação de desempenho entre as amostras sob
uma mesma taxa de perda.
Em cada um dos experimentos é calculado o tempo médio de envio de mensagens com a utilização do protocolo TCP, com a utilização do protocolo SCTP
enviando mensagens de forma desordenada, e com a utilização dos multi-fluxos do
SCTP, sendo que três fluxos são utilizados. A utilização de três fluxos é meramente
ilustrativa, para mostrar que o HOL pode ocorrer dentro de um mesmo fluxo.
A segunda etapa é executada com os parâmetros apresentados na Tabela 4.6.
Tabela 4.6: Parâmetros dos experimentos da segunda etapa
P arâmetro
Valor
Quantidade de mensagens
100
Tamanho das mensagens
50 bytes
Quantidade de mensagens por segundo
40
Na segunda etapa, cada experimento consiste no envio de 100 mensagens de
50 bytes, com um intervalo de 25 milisegundos entre cada uma delas, ou seja, são
enviadas 40 mensagens por segundo. Da mesma forma que na primeira etapa, as
taxas de perda variam de 0% a 20%, e para cada experimento são utilizados o TCP,
o SCTP com envio de mensagens de forma desordenada, e o SCTP com a utilização
de três fluxos.
4.3.3
O desempenho dos protocolos TCP e SCTP
Esta seção mostra os resultados comparativos entre o SCTP e o TCP. Os dez experimentos que são executados para cada taxa de perda configurada são indicados,
nas figuras, pelas letras de A até J. As legendas das barras significam, no caso de
“TCP”, a utilização do TCP na camada de transporte, no caso de “SCTP-D”, a
utilização do SCTP com entrega de mensagens de forma desordenada e, no caso de
“SCTP-F3”, a utilização dos multi-fluxos do SCTP com três fluxos. São mostrados
os resultados para algumas taxas de perda e, também, um gráfico geral que consta
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
92
a média dos resultados de todos os experimentos, para a duas etapas.
As Figuras 4.19, 4.20 e 4.21 mostram os resultados de 30 experimentos da primeira etapa, sendo dez experimentos com a taxa de perda configurada em 0%, dez
com a taxa de perda configurada em 10% e dez com a taxa de perda configurada
em 20%.
Figura 4.19: Resultados da primeira etapa com taxa de perda de 0%
Figura 4.20: Resultados da primeira etapa com taxa de perda de 10%
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
93
Figura 4.21: Resultados da primeira etapa com taxa de perda de 20%
Observa-se, pelos resultados dos experimentos, que o desempenho na transmissão
de mensagens com a utilização do TCP é degradada, ou seja, o tempo médio da
transmissão de mensagens nó-a-nó aumenta, quando se aumenta a taxa de perda.
Observa-se, ainda, que o desempenho do SCTP com a utilização dos três fluxos é
um pouco inferior ao SCTP com a utilização da entrega desordenada de mensagens
pois, mesmo aliviando o HOL em relação ao TCP, ainda há casos onde ocorre o
HOL intra-fluxo. Quando a taxa de perda é 0% o SCTP não demonstra ter ganhos
significativos em relação ao TCP, no entanto, a diferença na latência média de aproximadamente 0,7 segundos deve-se às diferenças de implementação dos protocolos,
assim como às funções adicinais da camada de aplicação que o TCP utiliza para
atingir a confiabilidade completa.
Os tempos médios da transmissão de 100 mensagens em cada um dos experimentos, de acordo com o comportamento configurado nesta primeira etapa, mostram que
o SCTP se adapta melhor ao comportamento de uma rede peer-to-peer que o TCP
pois, alivia o HOL com a utilização dos multi-fluxos e evita completamente o HOL
com a utilização da entrega desordenada de mensagens.
A Figura 4.22 mostra os resultados médios dos experimentos para taxas de perda
de 0% a 20%. Cada ponto no gráfico representa a latência média do envio de 1000
mensagens.
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
94
Figura 4.22: Média dos resultados da primeira etapa - variação da taxa de perda de 0% a
20%
Observa-se pelo gráfico geral que quanto maior a taxa de perda, mais degradado
é o desempenho do TCP em relação ao SCTP. Isso ocorre por causa do problema do
HOL, que faz com que as mensagens que chegam fora de ordem fiquem aguardando
na camada de transporte e não sejam liberadas à camada de aplicação.
As Figuras 4.23, 4.24 e 4.25 mostram os resultados de 30 experimentos, desta
vez com os parâmetros da segunda etapa, sendo dez experimentos com a taxa de
perda configurada em 0%, dez com a taxa de perda configurada em 10% e dez com
a taxa de perda configurada em 20%.
Figura 4.23: Resultados da segunda etapa com taxa de perda de 0%
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
Figura 4.24: Resultados da segunda etapa com taxa de perda de 10%
Figura 4.25: Resultados da segunda etapa com taxa de perda de 20%
95
4.3. Análise comparativa entre o SCTP e o TCP para aplicativos de
compartilhamento de arquivos em peer-to-peer
96
Os resultados dos experimentos feitos nesta etapa não demonstraram ser significantemente diferentes daqueles executados na primeira etapa. Mesmo com o envio
de maior quantidade de mensagens por segundo e, ainda, de mensagens de menor
comprimento, o comportamento causado pelo HOL foi similar. A Figura 4.26 mostra
os resultados médios de todos os experimentos da segunda etapa.
Figura 4.26: Média de resultados da segunda etapa - variação da taxa de perda de 0% a
20%
Observa-se na Figura 4.26, como na primeira etapa, que altas taxas de perda
de pacotes tendem a degradar o desempenho da transmissão de mensagens nó-anó quando o protocolo TCP é utilizado na camada de transporte. Assim sendo, a
utilização do SCTP pode ser mais adequada para esta tarefa. Ainda, como discutido
na Seção 4.1.3, caso seja melhorado o tempo de transmissão de mensagens nó-a-nó, o
tempo médio de resposta às consultas em uma rede peer-to-peer pode ser melhorado.
Foram mostradas duas utilizações para o SCTP, uma com os multi-fluxos, em
que foram usados três fluxos, e outra com a entrega desordenada de mensagens. A
forma de utilização destas funcionalidades em um aplicativo peer-to-peer depende
da especificação do protocolo e dos serviços demandados pelo aplicativo.
O aplicativo Octopus, descrito na próxima seção, faz uso dessas funcionalidades,
mais especificamente a entrega desordenada de mensagens para o transporte de
mensagens Gnutella, e a entrega ordenada de mensagens para a transferência de
arquivos entre dois nós, ambas através de uma única associação.
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
4.4
97
A aplicação peer-to-peer de compartilhamento de arquivos Octopus
Esta seção descreve o desenvolvimento do aplicativo Octopus. Este aplicativo permite que os usuários consultem os nós em uma rede overlay em busca de arquivos.
Uma vez encontrado um arquivo, o usuário, de posse de sua localização, pode efetuar a sua transferência. O Octopus utiliza os conceitos do SCTP e do Gnutella,
versão 0.4, descritos nos capı́tulos anteriores, em uma rede peer-to-peer cuja camada
de transporte é implementada por meio do SCTP.
Esta seção tem como objetivo descrever um protótipo de aplicativo que mostra
como algumas das funcionalidades do SCTP podem ser aproveitadas por um aplicativo peer-to-peer de compartilhamento de arquivos, sem a intenção de abranger
completamente todas as formas de uso do SCTP para este, ou outros gêneros e
arquiteturas de sistemas peer-to-peer.
O Octopus foi desenvolvido fazendo uso da linguagem C com as bibliotecas pthread lib, libsctp e a GTK lib, versão 2.0, para a interface gráfica, e seu código fonte
pode ser encontrado na ı́ntegra no Apêndice B.
4.4.1
O protocolo Gnutella com o uso do SCTP
O protocolo da camada de aplicação utilizado pelo aplicativo Octopus é o Gnutella, versão 0.4. No entanto, algumas modificações no formato do cabeçalho das
mensagens Gnutella podem ser feitas de modo a melhor utilizar as funcionalidades
oferecidas pelo protocolo da camada de transporte SCTP.
Dois campos são removidos do cabeçalho das mensagens do protocolo Gnutella:
o campo de tamanho do payload e o de descritor de payload. A Figura 4.27 mostra
a reestruturação do cabeçalho.
Figura 4.27: Novo cabeçalho para o protocolo Gnutella
A Figura 4.27 (a) mostra o cabeçalho antigo, e a Figura 4.27 (b) mostra o
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
98
cabeçalho reestruturado:
• O campo que indica o tamanho do payload é removido pois o SCTP é orientado
a mensagens, não havendo a necessidade de se delimitar o fluxo de bytes com
campos adicionais;
• O campo de descritor de payload é removido pois, para detectar qual tipo de
mensagem está sendo entregue à camada de aplicação, utiliza-se o identificador
do fluxo que a mensagem foi enviada.
4.4.2
O funcionamento do aplicativo Octopus
O aplicativo Octopus foi desenvolvido levando em consideração as três fases, descritas na introdução deste capı́tulo, referentes à conexão, à consulta e à transferência
de arquivos em redes peer-to-peer. Como mostra o fluxograma da Figura 4.28, os
estágios tı́picos3 de funcionamento do aplicativo Octopus são:
• Abrir servidor: Neste estágio, o aplicativo cria uma thread que inicia o servidor
de associações. Por meio deste servidor, o Octopus pode aceitar pedidos de
estabelecimento de associação de outros nós;
• Estabelecer associações: Neste estágio, o aplicativo solicita o estabelecimento
de associação a nós cujo endereço IP o usuário forneceu, ou que o aplicativo
buscou em uma base de endereços própria;
• Fazer consulta: O usuário pode efetuar uma consulta que vai ser propagada
através da rede caso o aplicativo tenha estabelecido, ao menos, uma associação;
• Receber resposta: Caso o aplicativo receba uma mensagem de resposta a uma
consulta feita pelo usuário, os dados desta resposta são mostrados na tela;
• Ativar transferência de arquivos: Se o usuário deseja obter um dos arquivos
cujos dados foram apresentados na tela, este estágio é, então, ativado.
3
Usa-se o termo “tı́pico”, pois esses estágios podem ser intercalados e sobrepostos, diferindo da maneira
pela qual é apresentada no fluxograma. Por exemplo, ao mesmo tempo que uma consulta está em andamento, o usuário pode decidir se associar a um outro nó e, ainda, pode já estar efetuando a transferência
de um arquivo.
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
Figura 4.28: O fluxograma de funcionamento do Octopus
99
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
100
O modo com que as mensagens Gnutella e os arquivos são transmitidos no Octopus é diferente da forma pela qual essas tarefas são normalmente executadas por
um aplicativo que faz uso do TCP na camada de transporte. O SCTP pode utilizar
o mesmo canal de relacionamento para executar ambas as tarefas simultaneamente.
As Figuras 4.29 e 4.30 mostram esses dois casos.
Figura 4.29: Um aplicativo peer-to-peer utilizando o TCP na camada de transporte
Um aplicativo que utiliza o TCP na camada de transporte normalmente utiliza
uma conexão dedicada para a transmissão das mensagens Gnutella e, para cada
arquivo que deseja transmitir, uma nova conexão é estabelecida, como mostrado na
Figura 4.29.
Por sua vez, o aplicativo Octopus faz uso de uma única associação SCTP para a
transferência das mensagens de negociação da “conexão” Gnutella, das mensagens
PING, PONG, QUERY e QUERYHIT4 , e que pode, ainda, ser utilizada para a
negociação e transferência de mais de um arquivo simultaneamente5 , como mostra
a Figura 4.30.
4
Nesta versão do Octopus não foi implementada a mensagem PUSH.
Nesta versão do Octopus o número máximo de arquivos a ser transferidos simultaneamente de um
mesmo nó é de três arquivos.
5
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
101
Figura 4.30: O aplicativo Octopus utilizando o SCTP na camada de transporte
Há dois cenários a serem considerados a respeito da utilização da associação
SCTP para a transferência de arquivos: aquele em que o arquivo a ser transferido
está em um nó que já é vizinho do nó requerente, ou seja, está localizado a um hop
de distância, e aquele em que o arquivo a ser transferido está localizado em um nó
que não é vizinho do nó requerente, ou seja, está localizado a mais de um hop de
distância.
No caso de o arquivo estar localizado em um nó vizinho, a associação já estabelecida é utilizada para a negociação e transferência deste arquivo, sem a necessidade
de se estabelecer outra associação entre esses nós. Há a possibilidade, ainda, de se
transferir mais de um arquivo simultaneamente, junto com as mensagens Gnutella,
através de uma única associação.
Por outro lado, caso o arquivo esteja localizado em um nó que está a mais
de um hop de distância do nó requerente, uma associação deve ser estabelecida
entre eles. A nova associação pode ser utilizada para transferir mais de um arquivo
simultaneamente e, também, para transmitir mensagens Gnutella.
4.4.3
A arquitetura do aplicativo Octopus
O aplicativo Octopus é composto basicamente de quatro camadas: a camada de
comunicação, a camada de tratamento de mensagens, a camada de armazenamento
e a camada de interface. A camada de interface é aquela que implementa a interface
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
102
gráfica que o usuário pode utilizar para executar as funções do aplicativo.
Os módulos que compõem a camada de armazenamento auxiliam o funcionamento do aplicativo por meio da implementação do cache de associações, do host
cache, do cache de identificadores de mensagens e do repositório de arquivos compartilhados.
A camada de tratamento de mensagens implementa o manipulador de mensagens,
que é a entidade que faz o reconhecimento das mensagens Gnutella de acordo com
o fluxo através do qual foram enviadas, além de providenciar os seus tratamentos.
Ainda, esta entidade trata a negociação e a transferência de arquivos, tanto como
cliente, quanto como servidor, ou seja, o próprio manipulador de mensagens faz a
negociação e o envio de arquivos que são requisitados por outros nós, ou que são
transferidos de outros nós para o nó local.
A camada de comunicação é aquela que implementa os protocolos SCTP e IP,
responsáveis, respectivamente pelas camadas de transporte e camada de rede. A
Figura 4.31 mostra as camadas do aplicativo e os relacionamentos dos diferentes
módulos, destacando, na camada de interface, os eventos que podem ocorrer durante
o tempo de execução.
Na camada de armazenamento, o módulo de cache de associações armazena
informações sobre as associações que o nó local estabeleceu com outros nós, tanto
aquelas que o próprio usuário solicitou, quanto aquelas que outros nós estabeleceram
com o nó local. Este módulo é sempre atualizado quando uma nova associação é
estabelecidada, ou quando há um encerramento em alguma associação do cache.
A estrutura que armazena as informações do cache de associações é implementada
através de uma estrutura cujo código é mostrado a seguir:
struct cacheAssociacoes{
int numeroSocket;
struct sockaddr_in endereco;
};
São armazenados o número do descritor do socket para o acesso a um determinado
nó, assim como as informações do endereço, em uma estrutura do tipo sockaddr in. É
possı́vel, por meio de uma chamada da interface gráfica, imprimir os dados contidos
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
Figura 4.31: A arquitetura do aplicativo Octopus
103
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
104
no cache de associações no shell no Linux, como mostrado na Figura 4.32.
Figura 4.32: A impressão do cache de associações no shell do Linux
O módulo de host cache armazena endereços de nós em que o usuário obteve
sucesso no estabelecimento da associação, e também endereços que foram recebidos
por meio das mensagens PONG, em resposta ao PING requerido pelo usuário.
O host cache é utilizado para facilitar o estabelecimento de associações, pois armazena endereços de possı́veis nós participantes da rede peer-to-peer em um arquivo,
que pode ser utilizado em outras sessões do aplicativo. É possı́vel, por meio de uma
chamada da interface gráfica, imprimir os dados contidos no host cache no shell no
Linux, como mostrado na Figura 4.33.
Figura 4.33: A impressão do host cache no shell do Linux
O cache de identificadores de mensagens é utilizado para três propósitos: a
detecção de mensagens que sofreram loops dentro da rede, a execução do backtrack
de mensagens de resposta e a detecção de mensagens de resposta que pertencem
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
105
ao nó local, providenciando, assim, a exibição das informações na interface gráfica.
A seguinte estrutura é utilizada para implementar o cache de identificadores de
mensagens:
struct cacheIDMensagens
{
int ID;
int NumeroSocket;
struct sockaddr_in endereco;
};
O repositório de arquivos compartilhados contém os arquivos que o usuário deseja disponibilizar para os nós da rede overlay. Para adicionar arquivos neste repositório, o usuário deve copiá-los no diretório “ArquivosCompartilhados”, tendo como
referência o diretório que contém o executável do aplicativo Octopus. O “ı́ndice de
arquivos” não apresenta nenhuma estrutura especı́fica de armazenamento, representando somente uma chamada da aplicação que faz referência aos nomes dos arquivos.
O manipulador de mensagens é a entidade que faz o tratamento das mensagens Gnutella, e que implementa o cliente e o servidor de arquivos. Para detectar
qual tipo de mensagem o nó recebeu e encaminhá-la para a função que faz o seu
tratamento correto, assim como para efetuar a transferência de arquivos simultaneamente através de uma única associação, este módulo faz uso da funcionalidade dos
multi-fluxos do SCTP, como mostrado na Figura 4.34.
Os fluxos numerados de um a quatro são utilizados para o envio das mensagens
Gnutella 6 . O fluxo de número cinco é utilizado para a negociação da transferência de
um arquivo. Todos estes fluxos utilizam o envio de mensagens de forma desordenada,
pois não importa a seqüência em que são entregues à camada de aplicação. As
mensagens que compõem um arquivo são entregues a partir dos fluxos seis, sete ou
oito.
Para cada mensagem que chega no servente, o conteúdo útil é armazenado em um
6
O fluxo de número zero é utilizado para as mensagens de handshake do Gnutella, mas não foi ilustrado
nesta figura.
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
106
Figura 4.34: A utilização dos multi-fluxos pelo aplicativo Octopus
buffer. Em seguida, identifica-se através de qual fluxo a mensagem chegou e, então, o
conteúdo do buffer é passado para a função que trata a mensagem apropriadamente.
As mensagens Gnutella são identificadas da seguinte forma:
• PINGs: Devem ser enviadas através do fluxo 1. Ao receber uma mensagem,
se o manipulador de mensagens detecta que chegou através do fluxo 1, ele a
encaminha para a função que trata PINGs.
• PONGs: Devem ser enviadas através do fluxo 2. Ao receber uma mensagem,
se o manipulador de mensagens detecta que chegou através do fluxo 2, ele a
encaminha para a função que trata PONGs.
• QUERY: Devem ser enviadas através do fluxo 3. Ao receber uma mensagem,
se o manipulador de mensagens detecta que chegou através do fluxo 3, ele a
encaminha para a função que trata QUERYs.
• QUERYHIT: Devem ser enviadas através do fluxo 4. Ao receber uma mensagem, se o manipulador de mensagens detecta que chegou através do fluxo 4,
ele a encaminha para a função que trata QUERYHITs.
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
107
Para identificar o número do fluxo através do qual a mensagem chegou, a API
(Application Programming Interface) do SCTP preenche uma estrutura que contém
as informações sobre o chunk que carregou essa mensagem, e confere o campo
sinfo stream da estrutura a seguir:
struct sctp_sndrcvinfo{
uint16_t
sinfo_stream;
uint16_t
sinfo_ssn;
uint16_t
sinfo_flags;
uint32_t
sinfo_ppid;
uint32_t
sinfo_context;
uint8_t
sinfo_dscp;
sctp_assoc_t sinfo_assoc_id;
}
A seguinte função é utilizada para receber as mensagens através do socket:
struct sctp_sndrcvinfo sri;
quantidadeRecebida=sctp_recvmsg(sock_fd,readbuf,200,
struct sockaddr *) &cliaddr,
&len,&sri,&msg_flags);
O campo sinfo stream da estrutura sctp sndrcvinfo, declarada no programa fonte
como a variável sri, é utilizado para identificar em qual fluxo a mensagem chegou,
e encaminhá-la para a função que faz o seu tratamento.
Os eventos que podem ocorrer neste aplicativo, os quais interagem com a interface
gráfica, são: o usuário fazer o pedido de estabelecimento de associação, o usuário
efetuar uma consulta, o usuário requerer a transferência de um arquivo, e o aplicativo
receber uma mensagem de resposta a uma consulta do usuário. A interface gráfica
do Octopus é mostrada na Figura 4.35:
• Associar: Ao pressionar este botão, o aplicativo tenta se associar ao nó cuja
localização é dada pelas informações fornecidas nos campos “Endereço” e
“Porta”. Caso haja sucesso na associação, o endereço em questão é adicionado ao host cache;
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
108
Figura 4.35: A interface gráfica do aplicativo Octopus
• Consultar: Caso uma associação tenha sido efetivada, o usuário pode consultar os nós da rede por arquivos, preenchendo o campo do texto e, em seguida,
pressionando o botão “Consulta”. O identificador da mensagem QUERY gerada é armazenado no cache de identificadores de mensagem. Desta forma, se
uma mensagem QUERYHIT for detectada, é possı́vel identificar se a resposta
pertence ao nó local;
• Download : Caso alguma resposta tenha chegado, as informações a respeito
dos arquivos são adicionadas à lista de respostas. O usuário que decide fazer
a transferência de algum arquivo deve indicar na lista a opção desejada e
pressionar “Download ”;
• Associações: Imprime no shell do Linux as informações sobre as associações
que estão ativas;
• Ping: Envia uma mensagem PING a todos os nós diretamente associados,
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
109
como forma de atualizar o host cache local. Os nós respondem ao PING com
um PONG para cada nó que tenham em seu host cache;
• Associa Host Cache: O nó local tenta associar-se a todos os nós cujas informações estão contidas no host cache;
• Host Cache: Imprime no shell do Linux as informações sobre todas os nós que
estão registrados no host cache.
Há eventos que podem ocorrer em tempo de execução, que não têm interação
com a interface gráfica:
• O recebimento de uma mensagem PONG: Mesmo sendo a mensagem PING
gerada por uma chamada da interface gráfica, o recebimento da PONG não
provoca nenhuma resposta visual ao usuário;
• O recebimento de uma mensagem QUERY: Quando uma mensagem QUERY
é detectada, o nó local a processa, conferindo se possui um arquivo local que
possa satisfazê-la, e a transfere a todos os nós diretamente conectados, exceto
àquele que a enviou, caso o TTL não tenha expirado;
• O recebimento de uma mensagem QUERYHIT que carrega uma resposta que
não pertence ao nó local: Neste caso, os dados do cache de identificadores de
mensagens são utilizados para efetuar o processo do backtrack.
4.4.4
A negociação e a transferência de arquivos no Octopus
A associação estabelecida entre dois nós que fazem uso do aplicativo Octopus pode
ser utilizada para o envio simultâneo arquivos. Para que esses nós possam negociar
essa transferência, um protocolo simples, baseado no HTTP, foi especificado, e é
mostrado nesta seção.
Quanto o usuário solicita a transferência de um arquivo, uma mensagem de
requisição é enviada ao nó que compartilha este arquivo. Esta mensagem é enviada
através do fluxo cinco, de forma desordenada, e sua sintaxe é mostrada a seguir:
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
110
GET /get/<Nome do arquivo>/ OCTOPUS/1.0\r\n
\r\n
Para implementar o envio simultâneo de arquivos através de uma única associação, faz-se uso da funcionalidade dos multi-fluxos. A versão do aplicativo desenvolvido limita-se ao envio de três arquivos, de maneira full-duplex, ou seja, três deles
podem trafegar em um sentido, e três podem trafegar no sentido oposto, simultaneamente. Os fluxos de número seis, sete ou oito são utilizados para esta tarefa, e as
mensagens enviadas através deles são entregues de forma ordenada.
Assim que um nó recebe o pedido de transferência de arquivo, ele seleciona
um dos três fluxos que estiver disponı́vel, de acordo com o algoritmo mostrado
no fluxograma da Figura 4.36, e envia a seguinte mensagem, através do fluxo que
selecionou:
OCTOPUS/1.0 001 OK\r\n
Arquivo:<Nome do Arquivo>\r\n
Tamanho:<Tamanho do arquivo>\r\n
\r\n
O nó requerente, segundo as informações recebidas na mensagem de resposta do
nó servidor, sabe o nome do arquivo que deve criar, assim como o fluxo através do
qual vai receber as mensagens que compõem este arquivo. Um contador é configurado com o valor do tamanho do arquivo, obtido da mensagem de resposta, que é
decrementado a cada porção de dados que é gravada. Quando este contador atinge
o valor zero, o arquivo é fechado e o processo de transferência é encerrado.
A escolha dos fluxos é feita de maneira seqüencial, como mostrado no fluxograma
da Figura 4.36. Primeiramente é avaliada a disponibilidade do fluxo de número seis.
Caso ele esteja ocupado, o fluxo de número sete é conferido. Caso ambos estejam
ocupados, o fluxo oito é utilizado.
Há, porém, a possibilidade de não haver mais fluxos disponı́veis em um dado sentido da associação, pois três arquivos já estão sendo transmitidos simultaneamente.
Neste caso, a seguinte mensagem é enviada ao nó requerente, através do fluxo cinco:
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
111
Figura 4.36: O fluxograma do servidor de arquivos do Octopus
OCTOPUS/1.0 002 ERRO\r\n
Arquivo:<Nome do Arquivo>\r\n
Motivo:Todos os fluxos ocupados\r\n
\r\n
Há, também, a possibilidade do arquivo não ser mais encontrado para o compartilhamento, e o servidor envia a seguinte mensagem, através do fluxo cinco:
OCTOPUS/1.0 003 ERRO\r\n
Arquivo:<Nome do Arquivo>\r\n
Motivo:Arquivo n~
ao encontrado\r\n
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
112
\r\n
O fluxograma da Figura 4.37 mostra os passos para a execução da transferência
de arquivos, sob a perspectiva do cliente de arquivos, ou seja, aquele que requisita
o arquivo.
Figura 4.37: O fluxograma do cliente de arquivos do Octopus
Primeiramente o nó envia o pedido do arquivo. Caso não ocorra nenhum erro no
servidor de arquivos, causados pela indisponibilidade dos fluxos, ou por algum erro
na abertura do arquivo, o nó cliente recebe, através do fluxo selecionado pelo servidor, as informações sobre o arquivo que é enviado, podendo, desta forma, executar
4.4. A aplicação peer-to-peer de compartilhamento de arquivos Octopus
a abertura deste arquivo e atualizar o contador que auxilia no seu fechamento.
113
Capı́tulo 5
Conclusão
Os aplicativos peer-to-peer têm sido cada vez mais utilizados na Internet para diversas finalidades. Aqueles aplicativos destinados ao compartilhamento de arquivos
são os mais disseminados e empregados pelos usuários. Por este motivo, esse gênero
de sistema foi o alvo de estudo e de desenvolvimento deste trabalho.
O protocolo de transporte é uma peça fundamental no desenvolvimento desses
aplicativos e a sua escolha correta afeta diretamente o desempenho e as funcionalidades que esses sistemas oferecem. Foi mostrada, neste trabalho, a viabilidade da
utilização do SCTP como protocolo da camada de transporte para os aplicativos
peer-to-peer de compartilhamento de arquivos.
Primeiramente, foram apresentadas as definições e os gêneros de sistemas que podem ser desenvolvidos conforme o modelo peer-to-peer, assim como as caracterı́sticas
das redes que o implementam. Em seguida, foi apresentada uma classificação das
diversas arquiteturas de redes peer-to-peer e exemplos de protocolos da camada de
aplicação para aplicativos de compartilhamento de arquivos.
Uma das contribuições deste trabalho foi a proposta de uma nova classificação
das diferentes arquiteturas de redes peer-to-peer. A partir do levantamento das
caracterı́sticas relacionadas à escalabilidade, aos papéis dos nós e aos algoritmos
de roteamento, foi apresentada uma forma de classificar as arquiteturas de redes
peer-to-peer dentro de um pólo mais centralizador a outro, descentralizador.
Em seguida, foram apresentados os conceitos sobre a camada de transporte da
114
Capı́tulo 5. Conclusão
115
arquitetura Internet, envolvendo os protocolos de transporte UDP, TCP e SCTP.
Foram mostrados o formato de seus cabeçalhos e os detalhes do estabelecimento
e do encerramento de conexões, assim como dos estados de funcionamento, para
os protocolos orientados à conexão. Fez-se também um comparativo entre os três
protocolos, com ênfase no TCP e no SCTP.
Os aplicativos peer-to-peer para compartilhamento de arquivos que apresentam
arquiteturas descentralizadas precisam trocar mensagens entre si para encontrar
arquivos entre os diversos computadores. Essas mensagens são caracterizadas por
possuı́rem tamanhos variáveis e por não restringirem a ordenação de entrega.
A natureza do TCP, que restringe a ordem da entrega dos dados à camada de
aplicação, pode fazer com que a transmissão de mensagens entre os nós de uma rede
peer-to-peer tenha o seu desempenho degradado, causados pelo problema do HOL.
O protocolo SCTP possui a funcionalidade da entrega desordenada de mensagens,
que pode melhorar o desempenho desta tarefa.
Para mostrar esse ganho, foi utilizado um ambiente de simulação composto por
dois aplicativos que foram desenvolvidos para simular o trânsito de mensagens entre
dois nós de uma rede overlay, tomando como base a dinâmica de uma rede Gnutella
real, e um gerador de perda e atraso de pacotes, implementado por meio do aplicativo NIST Net, que emula o comportamento que a Internet provoca nas mensagens
transmitidas.
Comprovou-se, então, que o SCTP tem um melhor desempenho em relação ao
TCP, em redes com altas taxas de perda de pacotes, proporcionando um menor
tempo médio de transmissão de mensagens nó-a-nó. Isso se deve à natureza ordenada do TCP que, ao restringir a ordem de entrega das mensagens, degrada o
desempenho geral, fato que não ocorre quando o SCTP utiliza a entrega desordenada de mensagens. A utilização dos multi-fluxos nesta simulação teve um caráter
ilustrativo, para mostrar que o HOL intra-fluxo pode prejudicar de maneira sutil o
desempenho da transmissão de mensagens.
Levando em consideração que muitos nós participantes nas redes peer-to-peer,
para compartilhamento de arquivos, estão inter-conectados por meios que podem
Capı́tulo 5. Conclusão
116
apresentar taxas de perdas de pacotes variadas e que, muitas vezes, estão em regiões
geográficas distintas, a utilização da entrega desordenada de mensagens, oferecida
pelo SCTP, passa a ser uma opção em potencial para a transmissão das mensagens
de busca, satisfazendo as necessidades dos aplicativos peer-to-peer.
Assim sendo, uma outra contribuição muito importante deste trabalho foi a
análise comparativa de desempenho entre o SCTP e o TCP como protocolos da
camada de transporte para aplicativos peer-to-peer de compartilhamento de arquivos. Com a utilização do SCTP, o tempo médio de transmissão de mensagens
nó-a-nó é reduzido e, com isso, o tempo médio para a chegada das respostas da fase
de consulta é diretamente beneficiado.
Para comprovar a viabilidade da utilização do SCTP, em vez do TCP, em um
aplicativo peer-to-peer de compartilhamento de arquivos, o protótipo de aplicação
Octopus foi desenvolvido. Este aplicativo utiliza os conceitos das redes peer-to-peer,
a partir do uso do protocolo Gnutella na camada de aplicação, e do SCTP, que é
utilizado na camada de transporte.
O objetivo deste desenvolvimento é, por meio deste protótipo, mostrar como
algumas funcionalidades do SCTP podem ser aproveitadas em um aplicativo peerto-peer de compartilhamento de arquivos. As funcionalidades exploradas foram
a orientação à mensagem, os multi-fluxos e a entrega desordenada de mensagens.
Comparativamente ao TCP, é de se esperar um melhor desempenho.
Foi implementada no Octopus uma reestruturação do cabeçalho das mensagens
Gnutella para ser utilizado com o SCTP. Os delimitadores de mensagem, implementados pelos campos de tamanho de mensagem na versão Gnutella original, podem
ser removidos, pois o SCTP oferece a orientação à mensagem. Os identificadores
dos tipos de payload também podem ser removidos, pois os identificadores de fluxo
podem ser utilizados para essa tarefa, sem necessariamente fazer uso da funcionalidade primária da transmissão de mensagem em um mesmo fluxo, que faz com que
a ordenação seja mantida.
A funcionalidade da entrega de mensagens desordenadas foi utilizada para o
tráfego de mensagens Gnutella. A principal motivação para este uso é a flexibilidade
Capı́tulo 5. Conclusão
117
na ordenação de entrega dessas mensagens. O ganho de desempenho no emprego
desta funcionalidade foi mostrado na análise comparatira entre o TCP e o SCTP.
A funcionalidade dos multi-fluxos foi utilizada para realizar a transmissão simultânea de arquivos, através de uma única associação SCTP, que foi também
utilizada para transmitir as mensagens Gnutella. Um protocolo, baseado no HTTP,
foi especificado para a negociação da transferência dos arquivos entre os nós.
Este desenvolvimento teve como contribuição a apresentação da reestruturação
do protocolo Gnutella que faz uso da orientação à mensagem e dos identificadores de fluxo, e a apresentação de um protótipo de aplicativo que mostra como as
funcionalidades do SCTP podem ser utilizadas em um aplicativo peer-to-peer de
compartilhamento de arquivos. Tem como contribuição, ainda, a especificação de
um protocolo simplificado para a negociação da transferência de arquivos.
O presente trabalho teve também como contribuição a publicação de três artigos. Dois deles foram apresentados em congressos [9, 10] e referem-se ao uso do
SCTP em aplicativos peer-to-peer. O outro refere-se à proposta de classificação das
arquiteturas de redes peer-to-peer, e foi aceito em um periódico [11].
Como sugestões para a melhoria deste trabalho, tem-se a execução de testes
exaustivos para a obtenção de estatı́sticas mais precisas referentes aos tamanhos e
à freqüência de transmissão das mensagens Gnutella, por meio da implementação
de um monitor de rede. Com isso, novas execuções de testes comparativos entre o
SCTP e o TCP podem ser feitas para validar os experimentos mostrados.
Uma sugestão para a melhoria do Octopus seria a atualização da interface do
usuário de modo a apresentar outras funcionalidades, tais como a exibição das informações do cache de associações e do host cache diretamente na interface gráfica,
além da implementação da mensagem do tipo PUSH. Outra melhoria seria a implementação de um proxy que integra a rede peer-to-peer do Octopus àquela já
consolidada pelo protocolo Gnutella tradicional.
Como trabalho futuro, é sugerida a especificação e implementação de um protocolo de rede peer-to-peer descentralizada baseada em ultranós que faz uso do SCTP,
tendo como referência as funcionalidades exploradas neste trabalho, e que, ainda,
Capı́tulo 5. Conclusão
118
faz uso da funcionalidade do multi-homing no nı́vel dos ultranós, de modo a alcançar
um melhor desempenho e a tolerância a falhas por meio da redundância de enlace.
Sugere-se também a utilização do SCTP sob as redes de arquitetura estruturada.
Por fim, sugere-se a implementação de um aplicativo completo que integra, não
só o compartilhamento de arquivos, mas também a comunicação entre usuários
por meio de texto, áudio e vı́deo, em que os multi-fluxos possam ser amplamente
explorados.
Referências Bibliográficas
[1] Backx, P., Wauters, T., Dhoedt, B., and Demeester, P. A comparison of peer-to-peer architectures. In Eurescom Summit (Heidelberg, Germany,
2002), Broadband Communication Networks Group.
[2] Brookshier, D., Govoni, D., Krishnan, N., and Soto, J. C. JXTA:
Java P2P Programming. Sams, USA, Março 2002. 0672323664.
[3] Carson, M., and Santay, D. NIST Net - A Linux-based Network Emulation
Tool. http://snad.ncsl.nist.gov/itg/nistnet/ acessado em 12/11/2005.
[4] Center, S. L. A. Iepm. In Internet End-to-end Performance Monitoring. University of Stanford, 2006. http://www-iepm.slac.stanford.edu/ em 09/02/2006.
[5] Clarke, I., Sandberg, O., Wiley, B., and Hong, T. W. Freenet: A
distributed anonymous information storage and retrieval system. In Proc. of
the Workshop on Design Issues in Anonymity and Unobservability, (CA, USA,
Julho 2000), pp. 311–320.
[6] Coene, L. RFC 3257 - Stream Control Transmission Protocol Applicability
Statement, 2002. http://www.sctp.org acessado em 06/08/2005.
[7] Crespo, A., and Garcia-Molina, H. Routing indices for peer-to-peer
systems. In Proceedings of the 22th International Conference on Distributed
Computing Systems (Washington, DC, 2002), 0-7695-1585-1, IEEE Computer
Society, pp. 23–34.
[8] Daswani, S., and Fisk, A. A. Gnutella udp extension for scalable searches
- guess v0.1. Tech. rep., Gnutella Development Forum, Agosto 2002.
119
Referências Bibliográficas
120
[9] do Carmo, G. S. B., and Barbar, J. S. Peer-to-peer usando sctp. In
CONTECSI - Congresso Internacional de Gestão de Tecnologia e Sistemas de
Informação (São Paulo, SP, Brasil, 2004), TECSI/FEA/USP.
[10] do Carmo, G. S. B., and Barbar, J. S. A utilização do protocolo sctp em
uma arquitura peer-to-peer. In WebMedia/LA-Web - Simpósio Brasileiro de
Sistemas Multimı́dia e Web (Ribeirão Preto, SP, Brasil, 2004), vol. 2, pp. 265–
267.
[11] do Carmo, G. S. B., Barbar, J. S., and Coelho, F. B. Uma nova classificação para as arquiteturas de redes peer-to-peer. Revista do IEEE América
Latina (2006). Artigo aceito com previsão de publicação em Julho de 2006.
[12] et al, S. Internet draft. In Sockets API Extensions for Stream Control Transmission Protocol (Setembro 2004), Network Working Group, The Internet Society.
[13] et al, S. Internet-draft. In SCTP Implementer’s Guide (2004), Network
Working Group, The Internet Society.
[14] Fanning, S. Napster. http://www.napster.com acessado em 25/06/2005.
[15] Ge, Z., Figueiredo, D. R., Jaiswal, S., Kurose, J., and Towsley,
D. Modeling peer-to-peer file sharing systems. In Proceedings of INFOCOM
(San Francisco, California, USA, Abril 2003), Department os Computer Science,
University of Massachussets, Amherst.
[16] Hargreaves, T. The fasttrack protocol. http://cvs.berlios.de/ - acessado em
24/10/1980, Julho 2004.
[17] Kirk, P., Ed. Gnutella a protocol for a revolution, RFC Gnutella 0.4 (2003).
http://rfc-gnutella.sourceforge.net acessado em 08/12/2005.
[18] Kirk, P., Ed. Gnutella a protocol for a revolution, RFC Gnutella 0.6 (2003).
http://rfc-gnutella.sourceforge.net acessado em 08/12/2005.
Referências Bibliográficas
121
[19] Kulbak, Y., and Bickson, D. Emule protocol guide. School of Computer
Science and Engineering, The Hebrew University of Jerusalem, Israel, Janeiro
2005.
[20] Kurose, J. F., and Ross, K. W. Rede de Computadores e a Internet.
Addison Wesley, São Paulo, 2003. 8588639181.
[21] Lv, Q., Cao, P., Cohen, E., Li, K., and Shenker, S. Search and replication in unstructured peer-to-peer networks. In Proceedings of the 16th International Conference on Supercomputing (New York, USA, 2002), A. Press, Ed.,
1-58113-483-5, pp. 82–95.
[22] Markatos, E. P. Tracing large-scale peer to peer system: an hour in the life
of gnutella. In 2nd IEEE/ACM Int. Symp. on Cluster Computing and the Grid
(2002), IEEE Press, Piscataway, NJ, USA.
[23] Mathis,
M.,
Mahdavi,
J.,
Floyd,
S.,
and
Romanow,
A.
RFC 2018 - TCP Selective Acknowledgement Options, Outubro 1996.
http://www.ietf.org/rfc/rfc2018.txt acessado em 06/08/2005.
[24] Maumounkov, P., and Mazières, D. Kademlia: A peer-to-peer information system based on the xor metric. In Proc. of the 1st Intl. Workshop on
Peer-to-Peer Systems (2004).
[25] McCaleb, J. eDonkeyProtocol, 2005. http://www.edonkey.com acessado em
24/08/2005.
[26] Menascé, D. A., and Kanchanapalli, L. Probabilistic scalable p2p resource location services. In ACM SIGMETRICS Performance Evaluation Review (New York, 2002), 0163-5999, ACM Press, pp. 48–58.
[27] Merkur.
Emule project.
http://www.emule-project.net acessado em
25/05/2005.
[28] Metrix, J. M. Global napster usage plummets, but new file-sharing alternatives gaining ground. Software Industry Report, 14 (Julho 2001), 5.
Referências Bibliográficas
122
[29] Milojicic, D. S., Kalogeraki, V., Lukose, R., Nagaraja, K.,
Pruyne, J., Richard, B., Rollins, S., and Xu, Z. Peer-to-peer Computing. HP Laboratories, 2002.
[30] Ong, L., and Yoakum, J. RFC 3286 - An Introduction to the Stream Control Transmission Protocol, Outubro 2002. http://www.sctp.org acessado em
06/08/2005.
[31] Oram, A., Ed. Peer-to-peer, 1 ed. O’Reilly Media, Março 2001. 059600110X.
[32] Postel,
J.
RFC 768 - User Datagram Protocol,
Agosto 1980.
http://www.faqs.org/rfcs/rfc768.txt acessado em 06/08/2005.
[33] Rajamani, R., Kumar, S., and Gupta, N. Sctp versus tcp: Comparing
the performance of transport protocols for web traffic. Tech. rep., Computer
Sciences Department, University of Wisconsin-Madison, Maio 2002.
[34] Ratnasamy, S., Francis, P., Handley, M., Karp, R., and Shenker,
S. A scalable content-addressable network. In Proceedings of the 2001 Conference on Applications, Technologies, architectures, and Protocols for Computer
Communications (New York, 2001), 1-58113-411-8, ACM Press, pp. 161–172.
[35] Ritter, J. Why gnutella can’t scale. no, really. Tech. rep., Darkridge, Fevereiro
2001. www.darkridge.com/ jpr5/doc/gnutella.html acessado em 15/01/2006.
[36] Rocha, J., Domingues, M., Callado, A., Souto, E., Silvestre, G.,
Kamienski, C., and Sadok, D. Peer-to-peer: Computação colaborativa na
internet. Tech. rep., P2P Work Group of the Brazilian Research Network, 2004.
[37] Rowstron, A., and Druschel, P. Pastry: Scalable, distributed object
location and routing for large-scale peer-to-peer systems. In Proceedings of the
18th IFIP ACM International Conference on Distributed Systems Platforms
(2001), vol. 2218, Heidelberg, Alemanha, pp. 329–350.
[38] Schollmeier, R. A definition of peer-to-peer networking for the classification
of peer-to-peer architectures and applications. In Proceedings of the First In-
Referências Bibliográficas
123
ternational Conference on Peer-to-Peer Computing (2002), 0-7695-1503-7/02,
IEEE Internet Computing.
[39] Singla, A., and Rohrs, C. Ultrapeers: Another step towards gnutella
scalability. Tech. rep., Lime Wire LLC, 2002. http://limewire.com.
[40] Sourceforge.net.
LKSCTP Project - Linux Implementation for SCTP,
2005. http://lksctp.sourceforge.net acessado em 14/08/2005.
[41] Stewart, R. R., and Xie, Q. RFC 2960 - Stream Control Transmission
Protocol, 2000. http://www.sctp.org acessado em 06/08/2005.
[42] Stewart, R. R., and Xie, Q. Stream Control Transmission Protocol (SCTP)
- A Reference Guide. Addison-Wesley, 2002. 0201721864.
[43] Stoica, I., Morris, R., Liben-Nowell, D., Karger, D. R., Kaashoek,
M. F., Dabek, F., and Balakrishnan, H. Chord: A scalable peer-topeer lookup protocol for internet applications. In IEEE/ACM Transactions on
Networking (2003), vol. 11 of 1063-6692, IEEE Press, Piscataway, NJ, USA,
pp. 17 – 32.
[44] Strokes, M.
Gnutella2 specifications.
In Gnutella2 Developer Network.
Gnutella Development Network, 2003. http://www.gnutella2.com acessado em
26/01/2006.
[45] Tanembaum, A. S. Redes de Computadores. Editora Campus, São Paulo,
2003. 8535211853.
[46] Team, D. S. Peer-to-peer Application Development. Hungry Minds, 2002.
[47] Tsoumakos, D., and Roussopoulos, N. Adaptive probabilistic search for
peer-to-peer networks. In Proc. 3rd International Conf. Peer-to-Peer Computing (P2P 2003) (2003), IEEE CS Press, pp. 102–110.
[48] Tsoumakos, D., and Roussopoulos, N. A comparison of peer-to-peer
search methods. In International Workshop on the Web and Databases (Junho
2003).
Referências Bibliográficas
124
[49] V. Kalogeraki, D. G. e. D. Z.-Y. A local search mechanism for peer-topeer networks. In Proceedings of the 11th International Conference on Information and Knowledge Management (New York, 2002), 1-58113-492-4, ACM
Press, pp. 300–307.
[50] Yang, B., and Garcia-Molina, H. Comparing hybrid peer-to-peer systems. In Proceedings of the 27th Very Large Data Bases Conference, Roma,
Itália (2001), 1-55860-804-4, ACM Press, Morgan Kaufmann Publishers Inc,
San Francisco, CA, USA, pp. 561 – 570.
[51] Yang, B., and Garcia-Molina, H.
Improving search in peer-to-peer
networks. In Proceedings of the 22nd International Conference on Distributed
Computing Systems (Washington, DC, 2002), 0-7695-1585-1, IEEE Computer
Society, pp. 5–14.
[52] Yang, B., and Garcia-Molina, H. Designing a super-peer network. In
Proceedings of the 19th International Conference on Data Engineering (ICDE),
Bangalore, India (Março 2003), Computer Science Department, Stanford University.
[53] Zhao, B. Y., Huang, L., Stribling, J., Rhea, S. C., Joseph, A. D.,
and Kubiatowicz, J. D. Tapestry: A resilient global-scale overlay for service deployment. IEEE Journal on Selected Areas in Communications (Janeiro
2004).
Apêndice A
Códigos fonte dos aplicativos do ambiente de simulação
Os códigos fonte apresentados neste apêndice referem-se aos aplicativos “Gerador e
receptor de pacotes”, ou “Nó 1”, e Echo server, ou “Nó 2”, descritos no Capı́tulo 4.
/****************************************************************
GeraPacotes.c - Aplicativo gerador e receptor de pacotes
Gustavo Salvadori Baptista do Carmo
10 de Fevereiro de 2006
[email protected]
****************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
<sys/types.h>
<sys/socket.h>
<netinet/in.h>
<netinet/tcp.h>
<netinet/sctp.h>
<arpa/inet.h>
<errno.h>
<netdb.h>
<stdio.h>
<stdlib.h>
<getopt.h>
<string.h>
<unistd.h>
<time.h>
<sys/time.h>
<pthread.h>
"./Misc.c"
//--------------------------------------------------------------//Estruturas de par^
ametros para funç~
oes - threads
//--------------------------------------------------------------struct parametroT1
{
int sock_fd;
struct sockaddr_in endereco;
};
//--------------------------------------------------------------//Protótipos
//--------------------------------------------------------------//Funç~
oes para controle geral e exibiç~
ao de resultados
void uso (void);
void cuidaDoLance (void);
125
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
void parser (int argc, char **argv);
void calculaTempo (void);
void geraScript (void);
//Funcoes para criaç~
ao de relacionamentos
void criaConexaoTCP (void);
void criaAssociacaoSCTP (void);
//Funç~
oes para envio e recebimento de mensagens
void *enviaMensagensTCP (struct parametroT1 *);
void *enviaMensagensSCTP (struct parametroT1 *);
void *recebeMensagensTCP (struct parametroT1 *);
void *recebeMensagensSCTP (struct parametroT1 *);
//Funç~
oes especificas para o TCP
int writeN (int fd, const void *buffer, int n);
int readN (int fd, void *buffer, int n);
//--------------------------------------------------------------//Variáveis globais
//--------------------------------------------------------------int fluxos = 0;
int fila = 0;
char *enderecoLocal = NULL;
char *enderecoRemoto = NULL;
int portaLocal = 0;
int portaRemota = 0;
int tamanhoBuffer = 0;
int numeroMensagensEnviar = 30;
int tipoDeProtocolo = 0;
int tamanhoMensagem = 20;
int mensagemDesordenada = 0;
int intervaloEntreMensagens = 0;
int verbose = 1;
FILE *arquivo;
FILE *arquivo2;
//Representaç~
ao de mensagens Gnutella
extern char *gnutella[5];
//estrutura enviada via socket
struct tipao
{
int id;
char sendline[100];
};
//Estruturas e variáveis de auxı́lio no cálculo de tempo
struct paraContagem
{
int id;
int tamanho;
struct timeval cronometroIda;
struct timeval cronometroVolta;
};
struct timeval tempoGlobalInicial;
struct timeval tempoGlobalFinal;
int quantidadeEnviadaGlobal = 0;
struct paraContagem matriz[NUMERO_MAXIMO_MENSAGENS];
//Miscel^
ania
double latenciaMedia;
double vazaoMedia;
double vazaoGeral = 0;
double latenciaGeral = 0;
double latenciaTotal;
double vazaoTotal;
double latenciaIndividual[NUMERO_MAXIMO_MENSAGENS];
double vazaoIndividual[NUMERO_MAXIMO_MENSAGENS];
126
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
//--------------------------------------------------------------//Programa principal
//--------------------------------------------------------------int
main (int argc, char **argv)
{
parser (argc, argv);
cuidaDoLance ();
int i;
//Executa o programa por um número definido de vezes
if (tipoDeProtocolo == 1)
for (i = 1; i <= verbose; i++)
{
//Protocolo escolhido foi o TCP
criaConexaoTCP ();
}
else
for (i = 1; i <= verbose; i++)
{
//Protocolo escolhido foi o SCTP
criaAssociacaoSCTP ();
}
return 0;
}
//fim de main
//--------------------------------------------------------------//Funç~
ao para preencher os valores da linha de comando
//--------------------------------------------------------------void
parser (int argc, char **argv)
{
int getoptC;
int bufferAux;
int numAux;
int tamanhoAux;
int desordem;
int intervalo;
int verb;
while (argc)
{
getoptC = getopt (argc, argv, "H:P:h:p:t:x:f:b:m:s:d:u:v:");
if (getoptC < 0)
break;
switch (getoptC)
{
case ’H’:
enderecoLocal = optarg;
break;
case ’P’:
portaLocal = atoi (optarg);
break;
case ’h’:
enderecoRemoto = optarg;
break;
case ’p’:
portaRemota = atoi (optarg);
break;
case ’t’:
tipoDeProtocolo = atoi (optarg);
break;
127
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
case ’x’:
fluxos = atoi (optarg);
break;
case ’f’:
fila = atoi (optarg);
break;
case ’b’:
bufferAux = atoi (optarg);
if ((bufferAux > 0) && (bufferAux < 1001))
tamanhoBuffer = bufferAux;
break;
case ’m’:
numAux = atoi (optarg);
if ((numAux > 0) && (numAux < 101))
numeroMensagensEnviar = numAux;
break;
case ’s’:
tamanhoAux = atoi (optarg);
if ((tamanhoAux > 1) && (tamanhoAux < 101))
tamanhoMensagem = tamanhoAux;
break;
case ’d’:
desordem = atoi (optarg);
if (desordem == 1)
mensagemDesordenada = MSG_UNORDERED;
break;
case ’u’:
intervalo = atoi (optarg);
if ((intervalo > 0) && (intervalo < 1001))
intervaloEntreMensagens = intervalo;
break;
case ’v’:
verb = atoi (optarg);
if ((verb > 1) && (verb < 101))
verbose = verb;
break;
default:
break;
}
}
}
//fim do switch
//fim while argc
//fim de parser
//--------------------------------------------------------------//Funç~
ao para verificaç~
ao dos par^
ametros de entrada
//--------------------------------------------------------------void
cuidaDoLance (void)
{
if ((enderecoLocal == NULL) ||
(inet_aton (enderecoLocal, NULL) == 0))
{
printf ("\nFaltou colocar o endereço local\n");
uso ();
exit (0);
}
if (portaLocal == 0)
{
printf ("\nFaltou colocar a porta local\n");
uso ();
exit (0);
}
128
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
if ((enderecoRemoto == NULL) ||
(inet_aton (enderecoRemoto, NULL) == 0))
{
printf ("\nFaltou colocar o endereço remoto\n");
uso ();
exit (0);
}
if (portaRemota == 0)
{
printf ("\nFaltou colocar a porta remota\n");
uso ();
exit (0);
}
if ((tipoDeProtocolo != 1) && (tipoDeProtocolo != 2))
{
printf ("\nEscolha de protocolo incorreta\n");
printf ("\nTCP=1 e SCTP=2\n");
uso ();
exit (0);
}
if ((fluxos < 1) && (tipoDeProtocolo == 2))
{
printf ("\nFluxo setado para 1\n");
fluxos = 1;
}
if ((fila < 1) && (tipoDeProtocolo == 2))
{
printf ("\nFila setada para 1\n");
fila = 1;
}
printf
printf
printf
printf
printf
("\n");
("Endereco
("Porta do
("Endereco
("Porta do
do host local:%s\n", enderecoLocal);
host local:%d\n", portaLocal);
do host remoto:%s\n", enderecoRemoto);
host remoto:%d\n", portaRemota);
if ((tipoDeProtocolo == 1))
printf ("Protocolo de transporte: TCP\n");
else
{
//Caso do SCTP
printf ("Protocolo de transporte: STCP\n");
printf ("Numero de fluxos:%d\n", fluxos);
printf ("Fila de mensagens em cada fluxo:%d\n", fila);
if (tamanhoBuffer == 0)
printf ("Buffer de envio e recebimento setado para padrao\n");
else
printf ("Buffer de envio e recebimento (kBytes):%d\n",
tamanhoBuffer);
if (mensagemDesordenada == 0)
printf ("Entrega das mensagens: ordenada\n");
else
printf ("Entrega das mensagens: desordenada\n");
}
//Fim do caso SCTP
printf ("Numero de mensagens a enviar:%d\n",
numeroMensagensEnviar);
printf ("Tamanho das mensagens (bytes):%d\n", tamanhoMensagem);
printf ("Intervalo entre mensagens (milisegundos):%d\n",
intervaloEntreMensagens);
printf ("\n");
}
//fim cuida do lance
129
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
//--------------------------------------------------------------//Funç~
ao para imprimir o help
//--------------------------------------------------------------void
uso (void)
{
printf ("\n");
printf ("Uso: ./GeraPacotes -H endLoc -P ptLoc -h endRem -p ptRem -t tipProt\n");
printf ("
-x numFlux -f fila -b buffer -m mensagens -s size\n");
printf ("
-d ordem -u tempo -v verbose\n\n");
printf ("
-H endLoc
- endereço do host local\n");
printf ("
-P ptLoc
- porta do host local\n");
printf ("
-h endRem
- endereço do host remoto\n");
printf ("
-p ptRem
- porta do host remoto\n");
printf ("
-t tipProt
- protocolo de transporte - TCP=1 - SCTP=2\n");
printf ("
-x numFlux
- número de fluxos - min 1 - max 300 - SCTP\n");
printf ("
-f fila
- fila de mensagens em cada fluxo - min 1 - max 5 - SCTP\n");
printf ("
-b buffer
- buffer de envio e recebimento (kBytes) ");
printf ("- min 1 - max 1000 - SCTP\n");
printf ("
-m mensagens
- número de mensagens a enviar - min 1 - max 100\n");
printf ("
-s size
- tamanho da mensagem (bytes) - min 2 - max 100\n");
printf ("
-d ordem
- 1 - envia as mensagens de maneira desordenada\n");
printf ("
-u tempo
- tempo entre mensagens - milisegundos - padr~
ao 50\n");
printf ("
-v verbose
- verbose 1 ou 2\n");
printf ("\n");
}
//fim de uso
//--------------------------------------------------------------//--------------------------------------------------------------void
criaAssociacaoSCTP (void)
{
int sock_fd;
int erro;
struct sockaddr_in servaddr, meuaddr, enderecoEnviante;
struct sctp_event_subscribe evnts;
//Criaç~
ao do socket
sock_fd = socket (AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
if (sock_fd < 0)
{
printf ("Criaç~
ao do socket falhou\n");
exit (0);
}
//Configurando os endereços
bzero (&servaddr, sizeof (servaddr));
servaddr.sin_family = AF_INET;
inet_aton (enderecoRemoto, &servaddr.sin_addr);
servaddr.sin_port = htons (portaRemota);
bzero (&meuaddr, sizeof (meuaddr));
meuaddr.sin_family = AF_INET;
inet_aton (enderecoLocal, &meuaddr.sin_addr);
meuaddr.sin_port = htons (portaLocal);
//Subscrevendo as notificaç~
oes de eventos
bzero (&evnts, sizeof (evnts));
evnts.sctp_data_io_event = 1;
erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_EVENTS,
&evnts, sizeof (evnts));
if (erro != 0)
{
printf ("Erro a setar opcao do socket de initmessage\n");
exit (0);
}
130
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
int desliga = 1;
//Desligando o Nagle
erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_NODELAY,
&desliga, sizeof (desliga));
if (erro == (-1))
{
printf ("Erro ao desligar o Nagle do SCTP\n");
exit (0);
}
struct sctp_initmsg initmsg;
//Configurando o número de fluxos
initmsg.sinit_num_ostreams = fluxos;
initmsg.sinit_max_instreams = fluxos;
initmsg.sinit_max_attempts = 3;
initmsg.sinit_max_init_timeo = 30;
erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_INITMSG,
(char *) &initmsg,
sizeof (struct sctp_initmsg));
if (erro != 0)
{
printf ("Erro a setar opç~
ao do socket de initmessage\n");
exit (0);
}
//Configurando o tamanho do buffer
tamanhoBuffer = tamanhoBuffer * 1000;
if (tamanhoBuffer != 0)
{
erro = setsockopt (sock_fd, IPPROTO_SCTP, SO_RCVBUF,
(char *) &tamanhoBuffer,
sizeof (tamanhoBuffer));
if (erro != 0)
{
printf ("Erro a setar opç~
ao do socket de receive buffer\n");
exit (0);
}
erro = setsockopt (sock_fd, IPPROTO_SCTP,
SO_SNDBUF, (char *) &tamanhoBuffer,
sizeof (tamanhoBuffer));
if (erro != 0)
{
printf ("Erro a setar opç~
ao do socket de send buffer\n");
exit (0);
}
}
//fim tamanhobuffer!=0
//Efetuando o bind
erro = bind (sock_fd, (struct sockaddr *) &meuaddr,
sizeof (meuaddr));
if (erro != 0)
{
printf ("Erro ao fazer o bind\n");
exit (0);
}
//esse trecho é para n~
ao contar o tempo do
//estabelecimento de associaç~
ao do SCTP
struct sctp_sndrcvinfo sri;
struct tipao mensagem;
int quantidadeEnviada;
131
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
int quantidadeRecebida;
int msg_flags;
bzero (&sri, sizeof (sri));
sri.sinfo_stream = 0;
int out_sz;
socklen_t len;
len = sizeof (struct sockaddr_in);
char *sendline;
sendline = malloc (25);
bzero (mensagem.sendline, sizeof (100));
bzero (sendline, sizeof (25));
sprintf (mensagem.sendline, "Conectando");
out_sz = strlen (mensagem.sendline) + sizeof (int);
mensagem.id = 1000;
quantidadeEnviada =
sctp_sendmsg (sock_fd, "Conectando", out_sz,
(struct sockaddr *) &servaddr,
sizeof (struct sockaddr), 0,
mensagemDesordenada, sri.sinfo_stream, 0,
0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
}
quantidadeRecebida =
sctp_recvmsg (sock_fd, sendline, 25,
(struct sockaddr *) &enderecoEnviante,
&len, &sri, &msg_flags);
if (quantidadeRecebida == (-1))
{
printf ("Falha ao receber a mensagem\n");
}
//fim do trecho de estabelecimento de associaç~
ao SCTP
struct parametroT1 valores1;
struct parametroT1 valores2;
//Preenchendo as estruturas de par^
ametros para as threads
valores1.sock_fd = sock_fd;
valores1.endereco = enderecoEnviante;
valores2.sock_fd = sock_fd;
valores2.endereco = servaddr;
pthread_t p_thread_envia;
pthread_t p_thread_recebe;
//Abre a thread que gera e envia mensagens
pthread_create (&p_thread_envia, NULL,
(void *) enviaMensagensSCTP, &valores2);
//Abre a thread que recebe mensagens
pthread_create (&p_thread_recebe, NULL,
(void *) recebeMensagensSCTP, &valores1);
//Aguarda os términos das duas threads
pthread_join (p_thread_envia, NULL);
pthread_join (p_thread_recebe, NULL);
//Calcula e exibe resultados
calculaTempo ();
geraScript ();
132
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
return;
}
//fim de cria associaç~
ao SCTP
//--------------------------------------------------------------//--------------------------------------------------------------void
geraScript (void)
{
printf ("\n");
printf ("#------------------------------------------------------------------\n");
printf ("#MANUAL DE LEITURA\n");
printf ("#------------------------------------------------------------------\n");
printf
printf
printf
printf
printf
printf
printf
printf
printf
("\n");
("#-lat^
encia média:
("#-vaz~
ao média:
("\n");
("#-lat^
encia geral:
("#-vaz~
ao geral:
("\n");
("#-lat^
encia total:
("#-vaz~
ao total:
printf
printf
printf
printf
("\n");
("#------------------------------------------------------------------\n");
("#INFORMAC~
OES GERAIS\n");
("#------------------------------------------------------------------\n");
printf
printf
printf
printf
printf
("\n");
("#Endereço
("#Porta do
("#Endereço
("#Porta do
a média aritmética de todas as lat^
encias individuais\n");
a média aritmética de todas as vaz~
oes individuais\n");
a soma de todas as lat^
encias, n~
ao considerando os intervalos\n");
a soma de todas as vazoes, n~
ao considerando os intervalos\n");
desde a primeira mensagem até a ultima recebida\n");
desde da primeira mensagem enviada ateh a ultima recebida\n");
do host local:%s\n", enderecoLocal);
host local:%d\n", portaLocal);
do host remoto:%s\n", enderecoRemoto);
host remoto:%d\n", portaRemota);
if ((tipoDeProtocolo == 1))
printf ("#Protocolo de transporte: TCP\n");
else
{
//Caso do SCTP
printf ("#Protocolo de transporte: STCP\n");
printf ("#Número de fluxos:%d\n", fluxos);
printf ("#Fila de mensagens em cada fluxo:%d\n", fila);
if (tamanhoBuffer == 0)
printf ("#Buffer de envio e recebimento setado para padr~
ao\n");
else
printf ("#Buffer de envio e recebimento (kBytes):%d\n",
tamanhoBuffer);
}
//Fim do caso SCTP
printf ("#Número de mensagens a enviar:%d\n",
numeroMensagensEnviar);
printf ("#Tamanho das mensagens (bytes):%d\n", tamanhoMensagem);
printf ("\n");
printf ("#Round Trip Time:\n");
printf ("#Taxa de perda de pacotes:\n");
printf
printf
printf
printf
printf
("\n");
("#------------------------------------------------------------------\n");
("#IdMsg : Lat(s) : Vaz(b/s) : Lat Media(s) : Vaz~
ao Média(b/s) :\n");
("#------------------------------------------------------------------\n");
("\n");
printf
printf
printf
printf
("#------------------------------------------------------------------\n");
("#Lat Geral(s) : Vaz~
ao Geral(b/s) : Lat Total(s) : Vaz Total(b/s)\n");
("#------------------------------------------------------------------\n");
("\n");
int i;
133
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
for (i = 0; i < numeroMensagensEnviar; i++)
{
printf ("%d
%0.6f
%0.6f
%0.6f
%0.6f
%0.6f
%0.6f
%0.6f
%0.6f\n", i, latenciaIndividual[i], vazaoIndividual[i],
latenciaMedia, vazaoMedia, latenciaGeral, vazaoGeral, latenciaTotal, vazaoTotal);
}
}
//fim de geraScript
//--------------------------------------------------------------//--------------------------------------------------------------void
calculaTempo (void)
{
int i, j;
double resultadoTempoSec;
double resultadoTempoMicro;
double resultadoFinal;
double vazao;
printf
printf
printf
printf
("\n");
("-----------------------------------------------------\n");
("CÁLCULO DE LAT^
ENCIA E VAZ~
AO POR MENSAGEM ENVIADA\n");
("-----------------------------------------------------\n");
for (i = 0; i < numeroMensagensEnviar; i++)
{
for (j = 0; j < numeroMensagensEnviar; j++)
{
if (i == matriz[j].id)
{
resultadoTempoSec =
(matriz[j].cronometroVolta.tv_sec) (matriz[j].cronometroIda.tv_sec);
resultadoTempoMicro =
(double) (matriz[j].cronometroVolta.
tv_usec) (double) (matriz[j].cronometroIda.tv_usec);
resultadoFinal =
resultadoTempoSec +
((double) resultadoTempoMicro /
(double) 1000000);
printf ("\n");
printf ("Lat^
encia para mensagem %d = %0.6f segundos\n", i, resultadoFinal);
latenciaIndividual[i] = resultadoFinal;
vazao = (double) (matriz[j].tamanho) /
(double) resultadoFinal;
printf ("Vaz~
ao para mensagem %d = %0.6f bytes/seg\n",
i, vazao);
printf ("Quantidade de dados enviado: %d\n",
matriz[j].tamanho);
vazaoIndividual[i] = vazao;
}
}
}
j = numeroMensagensEnviar;
//fim if
//fim for 2
//fim for 1
//Cálculo da lat^
encia e vaz~
ao globais
resultadoTempoSec =
(tempoGlobalFinal.tv_sec) - (tempoGlobalInicial.tv_sec);
134
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
resultadoTempoMicro =
(double) (tempoGlobalFinal.tv_usec) (double) (tempoGlobalInicial.tv_usec);
resultadoFinal =
resultadoTempoSec +
((double) resultadoTempoMicro / (double) 1000000);
printf
printf
printf
printf
("\n");
("-----------------------------------------------------\n");
("\n");
("Lat^
encia Total Final = %0.6f segundos\n",
resultadoFinal);
latenciaTotal = resultadoFinal;
vazao = (double) (quantidadeEnviadaGlobal) /
(double) resultadoFinal;
printf ("Quantidade de dados enviados: %d bytes\n",
quantidadeEnviadaGlobal);
printf ("Vazao Total Final = %0.6f bytes/seg\n\n", vazao);
vazaoTotal = vazao;
for (i = 0; i < numeroMensagensEnviar; i++)
{
latenciaGeral = latenciaIndividual[i] + latenciaGeral;
vazaoGeral = vazaoIndividual[i] + vazaoGeral;
}
printf ("Lat^
encia Geral = %0.6f seg\n\n", latenciaGeral);
latenciaMedia =
(double) latenciaGeral / (double) numeroMensagensEnviar;
printf ("Lat^
encia Média = %0.6f seg\n\n", latenciaMedia);
printf ("Vaz~
ao Geral = %0.6f bytes/seg\n\n", vazaoGeral);
vazaoMedia =
(double) vazaoGeral / (double) numeroMensagensEnviar;
printf ("Vaz~
ao Media = %0.6f bytes/seg\n\n", vazaoMedia);
}
//fim de calculaTempo
//--------------------------------------------------------------//Funç~
ao que gera e envia mensagens através do SCTP
//--------------------------------------------------------------void *
enviaMensagensSCTP (struct parametroT1 *variaveis)
{
printf ("Entrei na thread de ENVIAR mensagens\n");
struct tipao mensagem;
int sock_fd = variaveis->sock_fd;
struct sockaddr_in servaddr = variaveis->endereco;
int loop;
int indiceFluxos = 0;
int out_sz;
int indiceFila = fila;
int indiceMensagem = 0;
int quantidadeEnviada;
struct sctp_sndrcvinfo sri;
135
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
//Essa estrutura é utilizada para saber em qual fluxo
//a mensagem veio
bzero (&sri, sizeof (sri));
sri.sinfo_stream = 0;
printf
printf
printf
printf
("\n");
("-----------------------------------------------------\n");
("DADOS SOBRE O ENVIO DE MENSAGENS\n");
("-----------------------------------------------------\n");
//loop de envio de mensagens
for (loop = 0; loop < numeroMensagensEnviar; loop++)
{
sri.sinfo_stream = indiceFluxos;
bzero (mensagem.sendline, sizeof (100));
snprintf (mensagem.sendline, tamanhoMensagem,
gnutella[loop % 5]);
out_sz = strlen (mensagem.sendline) + 1;
mensagem.id = loop;
printf
printf
printf
printf
printf
printf
printf
("\n");
("ENVIEI\n");
("ID mensagem:%d\n", mensagem.id);
("Mensagem:%s\n", mensagem.sendline);
("Numero Fluxo:%d\n", indiceFluxos);
("Indice Fila:%d\n", indiceFila);
("Numero Loop:%d\n", loop);
//Tempo entre as mensagens em milisegundos
usleep (1000 * intervaloEntreMensagens);
//Inı́cio do cálculo do tempo de ida da mensagem
matriz[loop].id = mensagem.id;
gettimeofday (&matriz[loop].cronometroIda, NULL);
//Enviando a mensagem
quantidadeEnviada =
sctp_sendmsg (sock_fd, &mensagem, out_sz,
(struct sockaddr *) &servaddr,
sizeof (struct sockaddr), 0,
mensagemDesordenada,
sri.sinfo_stream, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar mensagem\n");
}
//Para cálculo do total de bytes enviados
quantidadeEnviadaGlobal =
quantidadeEnviadaGlobal + quantidadeEnviada;
printf ("Bytes enviados:%d\n", quantidadeEnviada);
matriz[loop].tamanho = quantidadeEnviada;
//Controle de fila nos fluxos
if (indiceFila == 1)
{
if (indiceFluxos == (fluxos - 1))
{
indiceFluxos = 0;
}
else
{
indiceFluxos++;
}
indiceFila = fila;
}
else
indiceFila--;
136
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
if (indiceMensagem == 4)
indiceMensagem = 0;
else
indiceMensagem++;
}
//fim do loop for de envio de mensagens
return 0;
}
//fim de envioMensagens
//--------------------------------------------------------------//Funç~
ao que recebe as mensagens através do SCTP
//--------------------------------------------------------------void *
recebeMensagensSCTP (struct parametroT1 *variaveis)
{
printf ("Entrei na thread de RECEBER mensagens\n");
//Utilizado para receber a mensagem
struct tipao mensagem;
//Preenchendo as variáveis locais com os dados
//do par^
ametro
int sock_fd = variaveis->sock_fd;
struct sockaddr_in enderecoEnviante = variaveis->endereco;
int loop;
int quantidadeRecebida;
struct sctp_sndrcvinfo sri;
char *sendline;
sendline = malloc (105);
socklen_t len;
len = sizeof (struct sockaddr_in);
int msg_flags;
//Estrutura utilizada para saber em qual fluxo
//a mensagem veio
bzero (&sri, sizeof (sri));
sri.sinfo_stream = 0;
printf
printf
printf
printf
("\n");
("-----------------------------------------------------\n");
("DADOS SOBRE A RECEPÇ~
AO DE MENSAGENS\n");
("-----------------------------------------------------\n");
//loop de recepç~
ao de mensagens
for (loop = 0; loop < numeroMensagensEnviar; loop++)
{
bzero (sendline, 105);
//Recebendo a mensagem
quantidadeRecebida =
sctp_recvmsg (sock_fd, sendline, 105,
(struct sockaddr *) &enderecoEnviante,
&len, &sri, &msg_flags);
//Armazenando o momento da chegada da mensagem
gettimeofday (&matriz[loop].cronometroVolta, NULL);
bzero (mensagem.sendline, 100);
memcpy (&mensagem, sendline, quantidadeRecebida);
matriz[loop].id = mensagem.id;
if (quantidadeRecebida == (-1))
{
printf ("Falha ao receber a mensagem\n");
loop = loop - 1;
137
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
continue;
}
if (loop == numeroMensagensEnviar - 1)
{
tempoGlobalInicial = matriz[0].cronometroIda;
tempoGlobalFinal = matriz[loop].cronometroVolta;
}
//Impress~
ao de dados para recebimento
printf ("\n");
printf ("RECEBI\n");
printf ("ID=%d - Mensagem:%s\n",
mensagem.id, mensagem.sendline);
printf ("Numero Fluxo:%d\n", sri.sinfo_stream);
printf ("Quantidade recebida:%d\n", quantidadeRecebida);
}
//fim do loop de recepç~
ao de mensagens
free (sendline);
close (sock_fd);
return 0;
}
//fim recebe mensagens
//--------------------------------------------------------------//Funç~
ao para enviar N bytes através do TCP
//--------------------------------------------------------------int
writeN (int fd, const void *buffer, int n)
{
int nRestante;
int nEscritos;
const char *ptr;
ptr = buffer;
nRestante = n;
while (nRestante > 0)
{
if ((nEscritos = write (fd, ptr, nRestante)) <= 0)
{
if (nEscritos < 0 && errno == EINTR)
nEscritos = 0;
else
return (-1);
}
nRestante -= nEscritos;
ptr += nEscritos;
}
return n;
}
//fim do writeN
//--------------------------------------------------------------//Funç~
ao para receber N bytes através do TCP
//--------------------------------------------------------------int
readN (int fd, void *buffer, int n)
{
int nRestante;
int nLidos;
char *ptr;
ptr = buffer;
nRestante = n;
138
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
while (nRestante > 0)
{
if ((nLidos = read (fd, ptr, nRestante)) < 0)
{
if (errno == EINTR)
nLidos = 0;
else
return (-1);
}
else if (nLidos == 0)
break;
nRestante -= nLidos;
ptr += nLidos;
}
return (n - nRestante);
}
//fim do readN
//--------------------------------------------------------------//Funç~
ao para criar a conex~
ao TCP com o echo server
//--------------------------------------------------------------void
criaConexaoTCP (void)
{
int sock_fd;
int erro;
struct sockaddr_in servaddr, meuaddr;
//Abre o socket
sock_fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock_fd < 0)
{
printf ("Criaç~
ao do socket falhou\n");
exit (0);
}
int desliga = 1;
//Desliga o Nagle
erro = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY,
&desliga, sizeof (desliga));
if (erro == (-1))
{
printf ("Falha ao desligar o Nagle do TCP\n");
}
//Definindo os endereços
bzero (&servaddr, sizeof (servaddr));
servaddr.sin_family = AF_INET;
inet_aton (enderecoRemoto, &servaddr.sin_addr);
servaddr.sin_port = htons (portaRemota);
bzero (&meuaddr, sizeof (meuaddr));
meuaddr.sin_family = AF_INET;
inet_aton (enderecoLocal, &meuaddr.sin_addr);
meuaddr.sin_port = htons (portaLocal);
//Tentando conectar ao echo server
erro = connect (sock_fd, (struct sockaddr *) &servaddr,
sizeof (servaddr));
if (erro < 0)
{
printf ("Erro ao efetuar o connect\n");
exit (0);
139
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
}
struct parametroT1 valores1;
struct parametroT1 valores2;
valores1.sock_fd = sock_fd;
valores2.sock_fd = sock_fd;
pthread_t p_thread_envia;
pthread_t p_thread_recebe;
//Cria a thread que envia mensagens
pthread_create (&p_thread_envia, NULL,
(void *) enviaMensagensTCP, &valores2);
//Cria a thread que recebe mensagens
pthread_create (&p_thread_recebe, NULL,
(void *) recebeMensagensTCP, &valores1);
//Aguarda as duas threads terminarem
pthread_join (p_thread_envia, NULL);
pthread_join (p_thread_recebe, NULL);
//Calcula resultados e os exibe
calculaTempo ();
geraScript ();
return;
}
//fim de cria conexao TCP
//--------------------------------------------------------------//Funç~
ao para o envio de mensagens através do TCP
//--------------------------------------------------------------void *
enviaMensagensTCP (struct parametroT1 *variaveis)
{
struct tipao mensagem;
int
int
int
int
sock_fd = variaveis->sock_fd;
out_sz;
loop;
quantidadeEnviada;
printf
printf
printf
printf
("\n");
("-----------------------------------------------------\n");
("DADOS DE ENVIO DE MENSAGENS\n");
("-----------------------------------------------------\n");
//loop de envio de mensagens
for (loop = 0; loop <= numeroMensagensEnviar; loop++)
{
bzero (mensagem.sendline, sizeof (100));
snprintf (mensagem.sendline, tamanhoMensagem,
gnutella[loop % 5]);
out_sz = strlen (mensagem.sendline) + 1;
mensagem.id = loop;
//Impress~
ao de dados sobre o envio
printf ("\n");
printf ("ENVIEI\n");
printf ("ID mensagem:%d\n", mensagem.id);
printf ("Mensagem:%s\n", mensagem.sendline);
printf ("Número Loop:%d\n", loop);
//Inı́cio do cálculo do tempo de ida da mensagem
matriz[loop].id = mensagem.id;
gettimeofday (&matriz[loop].cronometroIda, NULL);
usleep (1000 * intervaloEntreMensagens);
140
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
quantidadeEnviada = writeN (sock_fd, &mensagem, out_sz);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar mensagem\n");
}
//Para cálculo do total de bytes enviados
quantidadeEnviadaGlobal =
quantidadeEnviadaGlobal + quantidadeEnviada;
printf ("Bytes enviados:%d\n", quantidadeEnviada);
matriz[loop].tamanho = quantidadeEnviada;
}
return 0;
}
//fim do loop for de envio de mensagens
//fim de envia mensagens
//--------------------------------------------------------------//Funç~
ao que recebe mensagens através do TCP
//--------------------------------------------------------------void *
recebeMensagensTCP (struct parametroT1 *variaveis)
{
struct tipao mensagem;
int sock_fd = variaveis->sock_fd;
int loop;
socklen_t len;
int quantidadeRecebida;
len = sizeof (struct sockaddr_in);
char *sendline;
sendline = malloc (105);
printf
printf
printf
printf
("\n");
("-----------------------------------------------------\n");
("DADOS DE RECEPCAO DE MENSAGENS\n");
("-----------------------------------------------------\n");
//loop de recepç~
ao de mensagens
for (loop = 0; loop <= numeroMensagensEnviar; loop++)
{
bzero (sendline, 105);
quantidadeRecebida =
readN (sock_fd, sendline, matriz[loop].tamanho);
gettimeofday (&matriz[loop].cronometroVolta, NULL);
if (quantidadeRecebida == (-1))
{
printf ("Falha ao receber a mensagem\n");
loop = loop - 1;
continue;
}
if (quantidadeRecebida == (0))
{
loop = loop - 1;
continue;
}
bzero (mensagem.sendline, 100);
memcpy (&mensagem, sendline, quantidadeRecebida);
if (loop == numeroMensagensEnviar - 1)
{
tempoGlobalInicial = matriz[0].cronometroIda;
141
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
tempoGlobalFinal = matriz[loop].cronometroVolta;
}
//Impress~
ao de dados para recebimento
printf ("\n");
printf ("RECEBI\n");
printf ("ID=%d - Mensagem:%s\n", mensagem.id,
mensagem.sendline);
printf ("Quantidade Recebida: %d\n", quantidadeRecebida);
printf ("Quantidade que era pra receber: %d\n",
matriz[loop].tamanho);
}
//fim do loop de recepç~
ao de mensagens
free (sendline);
close (sock_fd);
return 0;
}
//fim de recebe mensagens
//--------------------------------------------------------------//Fim de GeraPacotes.c
//---------------------------------------------------------------
142
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
/****************************************************************
EchoServer.c - Echo Server
Gustavo Salvadori Baptista do Carmo
10 de Fevereiro de 2006
[email protected]
****************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
<sys/types.h>
<sys/socket.h>
<arpa/inet.h>
<netinet/in.h>
<netinet/sctp.h>
<netinet/tcp.h>
<errno.h>
<netdb.h>
<stdio.h>
<stdlib.h>
<unistd.h>
<string.h>
//--------------------------------------------------------------//Protótipos
//--------------------------------------------------------------void uso (void);
void parser (int argc, char **argv);
void cuidaDoLance (void);
void echoSCTP (void);
void echoTCP (void);
int writeN (int fd, const void *buffer, int n);
//--------------------------------------------------------------//Variáveis globais
//--------------------------------------------------------------int tamanhoBuffer = 0;
int portaLocal = 0;
char *enderecoLocal = NULL;
int mensagemDesordenada = 0;
int tipoProtocolo = 1;
//Estrutura usada para receber mensagens
struct tipao
{
int id;
char sendline[100];
} mensagem;
//--------------------------------------------------------------//Programa principal
//--------------------------------------------------------------int
main (int argc, char **argv)
{
parser (argc, argv);
cuidaDoLance ();
if (tipoProtocolo == 1)
echoTCP ();
else if (tipoProtocolo == 2)
echoSCTP ();
else
uso ();
return 0;
}
143
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
//--------------------------------------------------------------//EchoServer com o TCP
//--------------------------------------------------------------void
echoTCP ()
{
int sock_fd, sock_novo;
int erro;
struct sockaddr_in servaddr, cliaddr;
sock_fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock_fd < 0)
{
printf ("Criaç~
ao do socket falhou\n");
exit (0);
}
bzero (&servaddr, sizeof (servaddr));
servaddr.sin_family = AF_INET;
inet_aton (enderecoLocal, &servaddr.sin_addr);
servaddr.sin_port = htons (portaLocal);
int desliga = 1;
erro = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY,
&desliga, sizeof (desliga));
if (erro == (-1))
{
printf ("Falha ao desligar o Nagle do TCP\n");
}
erro = bind (sock_fd, (struct sockaddr *) &servaddr,
sizeof (servaddr));
if (erro != 0)
{
printf ("Erro ao dar o bind\n");
exit (0);
}
erro = listen (sock_fd, 1);
if (erro != 0)
{
printf ("Erro ao entrar no listen\n");
exit (0);
}
socklen_t len;
len = sizeof (struct sockaddr_in);
while (1)
{
sock_novo =
accept (sock_fd, (struct sockaddr *) &cliaddr, &len);
if (sock_novo < 0)
{
printf ("Erro no accept\n");
exit (0);
}
char *readbuf;
int quantidadeEnviada;
int quantidadeRecebida;
readbuf = malloc (100);
144
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
printf ("----------------------------------------------\n");
printf ("DADOS DA RECEPÇ~
AO\n");
printf ("----------------------------------------------\n");
while (1)
{
bzero (readbuf, 100);
//Recebendo o segmento do cliente
quantidadeRecebida = read (sock_novo, readbuf, 100);
if (quantidadeRecebida == (-1))
{
printf ("Falha ao receber os bytes\n");
}
//O cliente encerra a comunicaç~
ao
if (quantidadeRecebida == 0)
{
printf ("\nCliente encerrou a comunicaç~
ao\n\n");
close (sock_novo);
free (readbuf);
break;
}
printf ("\n");
printf ("Número de bytes recebidos:%d\n",
quantidadeRecebida);
printf ("Endereço remoto:%s\n",
inet_ntoa (cliaddr.sin_addr));
//Enviando o segmento devolta para o cliente
quantidadeEnviada =
writeN (sock_novo, readbuf, quantidadeRecebida);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar os bytes\n");
}
}
//fim do while 1
}
return;
}
//Fim de Echo Server TCP
//--------------------------------------------------------------//EchoServer com o SCTP
//--------------------------------------------------------------void
echoSCTP ()
{
int sock_fd;
int erro;
struct sockaddr_in servaddr, cliaddr;
struct sctp_sndrcvinfo sri;
struct sctp_event_subscribe evnts;
sock_fd = socket (AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
if (sock_fd < 0)
{
printf ("Criaç~
ao do socket falhou\n");
exit (0);
}
bzero (&servaddr, sizeof (servaddr));
servaddr.sin_family = AF_INET;
inet_aton (enderecoLocal, &servaddr.sin_addr);
servaddr.sin_port = htons (portaLocal);
145
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
bzero (&evnts, sizeof (evnts));
evnts.sctp_data_io_event = 1;
erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_EVENTS,
&evnts, sizeof (evnts));
if (erro != 0)
{
printf ("Erro ao setar a opç~
ao setar eventos\n");
exit (0);
}
struct sctp_initmsg initmsg;
initmsg.sinit_num_ostreams = 30;
initmsg.sinit_max_instreams = 30;
initmsg.sinit_max_attempts = 3;
initmsg.sinit_max_init_timeo = 30;
int desliga = 1;
erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_NODELAY,
&desliga, sizeof (desliga));
if (erro == (-1))
{
printf ("Erro ao desligar o Nagle do SCTP\n");
exit (0);
}
erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_INITMSG,
(char *) &initmsg,
sizeof (struct sctp_initmsg));
if (erro != 0)
{
printf ("Erro ao setar a opç~
ao de initmessage\n");
exit (0);
}
tamanhoBuffer = tamanhoBuffer * 1000;
if (tamanhoBuffer != 0)
{
erro = setsockopt (sock_fd, IPPROTO_SCTP, SO_RCVBUF,
(char *) &tamanhoBuffer,
sizeof (tamanhoBuffer));
if (erro != 0)
{
printf ("Erro a setar opç~
ao do socket de receive buffer\n");
exit (0);
}
erro = setsockopt (sock_fd, IPPROTO_SCTP, SO_SNDBUF,
(char *) &tamanhoBuffer,
sizeof (tamanhoBuffer));
if (erro != 0)
{
printf ("Erro a setar opç~
ao do socket de send buffer\n");
exit (0);
}
}
//fim tamanho buffer
erro = bind (sock_fd, (struct sockaddr *) &servaddr,
sizeof (servaddr));
if (erro != 0)
{
printf ("Erro ao dar o bind\n");
146
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
exit (0);
}
erro = listen (sock_fd, 1);
if (erro != 0)
{
printf ("Erro ao setar a entrar no listen\n");
exit (0);
}
socklen_t len;
int msg_flags;
char *readbuf;
int quantidadeEnviada;
int quantidadeRecebida;
len = sizeof (struct sockaddr_in);
readbuf = malloc (105);
while (1)
{
bzero (readbuf, 105);
//Recebendo a mensagem do cliente
quantidadeRecebida = sctp_recvmsg (sock_fd, readbuf, 105,
(struct sockaddr *)
&cliaddr, &len, &sri,
&msg_flags);
if (quantidadeRecebida == (-1))
{
printf ("Falha ao receber a mensagem\n");
}
bzero (mensagem.sendline, 100);
memcpy (&mensagem, readbuf, quantidadeRecebida);
//DADOS PARA CONFER^
ENCIA
printf ("\n");
printf ("Id:%d\n", mensagem.id);
printf ("buffer:%s\n", mensagem.sendline);
printf ("Numero da stream:%d\n", sri.sinfo_stream);
printf ("Numero de bytes recebidos:%d\n",
quantidadeRecebida);
printf ("Endereco remoto:%s\n",
inet_ntoa (cliaddr.sin_addr));
//enviando a mensagem devolta para o cliente
quantidadeEnviada =
sctp_sendmsg (sock_fd, readbuf, quantidadeRecebida,
(struct sockaddr *) &cliaddr,
sizeof (struct sockaddr),
sri.sinfo_ppid, mensagemDesordenada,
sri.sinfo_stream, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
}
}
//fim do while 1
free (readbuf);
return;
}
//fim de Echo Server SCTP
//--------------------------------------------------------------//Conferindo os dados da linha de comando
//--------------------------------------------------------------void
cuidaDoLance (void)
{
if ((enderecoLocal == NULL) ||
147
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
(inet_aton (enderecoLocal, NULL) == 0))
{
printf ("\nFaltou colocar o endereco local\n");
uso ();
exit (0);
}
if (portaLocal == 0)
{
printf ("\nFaltou colocar a porta local\n");
uso ();
exit (0);
}
printf
printf
printf
printf
("\n");
("Endereco do host local:%s\n", enderecoLocal);
("Porta do host local:%d\n", portaLocal);
("Protocolo de transporte = ");
if (tipoProtocolo == 1)
printf ("TCP\n");
else
printf ("SCTP\n");
if (tamanhoBuffer == 0)
printf ("Buffer de envio e recebimento setado para padrao\n");
else
printf ("Buffer de envio e recebimento (Kbytes):%d\n",
tamanhoBuffer);
if (mensagemDesordenada == 0)
printf ("Entrega das mensagens: ordenada\n");
else
printf ("Entrega das mensagens: desordenada\n");
printf ("\n");
}
//termina cuida do lance
//--------------------------------------------------------------//Preenchendo com os dados da linha de comando
//--------------------------------------------------------------void
parser (int argc, char **argv)
{
int getoptC;
int bufferAux;
int desordem;
int protocolo;
while (argc)
{
getoptC = getopt (argc, argv, "H:P:t:b:d:");
if (getoptC < 0)
break;
switch (getoptC)
{
case ’H’:
enderecoLocal = optarg;
break;
case ’P’:
portaLocal = atoi (optarg);
break;
case ’t’:
protocolo = atoi (optarg);
if ((protocolo == 1) || (protocolo == 2))
tipoProtocolo = protocolo;
148
Apêndice A. Códigos fonte dos aplicativos do ambiente de simulação
break;
case ’b’:
bufferAux = atoi (optarg);
if ((bufferAux > 0) && (bufferAux < 1001))
tamanhoBuffer = bufferAux;
break;
case ’d’:
desordem = atoi (optarg);
if (desordem == 1)
mensagemDesordenada = MSG_UNORDERED;
break;
default:
break;
}
}
}
//fim do switch
//fim while argc
//fim de parser
//--------------------------------------------------------------//Imprime uso do aplicativo
//--------------------------------------------------------------void
uso (void)
{
printf ("\nUso:\n");
printf ("./EchoServer -H endLoc -P ptLoc -t tipProt -b buffer -d ordem\n");
printf ("
-H endLoc
- endereco do host local\n");
printf ("
-P ptLoc
- porta do host local\n");
printf ("
-t tipProt
- protocolo de transporte - TCP=1 - SCTP=2\n");
printf ("
-b buffer
- buffer de envio e recebimento (Kbytes) ");
printf ("- min 1 - max 1000\n");
printf ("
-d ordem
- 1 - envia as mensagens de forma desordenada\n");
printf ("\n");
}
//fim de uso
//--------------------------------------------------------------//Funç~
ao para forçar a escrita de todos os bytes TCP
//--------------------------------------------------------------int
writeN (int fd, const void *buffer, int n)
{
int nRestante;
int nEscritos;
const char *ptr;
ptr = buffer;
nRestante = n;
while (nRestante > 0)
{
if ((nEscritos = write (fd, ptr, nRestante)) <= 0)
{
if (nEscritos < 0 && errno == EINTR)
nEscritos = 0;
else
return (-1);
}
nRestante -= nEscritos;
ptr += nEscritos;
}
return n;
}
//fim de writeN
//--------------------------------------------------------------//Fim de EchoServer.c
//---------------------------------------------------------------
149
Apêndice B
Código fonte do aplicativo Octopus
O código fonte apresentado neste apêndice refere-se ao aplicativo Octopus, descrito
no Capı́tulo 4.
/****************************************************************
octopus.c - Interface grafica do aplicativo Octopus
Versao 1.0
Gustavo Salvadori Baptista do Carmo
27 de Fevereiro de 2006
[email protected]
****************************************************************/
#include
#include
#include
#include
#include
#include
#include
</usr/include/gtk-2.0/gtk/gtk.h>
<pthread.h>
<unistd.h>
<sys/dir.h>
<sys/param.h>
<time.h>
"./SERVENTE.c"
//----------------------------------------------------------------//Headers das funcoes locais
//----------------------------------------------------------------void *fazGraficos (void);
void delete_event (void);
static void associa (GtkWidget * widget, GtkWidget * entry);
static void enviaDownload (GtkWidget * widget, GtkWidget * entry);
void selectionMade (GtkWidget * clist, gint row, gint column,gpointer data);
GtkWidget *image_new_from_file_at_size (const char *filename,
gint width, gint height);
//----------------------------------------------------------------//Headers das funcoes do SERVENTE.c
//----------------------------------------------------------------void *abreServidor (struct sockaddr_in *servaddr);
void imprimeAssociacoesAbertas (void);
void enviaQUERY (char *consulta);
void enviaPING (void);
void pedeArquivo (char *nomeArquivo, char *endereco, char *porta,char *tamanho);
int enderecoIgual (struct sockaddr_in endereco1,struct sockaddr_in endereco2);
int associaPeer (struct sockaddr_in);
void associaHostCache (void);
void imprimeHostCache (void);
int incluiHostCache (struct sockaddr_in);
150
Apêndice B. Código fonte do aplicativo Octopus
//----------------------------------------------------------------//Variáveis globais
//----------------------------------------------------------------struct sockaddr_in enderecoConectar;
struct sockaddr_in meuaddr;
GtkWidget *porta;
GtkWidget *listaRemota;
gint linhaListaLocal = 500;
gint colunaListaLocal = 0;
extern int emDownload;
//----------------------------------------------------------------//Programa principal
//----------------------------------------------------------------int
main (int argc, char *argv[])
{
int porta;
if (argc != 3)
{
printf ("./octopus Endereco Porta\n");
exit (0);
}
porta = atoi (argv[2]);
//Endereco local do aplicativo
bzero (&meuaddr, sizeof (meuaddr));
meuaddr.sin_family = AF_INET;
inet_aton (argv[1], &meuaddr.sin_addr);
meuaddr.sin_port = htons (porta);
//Criando a thread do servidor
pthread_t p_thread_servidor;
pthread_create (&p_thread_servidor, NULL, (void *) abreServidor,
&meuaddr);
//Criando a thread da interface grafica
pthread_t p_thread_graficos;
pthread_create (&p_thread_graficos, NULL, fazGraficos (), NULL);
return 0;
}
//fim de main
//-----------------------------------------------------------------//Funcao de evento do botao associar
//-----------------------------------------------------------------static void
associa (GtkWidget * widget, GtkWidget * entry)
{
const gchar *enderecoTexto;
const gchar *portaTexto;
struct sockaddr_in servaddr;
int erro;
enderecoTexto = gtk_entry_get_text (GTK_ENTRY (entry));
portaTexto = gtk_entry_get_text (GTK_ENTRY (porta));
if ((strcmp (portaTexto, "") == 0)|| (strcmp (enderecoTexto, "") == 0))
{
printf ("Nenhum endereco digitado para a associacao\n");
return;
}
bzero (&servaddr, sizeof (servaddr));
servaddr.sin_family = AF_INET;
inet_aton (enderecoTexto, &servaddr.sin_addr);
servaddr.sin_port = htons (atoi (portaTexto));
151
Apêndice B. Código fonte do aplicativo Octopus
if (widget == widget)
{
};
if (strcmp (enderecoTexto, inet_ntoa (meuaddr.sin_addr)) == 0)
printf ("Associar a si mesmo nao pode\n");
else
{
erro = associaPeer (servaddr);
if (erro == 1)
incluiHostCache (servaddr);
gtk_entry_set_text (GTK_ENTRY (entry), "");
gtk_entry_set_text (GTK_ENTRY (porta), "");
}
}
//Fim de associa
//----------------------------------------------------------------//Funcao para limpar a lista de respostas
//----------------------------------------------------------------void
limpaLista (GtkWidget * widget, GtkWidget * entry)
{
if (widget == widget)
{
};
gtk_clist_clear (GTK_CLIST (entry));
linhaListaLocal = 500;
}
//----------------------------------------------------------------//Funcao de controle da lista de respostas
//----------------------------------------------------------------void
selection_made (GtkWidget * clist, gint row, gint column,
gpointer data)
{
if (clist == clist)
{
};
if (data == data)
{
};
linhaListaLocal = row;
colunaListaLocal = column;
return;
}
//----------------------------------------------------------------//Funcao que envia o pedido de transferencia de arquivo
//----------------------------------------------------------------static void
enviaDownload (GtkWidget * widget, GtkWidget * entry)
{
if (widget == widget)
{
};
gchar
gchar
gchar
gchar
*nomeArquivo;
*endereco;
*porta;
*tamanho;
gtk_clist_get_text (GTK_CLIST (entry), linhaListaLocal, 0, &nomeArquivo);
gtk_clist_get_text (GTK_CLIST (entry), linhaListaLocal, 1, &endereco);
gtk_clist_get_text (GTK_CLIST (entry), linhaListaLocal, 2, &porta);
152
Apêndice B. Código fonte do aplicativo Octopus
gtk_clist_get_text (GTK_CLIST (entry), linhaListaLocal, 3, &tamanho);
if (linhaListaLocal != 500)
pedeArquivo (nomeArquivo, endereco, porta, tamanho);
}
//fim de enviaDownload
//----------------------------------------------------------------//Funcao que envia uma consulta para todos os nos associados
//----------------------------------------------------------------static void
enviaConsulta (GtkWidget * widget, GtkWidget * entry)
{
if (widget == widget)
{
};
const gchar *consultaTexto;
bzero (&consultaTexto, sizeof (gchar));
consultaTexto = gtk_entry_get_text (GTK_ENTRY (entry));
printf ("A string de consulta:%s\n", consultaTexto);
if (strlen (consultaTexto) < 4)
printf ("Consulta muito curta; use mais de 3 letras\n");
else
{
enviaQUERY ((char *) consultaTexto);
}
}
//fim de enviaConsulta
//----------------------------------------------------------------//Funç~
ao evento de clique no X da janela
//----------------------------------------------------------------void
delete_event ()
{
exit (0);
}
//----------------------------------------------------------------//Funcao para a exibicao da interface grafica
//----------------------------------------------------------------void *
fazGraficos ()
{
GtkWidget *janelaGeral;
GtkWidget *logo;
GtkWidget *letreiro;
GtkWidget *fixed;
GtkWidget *botaoAssocia;
GtkWidget *botaoDownload;
GtkWidget *botaoConsulta;
GtkWidget *botaoAssociacao;
GtkWidget *botaoPing;
GtkWidget *botaoLimpa;
GtkWidget *endereco;
GtkWidget *consulta;
GtkWidget *labelEndereco;
GtkWidget *labelPorta;
GtkWidget *labelConsulta;
GtkWidget *botaoAssociaHostCache;
GtkWidget *botaoImprimeHostCache;
gtk_init (NULL, NULL);
gchar *titles[] = { "Nome do arquivo", "Endereco", "Porta", "Tamanho" };
//Criando os widgets
janelaGeral = gtk_window_new (GTK_WINDOW_TOPLEVEL);
153
Apêndice B. Código fonte do aplicativo Octopus
logo = image_new_from_file_at_size ("./logo.png", 150, 150);
letreiro = image_new_from_file_at_size ("./letreiro.png", 150, 180);
fixed = gtk_fixed_new ();
botaoAssocia = gtk_button_new_with_label ("Associar");
botaoDownload = gtk_button_new_with_label ("Download");
botaoConsulta = gtk_button_new_with_label ("Consultar");
botaoAssociaHostCache = gtk_button_new_with_label ("Associa Host Cache");
botaoImprimeHostCache = gtk_button_new_with_label ("Host Cache");
botaoPing = gtk_button_new_with_label ("Ping");
botaoLimpa = gtk_button_new_with_label ("Limpa");
botaoAssociacao = gtk_button_new_with_label ("Associacoes");
listaRemota = gtk_clist_new_with_titles (4, titles);
consulta = gtk_entry_new ();
endereco = gtk_entry_new ();
porta = gtk_entry_new ();
labelEndereco = gtk_label_new ("Endereco");
labelPorta = gtk_label_new ("Porta");
labelConsulta = gtk_label_new ("Consulta");
//Configurando
gtk_window_set_title (GTK_WINDOW (janelaGeral), "oCToPuS");
gtk_window_set_position (GTK_WINDOW (janelaGeral), GTK_WIN_POS_CENTER);
gtk_window_set_resizable (GTK_WINDOW (janelaGeral), FALSE);
gtk_widget_set_size_request (janelaGeral, 950, 700);
gtk_clist_set_selection_mode (GTK_CLIST (listaRemota), GTK_SELECTION_BROWSE);
gtk_clist_set_shadow_type (GTK_CLIST (listaRemota), GTK_SHADOW_IN);
//Posicionando
gtk_fixed_put (GTK_FIXED (fixed), labelEndereco, 300, 30);
gtk_fixed_put (GTK_FIXED (fixed), labelPorta, 550, 30);
gtk_widget_set_size_request (endereco, 200, 40);
gtk_fixed_put (GTK_FIXED (fixed), endereco, 300, 50);
gtk_widget_set_size_request (porta, 150, 40);
gtk_fixed_put (GTK_FIXED (fixed), porta, 550, 50);
gtk_widget_set_size_request (botaoAssocia, 100, 40);
gtk_fixed_put (GTK_FIXED (fixed), botaoAssocia, 800, 50);
gtk_widget_set_size_request (botaoAssociacao, 170, 40);
gtk_fixed_put (GTK_FIXED (fixed), botaoAssociacao, 100, 420);
gtk_widget_set_size_request (botaoPing, 170, 40);
gtk_fixed_put (GTK_FIXED (fixed), botaoPing, 100, 470);
gtk_widget_set_size_request (botaoAssociaHostCache, 170, 40);
gtk_fixed_put (GTK_FIXED (fixed), botaoAssociaHostCache, 100, 520);
gtk_widget_set_size_request (botaoImprimeHostCache, 170, 40);
gtk_fixed_put (GTK_FIXED (fixed), botaoImprimeHostCache, 100,
570);
gtk_fixed_put (GTK_FIXED (fixed), labelConsulta, 300, 110);
gtk_widget_set_size_request (consulta, 400, 40);
gtk_fixed_put (GTK_FIXED (fixed), consulta, 300, 130);
gtk_widget_set_size_request (botaoConsulta, 100, 40);
gtk_fixed_put (GTK_FIXED (fixed), botaoConsulta, 800, 130);
gtk_widget_set_size_request (botaoDownload, 100, 40);
gtk_fixed_put (GTK_FIXED (fixed), botaoDownload, 300, 200);
gtk_widget_set_size_request (botaoLimpa, 100, 40);
gtk_fixed_put (GTK_FIXED (fixed), botaoLimpa, 500, 200);
gtk_widget_set_size_request (listaRemota, 600, 400);
154
Apêndice B. Código fonte do aplicativo Octopus
gtk_fixed_put (GTK_FIXED (fixed), listaRemota, 300, 250);
gtk_clist_set_column_width (GTK_CLIST (listaRemota), 0, 250);
gtk_clist_set_column_width (GTK_CLIST (listaRemota), 1, 140);
gtk_clist_set_column_width (GTK_CLIST (listaRemota), 2, 40);
gtk_clist_set_column_width (GTK_CLIST (listaRemota), 2, 100);
gtk_fixed_put (GTK_FIXED (fixed), logo, 70, 30);
gtk_fixed_put (GTK_FIXED (fixed), letreiro, 50, 155);
//Montando
gtk_container_add (GTK_CONTAINER (janelaGeral), fixed);
//Atribuido os sinais
g_signal_connect (G_OBJECT (janelaGeral), "delete_event",
G_CALLBACK (delete_event), NULL);
g_signal_connect (G_OBJECT (botaoAssociacao), "clicked",
G_CALLBACK (imprimeAssociacoesAbertas), NULL);
g_signal_connect (G_OBJECT (botaoAssocia), "clicked",
G_CALLBACK (associa), (gpointer) endereco);
g_signal_connect (G_OBJECT (botaoConsulta), "clicked",
G_CALLBACK (enviaConsulta), (gpointer) consulta);
g_signal_connect (G_OBJECT (botaoDownload), "clicked",
G_CALLBACK (enviaDownload), (gpointer) listaRemota);
g_signal_connect (G_OBJECT (listaRemota), "select_row",
GTK_SIGNAL_FUNC (selection_made), NULL);
g_signal_connect (G_OBJECT (botaoLimpa), "clicked",
G_CALLBACK (limpaLista), (gpointer) listaRemota);
g_signal_connect (G_OBJECT (botaoPing), "clicked",
G_CALLBACK (enviaPING), (gpointer) listaRemota);
g_signal_connect (G_OBJECT (botaoImprimeHostCache), "clicked",
G_CALLBACK (imprimeHostCache), NULL);
g_signal_connect (G_OBJECT (botaoAssociaHostCache), "clicked",
G_CALLBACK (associaHostCache), NULL);
//Mostrando
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
gtk_widget_show
(labelEndereco);
(labelPorta);
(labelConsulta);
(endereco);
(porta);
(consulta);
(listaRemota);
(botaoDownload);
(botaoAssociacao);
(botaoLimpa);
(botaoPing);
(botaoConsulta);
(botaoAssocia);
(botaoAssociaHostCache);
(botaoImprimeHostCache);
(logo);
(letreiro);
(fixed);
(janelaGeral);
gtk_main ();
//Término dos gráficos
return 0;
155
Apêndice B. Código fonte do aplicativo Octopus
}
//Fim de fazGraficos
//----------------------------------------------------------------//Funcao que coloca uma figura na interface grafica
//----------------------------------------------------------------GtkWidget *
image_new_from_file_at_size (const char *filename, gint width, gint height)
{
GtkWidget *image = NULL;
GdkPixbuf *pixbuf;
GError *error;
const gchar *st = "icon_size";
g_return_val_if_fail (filename != NULL, NULL);
error = NULL;
pixbuf = gdk_pixbuf_new_from_file (filename, &error);
if (error != NULL)
{
g_warning (G_STRLOC ": cannot open %s: %s", filename,
error->message);
g_error_free (error);
}
if (pixbuf != NULL)
{
#if DEBUG
debug (D_STATUS, "pixbuf new from file %s\n", filename);
#endif
GdkPixbuf *scaled =
gdk_pixbuf_scale_simple (pixbuf, width, height,
GDK_INTERP_BILINEAR);
image = gtk_image_new ();
gtk_image_set_from_pixbuf (GTK_IMAGE (image), scaled);
g_object_unref (G_OBJECT (scaled));
g_object_unref (G_OBJECT (pixbuf));
}
else
{
#if DEBUG
debug (D_STATUS, "pixbuf new from file %s failed !\n",
filename);
#endif
GtkIconSize icon_size =
gtk_icon_size_register (st, width, height);
image = gtk_image_new_from_stock (GTK_STOCK_MISSING_IMAGE, icon_size);
}
}
return image;
//Fim da funcao que insere uma figura na interface grafica
//----------------------------------------------------------------//Fim de octopus.c
//-----------------------------------------------------------------
156
Apêndice B. Código fonte do aplicativo Octopus
/*****************************************************************
SERVENTE.c - Nucleo do aplicativo Octopus
Versao 1.0
Gustavo Salvadori Baptista do Carmo
27 de Fevereiro de 2006
[email protected]
*****************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
<sys/types.h>
<sys/socket.h>
<netinet/in.h>
<arpa/inet.h>
<netinet/sctp.h>
<errno.h>
<netdb.h>
<stdio.h>
<stdlib.h>
<unistd.h>
<string.h>
<pthread.h>
<sys/dir.h>
<sys/param.h>
<fcntl.h>
<signal.h>
//----------------------------------------------------------------//Estruturas das mensagens Gnutella
//----------------------------------------------------------------struct CABECALHO
{
unsigned int DescriptorID;
unsigned char TTL;
unsigned char Hops;
};
struct PONG
{
short porta;
int endereco;
int numeroArquivos;
float numeroKilobytes;
};
struct QUERY
{
int DescriptorID;
int TTL;
int Hops;
char consulta[100];
};
struct QUERYHIT
{
int DescriptorID;
int TTL;
int Hops;
struct sockaddr_in endereco;
long tamanhoArquivo;
char nomeArquivo[100];
};
struct dadosPedido
{
struct sockaddr_in endereco;
int fluxo;
int sock_fd;
char nomeArquivo[100];
int valorVetor;
};
157
Apêndice B. Código fonte do aplicativo Octopus
int tamanhoCabecalho = sizeof (int) + sizeof (int) + sizeof (int);
int tamanhoQUERYHIT = 3 * sizeof (int) + sizeof (struct sockaddr_in) +
sizeof (long);
//----------------------------------------------------------------//Variaveis e estruturas para a manipulacao do cache de associacoes
//----------------------------------------------------------------int numeroDeAssociacoesAbertas = 0;
struct cacheAssociacoes
{
int numeroSocket;
struct sockaddr_in endereco;
};
struct cacheAssociacoes associacoesAbertas[20];
//----------------------------------------------------------------//Variaveis e estruturas que implementam o cache de ID de mensagens
//----------------------------------------------------------------int indiceListaLoop = 0;
struct cacheIDMensagens
{
int ID;
int NumeroSocket;
struct sockaddr_in endereco;
} listaDeIDs[200];
//----------------------------------------------------------------//Prototipos das funcoes locais
//----------------------------------------------------------------//Manipulacao de associacoes
void *abreServidor (struct sockaddr_in *servaddr);
int associaPeer (struct sockaddr_in servaddr);
//Manipulacao de mensagens
void *manipuladorMensagens (int *);
void enviaPING (void);
void enviaPONG (struct sockaddr_in, struct CABECALHO, int);
void enviaQUERY (char *);
void enviaQUERYHIT (char nomeArquivo[], struct QUERY, struct sockaddr_in, int);
void trataPING (struct CABECALHO mensagem, struct sockaddr_in cliaddr);
void trataQUERY (struct QUERY, struct sockaddr_in, int);
void trataQUERYHIT (struct QUERYHIT);
void pedeArquivo (char *, char *, char *, char *);
void *enviaArquivo (struct dadosPedido *);
//Tratamento de loops e backtrack
int trataLoop (int);
void iniciaListaLoop (void);
void incluiListaLoop (int, int, struct sockaddr_in);
//Manipulacao do Host Cache
int existeHostCache (struct sockaddr_in);
int incluiHostCache (struct sockaddr_in);
void imprimeHostCache (void);
void associaHostCache (void);
//Funcoes Miscelania
void imprimeAssociacoesAbertas (void);
int enderecoIgual (struct sockaddr_in, struct sockaddr_in);
int existeEnderecoLista (struct sockaddr_in);
int removeEnderecoLista (int);
int aRespostaMinha (int);
int retornaIndiceLista (int ID);
int retornaIndiceListaEndereco (struct sockaddr_in);
void adicionaResposta (char *texto[]);
158
Apêndice B. Código fonte do aplicativo Octopus
int calculaTamanhoArquivo (char *);
int contaArquivosDiretorio (void);
int tamanhoDiretorio (void);
void retornaNomeArquivoGET (char *, char *);
void retornaSeguinte (char *, int, char *);
int existeSocketLista (int);
//Variaveis Miscelania
int socketNovoParaAssociacoes[20];
int indiceSocketNovoAssociacoes = 0;
pthread_t threadCliente[20];
FILE *hostCacheFile;
//Variaveis do octopus.c
extern struct sockaddr_in meuaddr;
extern GtkWidget *listaRemota;
extern gint linhaListaLocal;
//----------------------------------------------------------------//Funcao para enviar o arquivo para alguem que pediu
//Envia informacoes sobre o arquivo, em seguida os dados
//----------------------------------------------------------------void *
enviaArquivo (struct dadosPedido *buffer)
{
int quantidadeEnviada;
int out_sz;
int o = 0;
char *stringPedido;
char bufferArquivo[200];
char nomeArquivoCompleto[100];
struct dadosPedido dados;
FILE *arquivo;
//Preenchendo estrutura local - informacoes da transferencia
dados.endereco = buffer->endereco;
strcpy (dados.nomeArquivo, buffer->nomeArquivo);
dados.sock_fd = buffer->sock_fd;
dados.fluxo = buffer->fluxo;
dados.valorVetor = buffer->valorVetor;
printf
printf
printf
printf
printf
printf
("\nEstou prestes a enviar um arquivo\n");
("Nome do arquivo:%s\n", dados.nomeArquivo);
("Numero socket:%d\n", dados.sock_fd);
("Pedido por:%s\n", inet_ntoa (dados.endereco.sin_addr));
("Porta:%d\n", ntohs (dados.endereco.sin_port));
("Fluxo de envio:%d\n\n", dados.fluxo);
bzero (nomeArquivoCompleto, 100);
strcat (nomeArquivoCompleto, "./ArquivosCompartilhados/");
strcat (nomeArquivoCompleto, dados.nomeArquivo);
stringPedido = malloc (100);
bzero (stringPedido, 100);
arquivo = fopen (nomeArquivoCompleto, "rb");
//Envia mensagem de erro se der problema ao abrir o arquivo
if (arquivo == NULL)
{
printf ("Problema ao abrir o arquivo\n");
sprintf (stringPedido,
"OCTOPUS/1.0 003 ERRO\r\n/Arquivo:%s\r\nMotivo:Arquivo nao encontrado\r\n\r\n",
dados.nomeArquivo);
printf ("Enviando mensagem de erro:%s\n", stringPedido);
out_sz = strlen (stringPedido);
159
Apêndice B. Código fonte do aplicativo Octopus
quantidadeEnviada =
sctp_sendmsg (dados.sock_fd, stringPedido, out_sz,
(struct sockaddr *) &dados.endereco,
sizeof (struct sockaddr), 0,
MSG_UNORDERED, 5, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
free (stringPedido);
memcpy ((void *) dados.valorVetor, &o, sizeof (int));
pthread_exit (0);
}
free (stringPedido);
memcpy ((void *) dados.valorVetor, &o, sizeof (int));
pthread_exit (0);
}
//Fim de erro de arquivo
//Mensagem de confirmacao que esta tudo ok
sprintf (stringPedido,
"OCTOPUS/1.0 001 OK\r\n/Arquivo:%s\r\n/Tamanho:%d/\r\n\r\n",
dados.nomeArquivo,calculaTamanhoArquivo (dados.nomeArquivo));
printf ("Mensagem sobre as informacoes:%s\n", stringPedido);
out_sz = strlen (stringPedido);
quantidadeEnviada =
sctp_sendmsg (dados.sock_fd, stringPedido, out_sz,
(struct sockaddr *) &dados.endereco,
sizeof (struct sockaddr), 0, 0,
dados.fluxo, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
free (stringPedido);
pthread_exit (0);
}
free (stringPedido);
//Tudo ok, enviando o arquivo para o cliente de arquivos
while (!(feof (arquivo)))
{
bzero (bufferArquivo, 200);
out_sz = fread (bufferArquivo, sizeof (char), 200, arquivo);
quantidadeEnviada =
sctp_sendmsg (dados.sock_fd, &bufferArquivo, out_sz,
(struct sockaddr *) &dados.endereco,
sizeof (struct sockaddr), 0, 0,
dados.fluxo, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
free (bufferArquivo);
fclose (arquivo);
pthread_exit (0);
}
}
//End while de envio do arquivo
fclose (arquivo);
//Liberando o fluxo que foi utilizado para enviar o arquivo
memcpy ((void *) dados.valorVetor, &o, sizeof (int));
pthread_exit (0);
160
Apêndice B. Código fonte do aplicativo Octopus
}
//Fim enviaArquivo
//----------------------------------------------------------------//Funcao para enviar os dados de solicitacao
//do arquivo a ser transferido
//----------------------------------------------------------------void
pedeArquivo (char *nomeArquivo, char *endereco, char *porta, char *tamanho)
{
int numeroSocket;
int quantidadeEnviada;
int out_sz;
char *stringPedido;
struct sockaddr_in enderecoPedido;
stringPedido = malloc (100);
bzero (stringPedido, 100);
sprintf (stringPedido, "GET /get/%s/ OCTOPUS/1.0\r\n\r\n", nomeArquivo);
printf ("A string de pedido eh:%s\n", stringPedido);
//Configurando o endereco para quem vai pedir
bzero (&enderecoPedido, sizeof (enderecoPedido));
enderecoPedido.sin_family = AF_INET;
inet_aton (endereco, &enderecoPedido.sin_addr);
enderecoPedido.sin_port = htons (atoi (porta));
printf
printf
printf
printf
printf
("\nEstou solicitando um arquivo\n");
("Nome do arquivo:%s\n", nomeArquivo);
("Endereco de contato:%s\n", inet_ntoa (enderecoPedido.sin_addr));
("Porta de contato:%d\n\n", ntohs (enderecoPedido.sin_port));
("Tamanho do arquivo:%s\n", tamanho);
//Tratar aqui a associacao com um no que jah estou associado
if (existeEnderecoLista (enderecoPedido) == 0)
{
associaPeer (enderecoPedido);
printf
("Me associei ao no que estava longe para transferir arquivo\n");
}
printf ("Jah estou associado\n");
numeroSocket =
associacoesAbertas[retornaIndiceListaEndereco
(enderecoPedido)].numeroSocket;
printf ("Numero do socket local do contato:%d\n", numeroSocket);
out_sz = strlen (stringPedido);
quantidadeEnviada =
sctp_sendmsg (numeroSocket, stringPedido, out_sz,
(struct sockaddr *) &enderecoPedido,
sizeof (struct sockaddr), 0, MSG_UNORDERED,
5, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem de pedido de arquivo\n");
}
printf ("Quantidade enviada para pedido de arquivo:%d\n",
quantidadeEnviada);
free (stringPedido);
return;
}
//fim de pedeArquivo
161
Apêndice B. Código fonte do aplicativo Octopus
//----------------------------------------------------------------//- Tratamento de mensagens //As mensagens que chegam ao no sao recebidas por esta funcao
//----------------------------------------------------------------void *
manipuladorMensagens (int *socketNumber)
{
int sock_fd = *socketNumber;
socklen_t len;
int msg_flags;
char *readbuf;
char *mensagemErro;
char nomeArquivo[100];
int out_sz;
int quantidadeRecebida;
int quantidadeEnviada;
int quantidadeEscrita;
struct sockaddr_in cliaddr;
struct sctp_sndrcvinfo sri;
len = sizeof (struct sockaddr_in);
readbuf = malloc (200);
mensagemErro = malloc (150);
printf ("\nO manipulador de mensagens foi aberto com o socket numero: %d\n",
sock_fd);
struct
struct
struct
struct
struct
PONG dadosPong;
CABECALHO ping;
QUERY corpo;
QUERYHIT resposta;
sockaddr_in END;
FILE *arquivo1;
FILE *arquivo2;
FILE *arquivo3;
pthread_t thread_servidor1;
pthread_t thread_servidor2;
pthread_t thread_servidor3;
int vetorEstadosTransferencia[3] = { 0, 0, 0 };
int vetorEstadosRecebimento[3] = { 0, 0, 0 };
int fluxo;
struct dadosPedido bufferEnvio;
//Aqui e o loop de direcionamento do tratamento das mensagens
while (1)
{
bzero (&sri, sizeof (sri));
bzero (readbuf, 200);
quantidadeRecebida = sctp_recvmsg (sock_fd, readbuf, 200,
(struct sockaddr *)
&cliaddr, &len, &sri,
&msg_flags);
if (quantidadeRecebida == (-1))
{
printf ("Falha ao receber a mensagem - encerrando thread\n");
removeEnderecoLista (sock_fd);
if (vetorEstadosRecebimento[0] != 0)
fclose (arquivo1);
if (vetorEstadosRecebimento[1] != 0)
fclose (arquivo2);
if (vetorEstadosRecebimento[2] != 0)
fclose (arquivo3);
free (readbuf);
pthread_exit (0);
}
162
Apêndice B. Código fonte do aplicativo Octopus
//Tratamento das mensagens PING
if ((sri.sinfo_stream) == 1)
{
bzero (&ping, sizeof (struct CABECALHO));
memcpy (&ping, readbuf, quantidadeRecebida);
//Se nao houve loop
if (trataLoop (ping.DescriptorID) == 0)
{
printf ("\nRecebi um PING\n");
printf ("Descriptor ID:%d\n", ping.DescriptorID);
printf ("TTL=%d\n", ping.TTL);
printf ("Hops=%d\n", ping.Hops);
printf ("Recebi do endereco:%s\n",
inet_ntoa (cliaddr.sin_addr));
incluiListaLoop (ping.DescriptorID, sock_fd,
cliaddr);
enviaPONG (cliaddr, ping, sock_fd);
trataPING (ping, cliaddr);
}
else
continue;
}
//senao joga fora o descritor
//fim de PING
//Tratamento das mensagens PONG
if ((sri.sinfo_stream) == 2)
{
bzero (&ping, sizeof (struct CABECALHO));
memcpy (&ping, readbuf, sizeof (struct CABECALHO));
bzero (&dadosPong, sizeof (dadosPong));
memcpy (&dadosPong,
(readbuf + sizeof (struct CABECALHO)),
sizeof (dadosPong));
bzero (&END, sizeof (struct sockaddr_in));
memcpy (&END.sin_addr, &dadosPong.endereco,
sizeof (int));
END.sin_port = htons (dadosPong.porta);
//Atualizando host cache
incluiHostCache (END);
printf
printf
printf
printf
("\nDados do PONG recebido\n");
("Quantidade:%d\n", quantidadeRecebida);
("ID do descritor:%d\n", ping.DescriptorID);
("Endereco IP do servente:%s\n",
inet_ntoa (END.sin_addr));
printf ("Porta do servente:%d\n", dadosPong.porta);
printf ("Quantidade de arquivos:%d\n",
dadosPong.numeroArquivos);
printf ("Quantidade de dados compartilhados:%f\n",
dadosPong.numeroKilobytes);
}
//fim de PONG
//Tratamento das mensagens QUERY
if ((sri.sinfo_stream) == 3)
{
bzero (&corpo, sizeof (struct QUERY));
memcpy (&corpo, readbuf, quantidadeRecebida);
//Se nao houve loop
if (trataLoop (corpo.DescriptorID) == 0)
{
incluiListaLoop (corpo.DescriptorID, sock_fd,
cliaddr);
trataQUERY (corpo, cliaddr, sock_fd);
}
163
Apêndice B. Código fonte do aplicativo Octopus
else
continue;
}
//senao joga fora o descritor
//fim de QUERY
//Tratamento das mensagens QUERYHIT
if ((sri.sinfo_stream) == 4)
{
bzero (&resposta, sizeof (resposta));
memcpy (&resposta, readbuf, quantidadeRecebida);
trataQUERYHIT (resposta);
}
//fim de QUERYHIT
//Tratamento da negociacao da transferencia de arquivo
if ((sri.sinfo_stream) == 5)
{
//Recebi um pedido
if (strstr (readbuf, "GET /get/") != NULL)
{
printf ("O outro no estah pedindo um arquivo\n");
bzero (&bufferEnvio, sizeof (struct dadosPedido));
retornaNomeArquivoGET (readbuf,
bufferEnvio.nomeArquivo);
bufferEnvio.endereco = cliaddr;
bufferEnvio.sock_fd = sock_fd;
if (vetorEstadosTransferencia[0] == 0)
{
fluxo = 6;
vetorEstadosTransferencia[0] = 1;
bufferEnvio.fluxo = fluxo;
bufferEnvio.valorVetor =
&vetorEstadosTransferencia[0];
pthread_create (&thread_servidor1, NULL,
(void *) enviaArquivo,
&bufferEnvio);
pthread_detach (thread_servidor1);
}
else if (vetorEstadosTransferencia[1] == 0)
{
fluxo = 7;
vetorEstadosTransferencia[1] = 1;
bufferEnvio.fluxo = fluxo;
bufferEnvio.valorVetor =
&vetorEstadosTransferencia[1];
pthread_create (&thread_servidor2, NULL,
(void *) enviaArquivo,
&bufferEnvio);
pthread_detach (thread_servidor2);
}
else if (vetorEstadosTransferencia[2] == 0)
{
fluxo = 8;
vetorEstadosTransferencia[2] = 1;
bufferEnvio.fluxo = fluxo;
bufferEnvio.valorVetor =
&vetorEstadosTransferencia[2];
pthread_create (&thread_servidor3, NULL,
(void *) enviaArquivo,
&bufferEnvio);
pthread_detach (thread_servidor3);
}
else
{
//Envia mensagem de erro - fluxos insuficientes
retornaNomeArquivoGET (readbuf, nomeArquivo);
bzero (mensagemErro, 150);
sprintf (mensagemErro,
164
Apêndice B. Código fonte do aplicativo Octopus
"OCTOPUS/1.0 002 ERRO\r\n/Arquivo:%s\r\n/Tamanho:%d/\r\n
Motivo:Todos os fluxos ocupados\r\n\r\n",
nomeArquivo, calculaTamanhoArquivo (nomeArquivo));
printf ("Enviando mensagem de erro:%s\n",
mensagemErro);
out_sz = strlen (mensagemErro);
quantidadeEnviada =
sctp_sendmsg (sock_fd, mensagemErro,
out_sz,
(struct sockaddr *)
&cliaddr,
sizeof (struct sockaddr),
0, MSG_UNORDERED, 5, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem de erro 002\n");
}
printf ("Quantidade enviada como erro:%d\n",
quantidadeEnviada);
}
//Fim de envio de mensagem de erro
}
//Fim de IF GET /get/
else if ((strstr (readbuf, "OCTOPUS/1.0 002 ERRO") != NULL)
|| (strstr (readbuf, "OCTOPUS/1.0 003 ERRO") != NULL))
{
printf ("\n%s", readbuf);
continue;
}
//Fim do recebimento de erros
}
//Fim de tratamento de transferencia de arquivos
//Tratamento dos fluxos usados para transferir arquivos
if ((sri.sinfo_stream) > 5)
{
//O outro no vai enviar o arquivo
//Enviou mensagem de OK
if (strstr (readbuf, "OCTOPUS/1.0 001 OK") != NULL)
{
char *nomeArquivo = malloc (100);
char *tamanhoArquivo = malloc (20);
retornaSeguinte (readbuf, 1, nomeArquivo);
retornaSeguinte (readbuf, 2, tamanhoArquivo);
printf ("Nome do arquivo:%s\n", nomeArquivo);
printf ("Tamanho do arquivo:%s\n", tamanhoArquivo);
char nomeArquivoCompleto[100];
bzero (nomeArquivoCompleto, 100);
strcat (nomeArquivoCompleto,
"./ArquivosCompartilhados/");
strcat (nomeArquivoCompleto, nomeArquivo);
strcat (nomeArquivoCompleto, ".novo");
if (sri.sinfo_stream == 6)
{
arquivo1 = fopen (nomeArquivoCompleto, "wb");
vetorEstadosRecebimento[0] =
atoi (tamanhoArquivo);
}
if (arquivo1 == NULL)
{
printf ("Deu problema ao abrir o arquivo\n");
vetorEstadosRecebimento[0] = 0;
165
Apêndice B. Código fonte do aplicativo Octopus
}
if (sri.sinfo_stream == 7)
{
arquivo2 = fopen (nomeArquivoCompleto, "wb");
vetorEstadosRecebimento[1] =
atoi (tamanhoArquivo);
}
if (arquivo2 == NULL)
{
printf ("Deu problema ao abrir o arquivo\n");
vetorEstadosRecebimento[1] = 0;
}
if (sri.sinfo_stream == 8)
{
arquivo3 = fopen (nomeArquivoCompleto, "wb");
vetorEstadosRecebimento[2] =
atoi (tamanhoArquivo);
}
if (arquivo3 == NULL)
{
printf ("Deu problema ao abrir o arquivo\n");
vetorEstadosRecebimento[2] = 0;
}
printf ("Arquivo %s aberto\n", nomeArquivoCompleto);
free (nomeArquivo);
free (tamanhoArquivo);
}
//fim de enviou mensagem de OK
else
//Escrevendo os dados no arquivo
{
printf ("Escrevi\n");
printf ("Quantidade de bytes que veio:%d\n",
quantidadeRecebida);
printf ("Fluxo:%d\n", sri.sinfo_stream);
if (sri.sinfo_stream == 6)
{
quantidadeEscrita =
fwrite (readbuf, sizeof (char),
quantidadeRecebida, arquivo1);
vetorEstadosRecebimento[0] -= quantidadeEscrita;
if (vetorEstadosRecebimento[0] == 0)
fclose (arquivo1);
}
if (sri.sinfo_stream == 7)
{
quantidadeEscrita =
fwrite (readbuf, sizeof (char),
quantidadeRecebida, arquivo2);
vetorEstadosRecebimento[1] -= quantidadeEscrita;
if (vetorEstadosRecebimento[1] == 0)
fclose (arquivo2);
}
}
}
if (sri.sinfo_stream == 8)
{
quantidadeEscrita =
fwrite (readbuf, sizeof (char),
quantidadeRecebida, arquivo3);
vetorEstadosRecebimento[2] -= quantidadeEscrita;
if (vetorEstadosRecebimento[2] == 0)
fclose (arquivo3);
}
}
//Fim de escrevendo os dados no arquivo
//Fim de tratamento de fluxos
//fim do while(1)
166
Apêndice B. Código fonte do aplicativo Octopus
return 0;
}
//fim do manipulador de mensagens
//----------------------------------------------------------------//Funcao que abre o servidor que aguarda novas associacoes
//----------------------------------------------------------------void *
abreServidor (struct sockaddr_in *serv)
{
int sock_fd;
int erro;
struct
struct
struct
struct
sockaddr_in servaddr;
sockaddr_in cliaddr;
sctp_sndrcvinfo sri;
sctp_event_subscribe evnts;
bzero (&servaddr, sizeof (servaddr));
servaddr = *serv;
printf ("\nAbrindo o Servidor\n");
printf ("No endereco:%s\n", inet_ntoa (servaddr.sin_addr));
printf ("Na porta:%d\n\n", ntohs (servaddr.sin_port));
//zerando o cache de ID de associacoes
iniciaListaLoop ();
//Cria o socket SCTP
sock_fd = socket (AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
if (sock_fd < 0)
{
printf ("Criacao do socket falhou\n");
exit (0);
}
//Inscrevendo nos eventos que devem ser notificados
bzero (&evnts, sizeof (evnts));
evnts.sctp_data_io_event = 1;
erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &evnts,
sizeof (evnts));
if (erro != 0)
{
printf ("Erro ao setar eventos\n");
exit (0);
}
//Definindo o tempo maximo de idle time de uma associacao para encerramento automatico
int close_time = 240;
erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_AUTOCLOSE,
&close_time, sizeof (close_time));
if (erro != 0)
{
printf ("Erro ao setar a opcao de close time\n");
exit (0);
}
//Definindo o numero de fluxos que entram e saem, assim como timeouts
struct sctp_initmsg initmsg;
initmsg.sinit_num_ostreams = 10;
initmsg.sinit_max_instreams = 10;
initmsg.sinit_max_attempts = 3;
initmsg.sinit_max_init_timeo = 30;
erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_INITMSG,
(char *) &initmsg,
sizeof (struct sctp_initmsg));
if (erro != 0)
{
167
Apêndice B. Código fonte do aplicativo Octopus
printf ("Erro ao setar a opcao de initmessage\n");
exit (0);
}
//Binding
erro = bind (sock_fd, (struct sockaddr *) &servaddr,
sizeof (servaddr));
if (erro != 0)
{
printf ("Erro ao dar o bind\n");
exit (0);
}
erro = listen (sock_fd, 10);
if (erro != 0)
{
printf ("Erro ao setar a entrar no listen\n");
exit (0);
}
socklen_t len;
pthread_t thread_servidor[20];
sctp_assoc_t IDassociacao;
int quantidadeEnviada;
int quantidadeRecebida;
int msg_flags;
int i = 0;
int SocketNovo[10];
char *readbuf;
len = sizeof (struct sockaddr_in);
readbuf = malloc (105);
while (1)
{
bzero (readbuf, 105);
bzero (&cliaddr, sizeof (cliaddr));
//recebendo a mensagem do cliente
//A partir daqui se estabelece uma associacao
quantidadeRecebida = sctp_recvmsg (sock_fd, readbuf, 105,
(struct sockaddr *)
&cliaddr, &len, &sri,
&msg_flags);
if (quantidadeRecebida == (-1))
{
printf ("Falha ao receber a mensagem\n");
continue;
}
//Dados para conferencia
printf ("Informacoes sobre o no requerente:\n");
printf ("Socket:%d\n", sock_fd);
printf ("Numero da stream:%d\n", sri.sinfo_stream);
printf ("Numero de bytes recebidos:%d\n", quantidadeRecebida);
printf ("Endereco remoto:%s\n", inet_ntoa (cliaddr.sin_addr));
printf ("Porta remota:%d\n", ntohs (cliaddr.sin_port));
printf ("Mensagem recebida:%s\n", readbuf);
//nao ha mais slots de associacao disponiveis
if (i == 21)
{
bzero (readbuf, 105);
sprintf (readbuf,
"500 ERROR - NO MORE AVAILABLE SLOTS\n\n");
quantidadeEnviada = sctp_sendmsg (sock_fd, readbuf,
quantidadeRecebida, (struct sockaddr *) &cliaddr,
sizeof (struct sockaddr), sri.sinfo_ppid, sri.sinfo_flags,
168
Apêndice B. Código fonte do aplicativo Octopus
sri.sinfo_stream, 0, 0);
//enviando devolta a mensagem
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
}
}
continue;
//fim de tratamento de casos de fim de slots disponiveis
//efetivando a associacao com outro no - handshake Gnutella
if (strcmp (readbuf, "GNUTELLA CONNECT/0.4\n\n") == 0)
{
printf ("GNUTELLA CONNECT/0.4 - Aceito\n");
//Sim, houve, envia a proxima leg
bzero (readbuf, 105);
sprintf (readbuf, "GNUTELLA OK\n\n");
quantidadeEnviada = sctp_sendmsg (sock_fd, readbuf,
quantidadeRecebida, (struct sockaddr *) &cliaddr,
sizeof (struct sockaddr), sri.sinfo_ppid,
sri.sinfo_flags, sri.sinfo_stream, 0, 0);
//enviando devolta a mensagem
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
continue;
}
printf ("\nEnviado o GNUTELLA OK\n");
printf ("Quantidade enviada:%d\n\n", quantidadeEnviada);
IDassociacao = sri.sinfo_assoc_id;
SocketNovo[i] = sctp_peeloff (sock_fd, IDassociacao);
if (SocketNovo[i] == (-1))
{
printf ("Falha ao receber no peeloff\n");
continue;
}
printf ("SOCKET NOVO:%d \n", SocketNovo[i]);
//Atualizando o cache de associacoes com a associacao vindoura
numeroDeAssociacoesAbertas++;
associacoesAbertas[numeroDeAssociacoesAbertas].
numeroSocket = SocketNovo[i];
associacoesAbertas[numeroDeAssociacoesAbertas].endereco =
cliaddr;
printf ("Associacao vindoura ok, e inclusa no cache de associacoes\n\n");
pthread_create (&thread_servidor[i], NULL,
(void *) manipuladorMensagens,
&SocketNovo[i]);
if (pthread_detach (thread_servidor[i]) == 0)
printf ("Detach da thread ok\n");
i++;
//Atualiza numero suportavel de slots de associacao
}
}
//Fim do if do GNUTELLA CONNECT
//fim do while 1
free (readbuf);
pthread_exit (0);
}
//Fim de abreServidor
//----------------------------------------------------------------//Funcao para fazer um pedido de associacao a outro no
//Retorna 1 em sucesso e 0 em falha
169
Apêndice B. Código fonte do aplicativo Octopus
//----------------------------------------------------------------int
associaPeer (struct sockaddr_in servaddr)
{
printf ("\nTentativa de associar peer\n");
printf ("O endereco a associar:%s\n",
inet_ntoa (servaddr.sin_addr));
printf ("A porta a associar:%d\n\n", ntohs (servaddr.sin_port));
int sock_fd;
int erro;
struct sockaddr_in enderecoEnviante;
struct sctp_event_subscribe evnts;
if (existeEnderecoLista (servaddr) == 1)
{
printf ("Voce ja possui uma associacao com esse no\n\n");
return 0;
}
if (numeroDeAssociacoesAbertas == 20)
{
printf ("Nao e possivel abrir mais associacoes\n");
return 0;
}
//Criando o socket
sock_fd = socket (AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
if (sock_fd < 0)
{
printf ("Criacao do socket falhou\n");
return 0;
}
//Inscricao de eventos para receber notificacoes
bzero (&evnts, sizeof (evnts));
evnts.sctp_data_io_event = 1;
erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &evnts,
sizeof (evnts));
if (erro != 0)
{
printf ("Erro a setar opcao do socket de initmessage\n");
return 0;
}
//Definindo numero de fluxos a utilizar
struct sctp_initmsg initmsg;
initmsg.sinit_num_ostreams = 10;
initmsg.sinit_max_instreams = 10;
initmsg.sinit_max_attempts = 3;
initmsg.sinit_max_init_timeo = 30;
erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_INITMSG,
(char *) &initmsg,
sizeof (struct sctp_initmsg));
if (erro != 0)
{
printf ("Erro a setar opcao do socket de initmessage\n");
return 0;
}
//Definindo close time automatico
int close_time = 240;
erro = setsockopt (sock_fd, IPPROTO_SCTP, SCTP_AUTOCLOSE,
&close_time, sizeof (close_time));
if (erro != 0)
{
printf ("Erro ao setar a opcao de close time\n");
return 0;
170
Apêndice B. Código fonte do aplicativo Octopus
}
struct sctp_sndrcvinfo sri;
int quantidadeEnviada;
int quantidadeRecebida;
int msg_flags;
int out_sz;
char *sendline;
socklen_t len;
len = sizeof (struct sockaddr_in);
sendline = malloc (50);
//Definindo o fluxo zero para envio de mensagens Gnutella Connect
bzero (&sri, sizeof (sri));
sri.sinfo_stream = 0;
bzero (sendline, sizeof (50));
sprintf (sendline, "GNUTELLA CONNECT/0.4\n\n");
out_sz = strlen (sendline);
printf ("Enviando o GNUTELLA CONNECT/0.4\n");
quantidadeEnviada = sctp_sendmsg (sock_fd, sendline, out_sz,
(struct sockaddr *) &servaddr,
sizeof (struct sockaddr), 0, 0,
sri.sinfo_stream, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem de GNUTELLA CONNECT\n");
return 0;
}
bzero (sendline, sizeof (50));
quantidadeRecebida = sctp_recvmsg (sock_fd, sendline, 50,
(struct sockaddr *)
&enderecoEnviante, &len, &sri,
&msg_flags);
if (quantidadeRecebida == (-1))
{
printf ("Falha ao receber a mensagem de GNUTELLA OK\n");
return 0;
}
//aqui eu vou preencher a lista de descritores de sockets
if (strcmp (sendline, "GNUTELLA OK\n\n") == 0)
{
numeroDeAssociacoesAbertas++;
associacoesAbertas[numeroDeAssociacoesAbertas].numeroSocket = sock_fd;
associacoesAbertas[numeroDeAssociacoesAbertas].endereco = servaddr;
printf ("Recebi o %s\n\n", sendline);
printf ("Entrando na thread com o socket de numero %d\n",sock_fd);
socketNovoParaAssociacoes[indiceSocketNovoAssociacoes] = sock_fd;
pthread_create (&threadCliente[indiceSocketNovoAssociacoes],
NULL, (void *) manipuladorMensagens,
&socketNovoParaAssociacoes
[indiceSocketNovoAssociacoes]);
if (pthread_detach(threadCliente[indiceSocketNovoAssociacoes]) == 0)
printf ("Detach da thread ok\n");
indiceSocketNovoAssociacoes++;
free (sendline);
return 1;
}
171
Apêndice B. Código fonte do aplicativo Octopus
else
{
close (sock_fd);
printf ("Associacao nao foi aceita\n");
free (sendline);
return 0;
}
}
//fim de associaPeer
//----------------------------------------------------------------//Tratamento das mensangens PING
//----------------------------------------------------------------void
trataPING (struct CABECALHO mensagem, struct sockaddr_in cliaddr)
{
printf ("\nEstou tratando um PING\n");
//Esse trecho eh para propagar os PINGs para outros nos
int out_sz;
int quantidadeEnviada;
struct sockaddr_in servaddr;
int i;
out_sz = sizeof (mensagem);
//Descarta o pacote se o TTL esgotou
if ((mensagem.TTL) == 0)
return;
else
{
(mensagem.TTL)--;
(mensagem.Hops)++;
}
//para cada associacao que foi feita, envia um QUERY,
//menos para aquela que enviou
for (i = 1; i <= numeroDeAssociacoesAbertas; i++)
{
servaddr = associacoesAbertas[i].endereco;
if (enderecoIgual (servaddr, cliaddr) != 1)
{
quantidadeEnviada =
sctp_sendmsg (associacoesAbertas[i].numeroSocket,
&mensagem, out_sz,
(struct sockaddr *) &servaddr,
sizeof (struct sockaddr), 0,
MSG_UNORDERED, 1, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
}
}
}
printf ("Quantidade de bytes de PING enviada:%d\n",
quantidadeEnviada);
printf ("Para o endereco:%s\n",
inet_ntoa (servaddr.sin_addr));
}
//fim do If
//fim do for de propagacao de PINGs
//Fim trataPING
//----------------------------------------------------------------//Funcao para o envio de PINGs aos nos diretamente associados
//----------------------------------------------------------------void
enviaPING (void)
172
Apêndice B. Código fonte do aplicativo Octopus
{
int out_sz;
int i;
int quantidadeEnviada;
struct sockaddr_in servaddr;
struct CABECALHO mensagem;
//Nao pode fazer enviar PING se nao esta associado a ninguem
if (numeroDeAssociacoesAbertas == 0)
{
printf ("Nenhuma associacao efetivada\n");
return;
}
//Preenchendo o payload
//gerando um numero randomico para o identificador da mensagem
bzero (&mensagem, sizeof (mensagem));
srand (time (0));
mensagem.DescriptorID = rand ();
mensagem.TTL = 0;
mensagem.Hops = 0;
printf
printf
printf
printf
("\nEnviando PING\n");
("DescriptorID:%d\n", mensagem.DescriptorID);
("TTL:%d\n", mensagem.TTL);
("Hops:%d\n", mensagem.Hops);
//quantidade de dados que vai enviar atraves do socket
out_sz = sizeof (mensagem);
//para cada associacao aberta, envia um PING
for (i = 1; i <= numeroDeAssociacoesAbertas; i++)
{
servaddr = associacoesAbertas[i].endereco;
quantidadeEnviada =
sctp_sendmsg (associacoesAbertas[i].numeroSocket,
&mensagem, out_sz,
(struct sockaddr *) &servaddr,
sizeof (struct sockaddr), 0,
MSG_UNORDERED, 1, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar PING\n");
}
printf ("Quantidade Enviada:%d\n", quantidadeEnviada);
printf ("Para o endereco:%s\n",
inet_ntoa (servaddr.sin_addr));
//Para identificacao de quando a mensagem voltar, para exibir os resultados
incluiListaLoop (mensagem.DescriptorID, 0, meuaddr);
}
}
//fim do for de envio de mensagens PING
//Fim do enviaPING
//----------------------------------------------------------------//Envia uma mensagem de PONG em resposta ao PING
//----------------------------------------------------------------void
enviaPONG (struct sockaddr_in enderecoCliente,
struct CABECALHO cabeca, int sock_fd)
{
int quantidadeEnviada;
int out_sz;
struct PONG resposta;
struct CABECALHO mensagemResp;
char *buffer;
buffer = malloc (200);
173
Apêndice B. Código fonte do aplicativo Octopus
bzero (buffer, 200);
//Setar os dados do cabecalho de resposta
bzero (&mensagemResp, sizeof (mensagemResp));
mensagemResp.DescriptorID = cabeca.DescriptorID;
mensagemResp.TTL = cabeca.Hops + 1;
mensagemResp.Hops = 0;
//Setar os dados da resposta
bzero (&resposta, sizeof (struct PONG));
resposta.porta = (short) ntohs (meuaddr.sin_port);
memcpy (&resposta.endereco, &meuaddr.sin_addr, sizeof (int));
resposta.numeroArquivos = contaArquivosDiretorio ();
resposta.numeroKilobytes = (float) (tamanhoDiretorio () / 1000);
//Passando pro buffer o cabecalho + resposta
memcpy (buffer, &mensagemResp, sizeof (struct CABECALHO));
memcpy (buffer + sizeof (struct CABECALHO), &resposta,
sizeof (struct PONG));
printf
printf
printf
printf
printf
printf
("\nEnviando PONG\n");
("O ID do descritor:%d\n", mensagemResp.DescriptorID);
("O TTL:%d\n", mensagemResp.TTL);
("O Hop:%d\n", mensagemResp.Hops);
("A quantidade de arquivos:%d\n", resposta.numeroArquivos);
("Tamanho do diretorio:%f\n", resposta.numeroKilobytes);
out_sz = sizeof (struct CABECALHO) + sizeof (struct PONG);
//Enviar os dados da mensagem PONG
quantidadeEnviada = sctp_sendmsg (sock_fd, buffer, out_sz,
(struct sockaddr *)
&enderecoCliente,
sizeof (struct sockaddr), 0,
MSG_UNORDERED, 2, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
}
printf ("Quantidade enviada como PONG:%d\n", quantidadeEnviada);
//Enviando os PONGs com os enderecos do host cache
struct sockaddr_in endereco;
hostCacheFile = fopen ("./hostCacheFile.dat", "rb");
if (hostCacheFile == NULL)
{
printf ("Erro ao abrir o arquivo do Host Cache\n\n");
free (buffer);
return;
}
while (!feof (hostCacheFile))
{
bzero (&endereco, sizeof (endereco));
fread (&endereco, sizeof (endereco), 1, hostCacheFile);
if (strcmp (inet_ntoa (endereco.sin_addr), "0.0.0.0") != 0)
{
//Setar os dados da resposta
bzero (&resposta, sizeof (struct PONG));
resposta.porta = (short) ntohs (endereco.sin_port);
memcpy (&resposta.endereco, &endereco.sin_addr,
sizeof (int));
resposta.numeroArquivos = 0;
resposta.numeroKilobytes = 0;
//Passando pro buffer cabecalho+resposta
memcpy (buffer + sizeof (struct CABECALHO), &resposta,
174
Apêndice B. Código fonte do aplicativo Octopus
sizeof (struct PONG));
printf
printf
printf
printf
printf
printf
printf
printf
("\nEnviando PONG\n");
("O ID do descritor:%d\n", mensagemResp.DescriptorID);
("O TTL:%d\n", mensagemResp.TTL);
("O Hop:%d\n", mensagemResp.Hops);
("A quantidade de arquivos:%d\n", resposta.numeroArquivos);
("Tamanho do diretorio:%f\n", resposta.numeroKilobytes);
("Endereco:%s\n", inet_ntoa (endereco.sin_addr));
("Porta:%d\n\n", ntohs (endereco.sin_port));
quantidadeEnviada =
sctp_sendmsg (sock_fd, buffer, out_sz,
(struct sockaddr *)
&enderecoCliente,
sizeof (struct sockaddr), 0,
MSG_UNORDERED, 2, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
}
}
printf ("Quantidade enviada como PONG:%d\n",
quantidadeEnviada);
}
//Fim do if
//fim do While(!feof)
fclose (hostCacheFile);
free (buffer);
return;
}
//Fim de enviaPONG
//----------------------------------------------------------------//Envia a QUERY solicitada pelo usuario
//----------------------------------------------------------------void
enviaQUERY (char *consulta)
{
int out_sz;
int i;
int quantidadeEnviada;
struct sockaddr_in servaddr;
struct QUERY cabeca;
//Nao pode fazer consulta se nao esta associado a ninguem
if (numeroDeAssociacoesAbertas == 0)
{
printf ("Nenhuma associacao efetivada\n");
return;
}
//Preenchendo o payload
//Gerando um numero randomico para o identificador do descritor
bzero (&cabeca, sizeof (cabeca));
srand (time (0));
cabeca.DescriptorID = rand ();
cabeca.TTL = 4;
cabeca.Hops = 0;
strcpy (cabeca.consulta, consulta);
printf
printf
printf
printf
printf
printf
("Enviando QUERY do usuario\n");
("DescriptorID:%d\n", cabeca.DescriptorID);
("TTL:%d\n", cabeca.TTL);
("Hops:%d\n", cabeca.Hops);
("Consulta da cabeca %s\n\n", cabeca.consulta);
("Consulta sem cabeca %s\n\n", consulta);
//quantidade de dados que vai enviar atraves do socket
out_sz = tamanhoCabecalho + strlen (consulta);
175
Apêndice B. Código fonte do aplicativo Octopus
//para cada associacao que foi feita, envia uma QUERY
for (i = 1; i <= numeroDeAssociacoesAbertas; i++)
{
servaddr = associacoesAbertas[i].endereco;
quantidadeEnviada =
sctp_sendmsg (associacoesAbertas[i].numeroSocket,
&cabeca, out_sz,
(struct sockaddr *) &servaddr,
sizeof (struct sockaddr), 0,
MSG_UNORDERED, 3, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
}
printf ("Quantidade Enviada:%d\n", quantidadeEnviada);
printf ("Para o endereco:%s\n",
inet_ntoa (servaddr.sin_addr));
//Para identificacao se um QUERYHIT voltar
//Inclui no cache de ID de mensagens
incluiListaLoop (cabeca.DescriptorID, 0, meuaddr);
}
}
//fim do for de envio de mensagens QUERY
//Fim do enviaQUERY
//----------------------------------------------------------------//Quando satisfaz a consulta, envia um QUERYHIT
//Inicio de backtrack
//----------------------------------------------------------------void
enviaQUERYHIT (char nomeArquivo[], struct QUERY corpo,
struct sockaddr_in endereco, int sock_fd)
{
int quantidadeEnviada;
struct QUERYHIT resposta;
int out_sz;
//Setar os dados da resposta
bzero (&resposta, sizeof (resposta));
resposta.DescriptorID = corpo.DescriptorID;
resposta.TTL = corpo.Hops;
resposta.Hops = 0;
resposta.endereco = meuaddr;
resposta.tamanhoArquivo =
(long) calculaTamanhoArquivo (nomeArquivo);
strcpy (resposta.nomeArquivo, nomeArquivo);
printf
printf
printf
printf
printf
printf
printf
("\nEnviando QUERYHIT\n");
("Nome do arquivo encontrado:%s\n", resposta.nomeArquivo);
("O ID da mensagem que veio:%d\n", resposta.DescriptorID);
("O TTL da mensagem que vai:%d\n", resposta.TTL);
("O tamanho do arquivo (bytes):%d\n", (int) resposta.tamanhoArquivo);
("O Hop do descritor que veio:%d\n", corpo.Hops);
("O endereco para o qual devo responder:%s\n",
inet_ntoa (endereco.sin_addr));
printf ("Porta para a qual devo responder:%d\n",
endereco.sin_port);
printf ("O meu endereco:%s\n",
inet_ntoa (resposta.endereco.sin_addr));
printf ("Minha porta:%d\n\n",
ntohs (resposta.endereco.sin_port));
out_sz = tamanhoQUERYHIT + strlen (resposta.nomeArquivo);
//Enviar os dados de resposta
quantidadeEnviada = sctp_sendmsg (sock_fd, &resposta, out_sz,
(struct sockaddr *) &endereco,
176
Apêndice B. Código fonte do aplicativo Octopus
sizeof (struct sockaddr), 0,
MSG_UNORDERED, 4, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
}
printf ("Quantidade enviada como QUERYHIT:%d\n",
quantidadeEnviada);
}
//Fim de enviaQUERYHIT
//----------------------------------------------------------------//Funcao que trata as mensagens QUERYHIT
//Ou exibe a resposta na tela ou efetua o backtrack
//----------------------------------------------------------------void
trataQUERYHIT (struct QUERYHIT resposta)
{
int indiceLista;
int out_sz;
int quantidadeEnviada;
char endereco[30];
char porta[10];
char tamanho[50];
char *texto[] =
{ resposta.nomeArquivo, endereco, porta, tamanho };
printf
printf
printf
printf
printf
printf
printf
("\nRECEBI UM QUERYHIT\n");
("Nome do arquivo que encontrei:%s\n", resposta.nomeArquivo);
("O ID do descritor que veio:%d\n", resposta.DescriptorID);
("O TTL da mensagem:%d\n", (resposta.TTL - 1));
("O Hop do descritor que veio:%d\n", (resposta.Hops + 1));
("O tamanho do arquivo:%d\n", (int) resposta.tamanhoArquivo);
("O endereco do sujeito que tem o arquivo:%s\n",
inet_ntoa (resposta.endereco.sin_addr));
printf ("A porta do sujeito:%d\n\n",
ntohs (resposta.endereco.sin_port));
strcpy (endereco, inet_ntoa (resposta.endereco.sin_addr));
sprintf (porta, "%d", ntohs (resposta.endereco.sin_port));
sprintf (tamanho, "%d", (int) resposta.tamanhoArquivo);
//Caso a resposta seja deste no mesmo
//Exibe as informacoes na lista da interface grafica
if (aRespostaMinha (resposta.DescriptorID) == 1)
{
adicionaResposta (texto);
linhaListaLocal = 0;
printf ("Essa resposta pertence a este no\n");
}
else
//resposta de outro no
{
printf ("A resposta e de outro computador - Backtrack\n");
indiceLista = retornaIndiceLista (resposta.DescriptorID);
resposta.TTL--;
resposta.Hops++;
out_sz = tamanhoQUERYHIT + strlen (resposta.nomeArquivo);
//Enviar os dados do backtrack
quantidadeEnviada =
sctp_sendmsg (listaDeIDs[indiceLista].NumeroSocket,
&resposta, out_sz,
(struct sockaddr *)
&(listaDeIDs[indiceLista].endereco),
sizeof (struct sockaddr), 0,
MSG_UNORDERED, 4, 0, 0);
if (quantidadeEnviada == (-1))
{
177
Apêndice B. Código fonte do aplicativo Octopus
printf ("Falha ao enviar a mensagem\n");
}
}
}
//Fim do else de resposta de outro no
//Fim trataQUERYHIT
//----------------------------------------------------------------//Funcao que trata as mensagens do tipo QUERY
//----------------------------------------------------------------void
trataQUERY (struct QUERY corpo, struct sockaddr_in cliaddr,
int sock_fd)
{
printf ("\nRECEBI UMA QUERY\n");
printf ("DescriptorID:%d\n", corpo.DescriptorID);
printf ("TTL:%d\n", corpo.TTL);
printf ("Hops:%d\n", corpo.Hops);
printf ("Consulta:%s\n", corpo.consulta);
printf ("Enviado por:%s\n\n", inet_ntoa (cliaddr.sin_addr));
//No caso de um dos arquivos satisfazer a QUERY
//Envia um QUERYHIT
struct direct **arquivos;
int count;
int i;
count = scandir ("./ArquivosCompartilhados", &arquivos, NULL,
NULL);
printf ("\nMeus Arquivos sao:\n");
for (i = 3; i < count + 1; ++i)
{
printf ("%s\n", arquivos[i - 1]->d_name);
if (strstr (arquivos[i - 1]->d_name, corpo.consulta) != NULL)
{
printf ("Este arquivo satisfaz:%s\n",
arquivos[i - 1]->d_name);
enviaQUERYHIT (arquivos[i - 1]->d_name, corpo, cliaddr,
sock_fd);
}
}
//Propagando a consulta para os outros nos
int out_sz;
int quantidadeEnviada;
struct sockaddr_in servaddr;
out_sz = tamanhoCabecalho + strlen (corpo.consulta);
//Descarta o pacote se o TTL esgotou
if ((corpo.TTL) == 0)
return;
else
{
(corpo.TTL)--;
(corpo.Hops)++;
}
//Para cada associacao no cache de associacoes
//envia uma QUERY, menos para aquela que enviou a consulta
for (i = 1; i <= numeroDeAssociacoesAbertas; i++)
{
servaddr = associacoesAbertas[i].endereco;
if (enderecoIgual (servaddr, cliaddr) != 1)
{
quantidadeEnviada =
sctp_sendmsg (associacoesAbertas[i].numeroSocket,
&corpo, out_sz,
(struct sockaddr *) &servaddr,
178
Apêndice B. Código fonte do aplicativo Octopus
sizeof (struct sockaddr), 0,
MSG_UNORDERED, 3, 0, 0);
if (quantidadeEnviada == (-1))
{
printf ("Falha ao enviar a mensagem\n");
}
}
}
printf ("Quantidade da QUERY enviada:%d\n",
quantidadeEnviada);
printf ("Para o endereco:%s\n",
inet_ntoa (servaddr.sin_addr));
}
//fim do If
//fim do for de envio de mensagens QUERY
//Fim trataQUERY
//----------------------------------------------------------------------------//Compara se dois enderecos IP sao iguais
//Retorna 1 se for igual, retorna 0 caso contrario
//----------------------------------------------------------------------------int
enderecoIgual (struct sockaddr_in endereco1,
struct sockaddr_in endereco2)
{
char endereco1Texto[16];
char endereco2Texto[16];
bzero (endereco1Texto, 16);
bzero (endereco2Texto, 16);
strcpy (endereco1Texto, inet_ntoa (endereco1.sin_addr));
strcpy (endereco2Texto, inet_ntoa (endereco2.sin_addr));
if (strcmp (endereco1Texto, endereco2Texto) == 0)
return 1;
else
return 0;
}
//Fim de enderecoIgual
//----------------------------------------------------------------------------//Checa se jah existe uma associacao dessa no cache de associacoes
//Retorna 1 se existe, e 0 caso contrario
//----------------------------------------------------------------------------int
existeEnderecoLista (struct sockaddr_in endereco)
{
int i;
for (i = 1; i <= numeroDeAssociacoesAbertas; i++)
{
if (enderecoIgual (associacoesAbertas[i].endereco, endereco)
== 1)
return 1;
}
return 0;
}
//fim existeEnderecoLista
//----------------------------------------------------------------------------//Checa se jah existe um socket no cache de associacoes
//Retorna 1 se existe, e 0 caso contrario
//----------------------------------------------------------------------------int
existeSocketLista (int numSocket)
{
int i;
for (i = 1; i <= numeroDeAssociacoesAbertas; i++)
{
179
Apêndice B. Código fonte do aplicativo Octopus
if (numSocket == (associacoesAbertas[i].numeroSocket))
return 1;
}
return 0;
}
//fim de existeSocketLista
//----------------------------------------------------------------------------//Remove um endereco do cache de associacoes
//Se sucesso, retorna 1, senao 0
//----------------------------------------------------------------------------int
removeEnderecoLista (int sock_fd)
{
if (numeroDeAssociacoesAbertas == 0)
return 0;
if (numeroDeAssociacoesAbertas == 1)
{
numeroDeAssociacoesAbertas--;
printf ("Removi no do cache de associacoes\n");
return 1;
}
int i;
for (i = 1; i <= numeroDeAssociacoesAbertas; i++)
{
if (sock_fd == (associacoesAbertas[i].numeroSocket))
{
if (i == numeroDeAssociacoesAbertas)
{
numeroDeAssociacoesAbertas--;
printf ("Removi no do cache de associacoes\n");
return 1;
}
else
{
associacoesAbertas[i] =
associacoesAbertas[numeroDeAssociacoesAbertas];
numeroDeAssociacoesAbertas--;
printf ("Removi\n");
return 1;
}
}
//fim do if
}
//fim do for
return 0;
}
//fim removeEnderecoLista
//----------------------------------------------------------------//Imprime as associacoes estabelecidas e ativas
//----------------------------------------------------------------void
imprimeAssociacoesAbertas (void)
{
int i;
printf ("CACHE DE ASSOCIACOES\n");
for (i = 1; i <= numeroDeAssociacoesAbertas; i++)
{
printf ("\nAssociacao numero:%d\n", i);
printf ("Descritor de socket de numero:%d\n",
associacoesAbertas[i].numeroSocket);
printf ("Endereco:%s\n",
inet_ntoa (associacoesAbertas[i].endereco.sin_addr));
printf ("Porta:%d\n\n",
ntohs (associacoesAbertas[i].endereco.sin_port));
}
180
Apêndice B. Código fonte do aplicativo Octopus
return;
}
//fim de imprimeAssociacoesAbertas
//----------------------------------------------------------------//Iniciacao do cache de ID de mensagens
//----------------------------------------------------------------void
iniciaListaLoop ()
{
int i;
for (i = 0; i <= 200; i++)
{
listaDeIDs[i].ID = 0;
}
}
//fim iniciaListaLoop
//----------------------------------------------------------------//Funcao para conferir se uma mensagem efetuou loop dado o ID
//Retorna 0 caso nao efetuou um loop e 1 caso tenha efetuado
//----------------------------------------------------------------int
trataLoop (int ID)
{
int i;
for (i = 0; i <= 200; i++)
{
if (listaDeIDs[i].ID == ID)
return 1;
}
return 0;
}
//fim trataLoop
//----------------------------------------------------------------//Retorna 1 caso conste o ID na lista e ainda tenha saido daqui
//Encerra o processo de backtrack
//----------------------------------------------------------------int
aRespostaMinha (int ID)
{
int i;
for (i = 0; i <= 200; i++)
{
if ((listaDeIDs[i].ID == ID)
&& (enderecoIgual (listaDeIDs[i].endereco, meuaddr) ==
1))
return 1;
}
return 0;
}
//fim aRespostaMinha
//----------------------------------------------------------------//Caso o DescriptorID esteja na lista
//retorna a posicao dele dentro da lista, senao retorna 500
//----------------------------------------------------------------int
retornaIndiceLista (int ID)
{
int i;
for (i = 0; i <= 200; i++)
{
if (listaDeIDs[i].ID == ID)
return i;
}
181
Apêndice B. Código fonte do aplicativo Octopus
return 500;
}
//fim retornaIndiceLista
//----------------------------------------------------------------//Caso o endereco exista no cache de associacoes
//retorna o indice dele dentro do cache
//----------------------------------------------------------------int
retornaIndiceListaEndereco (struct sockaddr_in endereco)
{
int i;
for (i = 0; i <= 200; i++)
{
if (enderecoIgual (endereco, associacoesAbertas[i].endereco)
== 1)
return i;
}
return 500;
}
//fim retornaIndiceListaEndereco
//----------------------------------------------------------------//Adiciona um numero no cache de ID de associacoes
//----------------------------------------------------------------void
incluiListaLoop (int ID, int NumeroSocket,
struct sockaddr_in endereco)
{
listaDeIDs[indiceListaLoop].ID = ID;
listaDeIDs[indiceListaLoop].NumeroSocket = NumeroSocket;
listaDeIDs[indiceListaLoop].endereco = endereco;
indiceListaLoop++;
if (indiceListaLoop == 201)
indiceListaLoop = 0;
}
//fim incluiListaLoop
//----------------------------------------------------------------//Adiciona as respostas QUERYHIT na interface grafica
//----------------------------------------------------------------void
adicionaResposta (char *texto[])
{
gtk_clist_prepend (GTK_CLIST (listaRemota), texto);
gtk_widget_show (listaRemota);
return;
}
//fim adicionaResposta
//----------------------------------------------------------------//Calcula o tamanho, em bytes, do repositorio de arquivos
//----------------------------------------------------------------int
tamanhoDiretorio (void)
{
struct direct **arquivos;
int count;
int tamanhoFinal = 0;
int i;
char nomeArquivo[100];
count = scandir ("./ArquivosCompartilhados", &arquivos, NULL,
NULL);
for (i = 2; i < count; i++)
{
bzero (nomeArquivo, 100);
strcpy (nomeArquivo, "");
182
Apêndice B. Código fonte do aplicativo Octopus
strcat (nomeArquivo, arquivos[i]->d_name);
tamanhoFinal = tamanhoFinal + calculaTamanhoArquivo (nomeArquivo);
}
return tamanhoFinal;
}
//fim tamanho Diretorio
//----------------------------------------------------------------//Checa se um determinado endereco existe no host cache
//Retorna 1 se existe e 0 se naum existe
//----------------------------------------------------------------int
existeHostCache (struct sockaddr_in enderecoComparar)
{
struct sockaddr_in endereco;
hostCacheFile = fopen ("./hostCacheFile.dat", "rb");
if (hostCacheFile == NULL)
{
printf ("Erro ao abrir o arquivo do Host Cache\n\n");
return 0;
}
while (!feof (hostCacheFile))
{
bzero (&endereco, sizeof (endereco));
fread (&endereco, sizeof (endereco), 1, hostCacheFile);
if (enderecoIgual (endereco, enderecoComparar) == 1)
{
fclose (hostCacheFile);
return 1;
}
}
fclose (hostCacheFile);
return 0;
}
//fim de existeHostCache
//----------------------------------------------------------------//Inclui um endereco no host cache
//retorna 1 em sucesso ou 0 em falha
//----------------------------------------------------------------int
incluiHostCache (struct sockaddr_in enderecoIncluir)
{
struct sockaddr_in endereco = enderecoIncluir;
if (existeHostCache (endereco) == 1)
return 0;
int erro;
hostCacheFile = fopen ("./hostCacheFile.dat", "ab");
if (hostCacheFile == NULL)
{
printf ("Erro ao abrir o arquivo do Host Cache\n\n");
return 0;
}
erro = fwrite (&endereco, sizeof (endereco), 1, hostCacheFile);
if (erro == 0)
{
printf ("Erro ao inserir um arquivo no host cache\n");
fclose (hostCacheFile);
return 0;
}
fclose (hostCacheFile);
return 1;
183
Apêndice B. Código fonte do aplicativo Octopus
}
//fim incluiHostCache
//----------------------------------------------------------------//Funcao para imprimir os enderecos do Host Cache
//----------------------------------------------------------------void
imprimeHostCache (void)
{
struct sockaddr_in endereco;
hostCacheFile = fopen ("./hostCacheFile.dat", "rb");
if (hostCacheFile == NULL)
{
printf ("Erro ao abrir o arquivo do Host Cache\n\n");
return;
}
printf ("\nImprimindo HOST CACHE\n\n");
while (!feof (hostCacheFile))
{
bzero (&endereco, sizeof (endereco));
fread (&endereco, sizeof (endereco), 1, hostCacheFile);
if (strcmp (inet_ntoa (endereco.sin_addr), "0.0.0.0") != 0)
{
printf ("Endereco:%s\n", inet_ntoa (endereco.sin_addr));
printf ("Porta:%d\n\n", ntohs (endereco.sin_port));
}
}
fclose (hostCacheFile);
}
//fim de imprimeHostCache
//----------------------------------------------------------------//Funcao para ativar a associacao com todos os enderecos do
//host cache
//----------------------------------------------------------------void
associaHostCache (void)
{
int erro;
struct sockaddr_in endereco;
hostCacheFile = fopen ("./hostCacheFile.dat", "rb");
if (hostCacheFile == NULL)
{
printf ("Erro ao abrir o arquivo do Host Cache\n\n");
return;
}
while (!feof (hostCacheFile))
{
bzero (&endereco, sizeof (endereco));
fread (&endereco, sizeof (endereco), 1, hostCacheFile);
if (strcmp (inet_ntoa (endereco.sin_addr), "0.0.0.0") != 0)
{
printf ("Tentando se associar ao no do host cache:\n");
printf ("Endereco:%s\n", inet_ntoa (endereco.sin_addr));
printf ("Porta:%d\n", ntohs (endereco.sin_port));
erro = associaPeer (endereco);
if (erro == 0)
printf ("Erro ao associar a este no\n\n");
else
printf ("Sucesso ao associar a este no\n\n");
}
}
//fim do while feof
184
Apêndice B. Código fonte do aplicativo Octopus
fclose (hostCacheFile);
}
//Fim de associaHostCache
//----------------------------------------------------------------//Calcula o tamanho de um determinado arquivo
//----------------------------------------------------------------int
calculaTamanhoArquivo (char *nomeArquivo)
{
char nomeArquivoCompleto[120];
int tamanhoArquivo = 0;
int n;
FILE *arquivo;
char buffer[200];
bzero (nomeArquivoCompleto, 120);
strcat (nomeArquivoCompleto, "./ArquivosCompartilhados/");
strcat (nomeArquivoCompleto, nomeArquivo);
arquivo = fopen (nomeArquivoCompleto, "rb");
if (arquivo == NULL)
{
printf ("Deu problema ao abrir o arquivo\n");
pthread_exit (0);
}
while (!(feof (arquivo)))
{
bzero (buffer, 200);
n = fread (buffer, sizeof (char), 200, arquivo);
tamanhoArquivo = tamanhoArquivo + n;
}
//End while
fclose (arquivo);
return tamanhoArquivo;
}
//Fim calculaTamanhoArquivo
//----------------------------------------------------------------//Funcao para separar os tokiens do servidor HTTP
//----------------------------------------------------------------//Retorna o nome do arquivo de pedido
void
retornaNomeArquivoGET (char *entrada, char *saida)
{
int i = 0;
int j = 0;
int barra = 0;
while (entrada[i] != ’\0’)
{
if (entrada[i] == ’/’)
{
barra++;
i++;
continue;
}
if (barra == 2)
{
saida[j] = entrada[i];
j++;
}
i++;
}
saida[j] = ’\0’;
185
Apêndice B. Código fonte do aplicativo Octopus
}
//fim de retornaNomeArquivoGET
//Retorna o nome do arquivo da segunda mensagem de negociacao
void
retornaSeguinte (char *entrada, int doisPontos, char *saida)
{
int i = 0;
int j = 0;
int doisPontosInternos = 0;
while (entrada[i] != ’\0’)
{
if (entrada[i] == ’:’)
{
doisPontosInternos++;
i++;
}
if (doisPontosInternos == doisPontos)
{
saida[j] = entrada[i];
j++;
if (entrada[i + 1] == ’\r’)
break;
}
i++;
}
saida[j] = ’\0’;
}
//fim de retornaSeguinte
//----------------------------------------------------------------//Funcao para contar o numero de arquivos num diretorio
//----------------------------------------------------------------int
contaArquivosDiretorio (void)
{
struct direct **arquivos;
int count;
count = scandir ("./ArquivosCompartilhados", &arquivos, NULL,
NULL);
count = count - 2;
return count;
}
//Fim de contaArquivosDiretorio
//----------------------------------------------------------------//Fim de SERVENTE.c
//-----------------------------------------------------------------
186
Anexo A
A interface gráfica do aplicativo NIST Net
O aplicativo NIST Net consiste de duas partes: um módulo de kernel carregável,
que se conecta ao código de kernel que implementa as funções de rede e do relógio
do sistema, além de exportar uma série de APIs; um conjunto de interfaces que
utilizam as APIs para a configuração e o controle das operações do emulador.
Estas interfaces podem ser usadas em modo texto, ou em modo gráfico. A
interface gráfica, como mostrada na figura a seguir, torna simples a configuração
dos parâmetros do aplicativo.
Os campos “Source” e “Dest” são configurados com os endereços IP de origem e
destino, respectivamente, que identificam os pacotes que devem ser interceptados e
tratados pelo aplicativo. Podem ser configurados os parâmetros de atraso e variação
de atraso de pacotes, limitação de largura de banda, taxa e variação da taxa de
perda, dentre outros.
187