Consolida documentação e plugins de referência ao núcleo atual.

Alinha mpChat e screenCast a ConnectionManager/GuiManager, adiciona docs em docs/ e README de plugins; remove artefatos de build do índice.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Gabriel Felipe Souza SantosandCursor committed 2026-10-13 21:20:20 -03:00
1 parent d92708a6e9
commit f02acaf131
61 files changed
+1408 -758

No files matched your search

+4
View File
@@ -1,9 +1,13 @@
/pitiupi/build/
/pitiupi/dist/
/pitiupi/plugins/*.jar
/pitiupi.chat/build/
/pitiupi.chat/dist/
/pitiupi.chat/nbproject/private/
/pitiupi.screenCast/build/
/pitiupi.screenCast/dist/
/pitiupi.mpChat/out/
/pitiupi.mpChat/dist/
**/built-jar.properties
.idea/
+40 -19
View File
@@ -1,29 +1,50 @@
# pitiupi
Ferramenta p2p para práticas em rede
Ferramenta **P2P** para práticas de Redes de Computadores: núcleo em Java com arquitetura de **plugins** (chat, compartilhamento de tela, exemplos multi-protocolo).
Este repositório tem 3 projetos Java que são independentes:
- A ferramenta pitiupi (P2P)
- Um plugin de Chat
- Um plugin de ScreenCast
**Documentação completa:** [docs/README.md](docs/README.md)
Cada um destes projetos deve ser compilado individualmente. Para isso, use a ferramenta ant no diretório do projeto.
Ex:
## Pré-requisitos
| Ferramenta | Uso |
| --- | --- |
| **JDK** com `javac` | Compilação (OpenJDK **25** recomendado; núcleo compila com target **17**) |
| **Apache Ant** 1.10+ | `pitiupi/`, `pitiupi.chat/`, `pitiupi.screenCast/` |
| **GNU Make** | `pitiupi.mpChat/` |
Exemplo Ubuntu / WSL:
```bash
sudo apt install openjdk-25-jdk ant make
javac -version # deve existir — JRE sozinho não basta
```
cd pitiupi.chat
ant jar
```
Feito isso, os arquivos jar dos plugins deverão ser copiados ou linkados na pasta plugin da ferramenta.
No caso de a mesma ser linkada, ao recompilar os plugins não é necessário copiar o mesmo novamente para a pasta de plugins da ferramenta.
Para criar links simbólicos no Linux, use o comando:
## Módulos
```
| Pasta | Descrição | Build |
| --- | --- | --- |
| `pitiupi/` | Núcleo (GUI, UDP multicast, TCP, heartbeat) | `ant jar` |
| `pitiupi.chat/` | Plugin referência — chat | `ant jar` |
| `pitiupi.screenCast/` | Plugin referência — screen cast | `ant jar` |
| `pitiupi.mpChat/` | Plugin referência — chat multicast + TCP | `make` |
Compile cada módulo no seu diretório (núcleo primeiro). Instale os JARs dos plugins em `pitiupi/plugins/` — ver [pitiupi/plugins/README.md](pitiupi/plugins/README.md).
Exemplo de symlinks (Linux):
```bash
cd pitiupi/plugins
ln -s ../../pitiupi.chat/dist/*.jar .
ln -s ../../pitiupi.screenCast/dist/*.jar .
```
Isso deve resolver!
ln -sf ../../pitiupi.chat/dist/pitiupi.chat.jar pitiupi.chat.jar
ln -sf ../../pitiupi.screenCast/dist/pitiupi.screenCast.jar pitiupi.screenCast.jar
ln -sf ../../pitiupi.mpChat/dist/mpChat.jar mpChat.jar
```
Para criar um novo plugin:
Execute o núcleo (a partir de `pitiupi/`):
```bash
cd pitiupi
java -jar dist/pitiupi.jar
```
Quickstart: [docs/documentation/00_quickstart.md](docs/documentation/00_quickstart.md).
Trabalho prático (plugin próprio): [docs/documentation/10_tutorial_plugin.md](docs/documentation/10_tutorial_plugin.md).
+35
View File
@@ -0,0 +1,35 @@
# Documentação — Pitiupi
Entrada na raiz do repositório: [README.md](../README.md).
## Pilares
| Pasta / arquivo | Papel |
| --- | --- |
| **[documentation/](documentation/README.md)** | Código, rede, plugins, operação e guia para alunos |
| **[references/](references/README.md)** | Bibliografia e notas de conceitos (UDP, TCP, SPI) |
| **[decision-register.md](decision-register.md)** | Decisões de desenho da plataforma |
## Público-alvo
| Leitor | Começar por |
| --- | --- |
| **Aluno (TP)** | [documentation/00_quickstart.md](documentation/00_quickstart.md) → [10_tutorial_plugin.md](documentation/10_tutorial_plugin.md) → [11_plugins_referencia.md](documentation/11_plugins_referencia.md) |
| **Monitor / docência** | [documentation/01_visao_disciplina_e_tp.md](documentation/01_visao_disciplina_e_tp.md) → [15_testes_e_gate.md](documentation/15_testes_e_gate.md) |
| **Desenvolvedor** | [documentation/13_guia_dev_e_agente.md](documentation/13_guia_dev_e_agente.md) → [14_arquitetura_e_manutencao.md](documentation/14_arquitetura_e_manutencao.md) |
| **Professor / banca** | [documentation/16_fundamentacao_redes.md](documentation/16_fundamentacao_redes.md) → [references/README.md](references/README.md) |
**Idioma:** documentação em PT-BR; pacotes e classes Java como no código.
## Manutenção
| Tipo de mudança | Onde documentar |
| --- | --- |
| Protocolo UDP/TCP, multicast, portas | [documentation/05_udp_multicast.md](documentation/05_udp_multicast.md), [06_tcp_conexoes.md](documentation/06_tcp_conexoes.md), [appendix_portas_e_multicast.md](documentation/appendix_portas_e_multicast.md) |
| Contrato de plugin / SPI | [documentation/09_sistema_de_plugins.md](documentation/09_sistema_de_plugins.md), [10_tutorial_plugin.md](documentation/10_tutorial_plugin.md) |
| Novo plugin de referência | [documentation/11_plugins_referencia.md](documentation/11_plugins_referencia.md) |
| Decisão de arquitetura | [decision-register.md](decision-register.md) |
| Débito técnico | [documentation/engineering_backlog.md](documentation/engineering_backlog.md) |
| Literatura de redes | [references/](references/) |
Documentação legada em `pitiupi/docs/` redireciona para esta árvore.
+14
View File
@@ -0,0 +1,14 @@
# Registro de decisões — Pitiupi
| ID | Decisão | Motivação | Doc |
| --- | --- | --- | --- |
| D1 | Grupo multicast `224.0.0.3` | Endereço fixo para descoberta e broadcast na LAN da sala | [appendix_portas_e_multicast.md](documentation/appendix_portas_e_multicast.md) |
| D2 | Mesma porta UDP e TCP por instância | Configurada no login; simplifica firewall em laboratório | [00_quickstart.md](documentation/00_quickstart.md) |
| D3 | Heartbeat via UDP multicast | Presença sem conexão TCP permanente com todos os peers | [08_heartbeat_e_presenca.md](documentation/08_heartbeat_e_presenca.md) |
| D4 | Timeout de peer ~15 s sem heartbeat | Remover usuários inativos da lista | [08_heartbeat_e_presenca.md](documentation/08_heartbeat_e_presenca.md) |
| D5 | Framing TCP: `int` tamanho + bytes | Delimitar mensagens em stream orientado a bytes | [06_tcp_conexoes.md](documentation/06_tcp_conexoes.md) |
| D6 | Mensagens `Serializable` em `byte[]` | Independência do transporte; plugins definem tipos | [07_mensagens_e_serializacao.md](documentation/07_mensagens_e_serializacao.md) |
| D7 | Plugins via JAR + Java SPI | Carregamento dinâmico sem recompilar o núcleo | [09_sistema_de_plugins.md](documentation/09_sistema_de_plugins.md) |
| D8 | Classloader por JAR de plugin | Isolar dependências; desserialização com `PluginObjectInputStream` | [09_sistema_de_plugins.md](documentation/09_sistema_de_plugins.md) |
Novas decisões: adicionar linha nesta tabela e detalhar no arquivo técnico correspondente.
+63
View File
@@ -0,0 +1,63 @@
# Quickstart
## Pré-requisitos
- **JDK completo** (`java` e `javac`) — OpenJDK 25 recomendado; o núcleo usa `javac.source/target=17`, plugins Ant usam 25.
- Apache Ant (`pitiupi`, `pitiupi.chat`, `pitiupi.screenCast`)
- GNU Make (`pitiupi.mpChat`)
Ubuntu / WSL:
```bash
sudo apt install openjdk-25-jdk ant make
which javac ant make
```
Se `ant jar` falhar com *Cannot run program "javac"*, você instalou apenas JRE — instale `openjdk-*-jdk`.
## Build
Na raiz do clone:
```bash
cd pitiupi && ant jar
cd ../pitiupi.chat && ant jar
cd ../pitiupi.screenCast && ant jar
cd ../pitiupi.mpChat && make
```
O núcleo gera `pitiupi/dist/pitiupi.jar`. Cada plugin gera JAR em seu `dist/`.
## Instalar plugins
A aplicação lê JARs de `pitiupi/plugins/` (relativo ao diretório de trabalho ao executar o núcleo). Detalhes: [pitiupi/plugins/README.md](../../pitiupi/plugins/README.md).
Symlinks em desenvolvimento (Linux):
```bash
cd pitiupi/plugins
ln -sf ../../pitiupi.chat/dist/pitiupi.chat.jar pitiupi.chat.jar
ln -sf ../../pitiupi.screenCast/dist/pitiupi.screenCast.jar pitiupi.screenCast.jar
ln -sf ../../pitiupi.mpChat/dist/mpChat.jar mpChat.jar
```
## Executar
```bash
cd pitiupi
java -jar dist/pitiupi.jar
```
No diálogo inicial informe **nome** e **porta** (UDP/TCP). Para duas instâncias no mesmo computador, use **portas diferentes** (ex.: 5000 e 5001).
## Verificar P2P
1. Abra duas instâncias (mesma rede ou mesmo host com portas distintas).
2. Confirme que os nomes aparecem no painel de usuários online.
3. Teste um plugin de referência (ex.: chat em `MenuCategory.Social`).
Problemas de multicast: [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md).
Gate de build (quatro módulos): [15_testes_e_gate.md](15_testes_e_gate.md).
Próximo passo para o TP: [10_tutorial_plugin.md](10_tutorial_plugin.md).
@@ -0,0 +1,47 @@
# Visão da disciplina e trabalho prático
## Papel do Pitiupi
O Pitiupi é uma **plataforma P2P com plugins** usada em Redes de Computadores para:
- Experimentar UDP (multicast/unicast) e TCP na mesma aplicação.
- Observar **presença** de peers (heartbeat) sem servidor central de aplicação.
- Desenvolver **trabalhos individuais ou em grupo** como novos plugins (jogos, ferramentas colaborativas, etc.).
O núcleo fornece rede e GUI; o aluno implementa a lógica no plugin.
## Trabalho típico (plugin)
Cada equipe/aluno entrega um **módulo Java separado** que:
1. Compila para JAR e carrega via `pitiupi/plugins/`.
2. Define mensagens (`extends Message`) e o fluxo entre peers.
3. Integra menus/janelas via `GuiManager`.
4. Inclui README do módulo (build + protocolo).
Exemplos de tema: chat temático, tabuleiro (xadrez/damas), compartilhamento de arquivo, quiz síncrono.
## Plugins de referência no repositório
| Módulo | Ideia pedagógica |
| --- | --- |
| `pitiupi.chat` | Mensagens UDP + UI simples |
| `pitiupi.screenCast` | Payload maior / streaming de imagem |
| `pitiupi.mpChat` | Multicast + TCP privado no mesmo plugin |
Detalhes: [11_plugins_referencia.md](11_plugins_referencia.md).
## Critérios genéricos (ajustar na disciplina)
| Critério | Indicador |
| --- | --- |
| Funcionalidade | Comportamento correto entre dois+ peers na rede da sala |
| Uso da plataforma | SPI, mensagens, sem “fork” desnecessário do núcleo |
| Build | `ant jar` / `make` reproduzível |
| Documentação | README do plugin + descrição das mensagens |
## Para monitores
- Roteiro de build: [00_quickstart.md](00_quickstart.md).
- Checklist de entrega: [15_testes_e_gate.md](15_testes_e_gate.md).
- Fundamentação para dúvidas de alunos: [16_fundamentacao_redes.md](16_fundamentacao_redes.md).
@@ -0,0 +1,34 @@
# Módulos do repositório
```
pitiupi/ # clone (este repositório)
├── pitiupi/ # núcleo P2P (executável)
├── pitiupi.chat/ # plugin referência — chat
├── pitiupi.screenCast/ # plugin referência — captura de tela
├── pitiupi.mpChat/ # plugin referência — chat multi-protocolo
└── docs/ # documentação canônica
```
## Núcleo (`pitiupi/`)
- **Build:** Apache Ant (`build.xml`, `nbproject/`).
- **Entrada:** `pitiupi.Main` → login → `MainWindow`.
- **Pacotes principais:** `pitiupi.net`, `pitiupi.control`, `pitiupi.GUI`, `pitiupi.models`.
## Plugins de referência
| Pasta | Build | JAR típico | Classe SPI |
| --- | --- | --- | --- |
| `pitiupi.chat` | `ant jar` | `dist/pitiupi.chat.jar` | `pitiupi.chat.plugin.ChatPlugin` |
| `pitiupi.screenCast` | `ant jar` | `dist/pitiupi.screenCast.jar` | `pitiupi.screenCast.plugin.ScreenCastPlugin` |
| `pitiupi.mpChat` | `make` | `dist/mpChat.jar` | `plugin.MultiProtocolChat` |
Todos dependem do JAR do núcleo no classpath de compilação.
## O que o aluno cria
Novo diretório irmão, por exemplo `pitiupi.xadrez/`, copiando a estrutura de `pitiupi.chat` (Ant + `META-INF/services`).
Não é obrigatório commitar o plugin do aluno neste repositório base; o professor pode definir fork ou entrega em zip.
Ver [03_build_ant_netbeans.md](03_build_ant_netbeans.md) e [10_tutorial_plugin.md](10_tutorial_plugin.md).
@@ -0,0 +1,49 @@
# Build — Ant, NetBeans e Make
## Pré-requisitos e versões Java
| Módulo | Build | `javac.source` / `target` (NetBeans) |
| --- | --- | --- |
| `pitiupi` | Ant | 17 |
| `pitiupi.chat` | Ant | 25 |
| `pitiupi.screenCast` | Ant | 25 |
| `pitiupi.mpChat` | Make | (sem `project.properties`; compila contra `pitiupi.jar`) |
Use um JDK que forneça `javac` (ex.: OpenJDK 25). Ant 1.10+ e Make no PATH.
## Núcleo e plugins NetBeans
Projetos com `nbproject/` e `build.xml`:
```bash
cd pitiupi && ant jar
cd pitiupi.chat && ant jar
cd pitiupi.screenCast && ant jar
```
Abrir no NetBeans: **File → Open Project** em cada pasta.
O target `jar` produz o executável do núcleo e os JARs dos plugins. O classpath de compilação dos plugins aponta para `../pitiupi/dist/pitiupi.jar` (ver `project.properties` de cada módulo).
## pitiupi.mpChat (Makefile)
```bash
cd pitiupi.mpChat
make
```
O Makefile compila com `javac -cp ../pitiupi/dist/pitiupi.jar`, copia `META-INF` e gera `dist/mpChat.jar`. **Compile o núcleo antes.**
## Diretório `plugins/`
A pasta `pitiupi/plugins/` contém os JARs carregados em runtime. Apenas arquivos `.jar` são lidos. Ver [pitiupi/plugins/README.md](../../pitiupi/plugins/README.md).
## Ordem recomendada de build
1. `pitiupi` (núcleo)
2. Plugins em qualquer ordem
3. Symlink ou cópia para `pitiupi/plugins/`
## Gate automatizado (docentes)
Quem mantém o meta-workspace Cursor pode usar o script de gate que executa `ant jar` nos três módulos Ant e `make` no mpChat (requer Ant/Make no PATH). Detalhes em [15_testes_e_gate.md](15_testes_e_gate.md).
@@ -0,0 +1,76 @@
# Visão geral
O `Pitiupi` é uma plataforma P2P baseada em uma arquitetura de plugins,
desenvolvida para permitir a criação de aplicações distribuídas sobre uma
infraestrutura comum de comunicação em rede.
A aplicação fornece mecanismos de descoberta de usuários, comunicação entre
peers, troca de mensagens serializadas e integração de extensões por meio de
plugins. A comunicação de rede utiliza dois protocolos de transporte: UDP,
empregado principalmente na descoberta e comunicação multicast entre as
instâncias, e TCP, utilizado para comunicação direta e persistente entre peers.
O projeto foi desenvolvido como suporte às práticas da disciplina de Redes de
Computadores, servindo também como base para experimentação de aplicações
distribuídas e desenvolvimento de projetos acadêmicos.
Índice: [README.md](README.md).
# Arquitetura geral
A aplicação é dividida em três grupos principais:
- **Núcleo da aplicação**: a `MainWindow` coordena GUI, rede e plugins.
- **Sistema de plugins**: extensões em JAR carregadas de `pitiupi/plugins/`.
- **Comunicação de rede**: UDP (`SocketUDP`) e TCP (`SocketTCP`, `TcpConnection`).
## Inicialização da aplicação
1. A `MainWindow` é criada após o diálogo de login (nome e porta).
2. O `SocketUDP` associa-se à porta configurada e ingressa no grupo multicast.
3. O `SocketTCP` cria um `ServerSocket` na mesma porta para conexões TCP.
4. Os mecanismos de comunicação iniciam threads de recepção.
5. O `HeartbeatManager` envia heartbeats periodicamente.
6. O painel de usuários online registra-se como listener do heartbeat.
7. O `PluginManager` carrega os JARs em `plugins/` via SPI.
Ver também: [05_udp_multicast.md](05_udp_multicast.md), [09_sistema_de_plugins.md](09_sistema_de_plugins.md).
# Interface gráfica
A interface gráfica da aplicação é baseada em Swing e é centralizada na
classe `MainWindow`.
Durante a inicialização, cada plugin recebe `ConnectionManager` e `GuiManager`
no método `createPlugin()`.
A extensão usual é registrar menus por categoria (`MenuCategory`). Ver
[12_menus_unicast_multicast.md](12_menus_unicast_multicast.md).
## Diagrama de comunicação
```
+------------------+
| MainWindow |
+--------+---------+
|
+--------------------+--------------------+
| | |
v v v
HeartbeatManager SocketUDP SocketTCP
| | |
v v v
Heartbeat UDP Multicast TCP Connections
| |
+---------+----------+
|
v
Plugins
|
v
Message
```
O UDP multicast sustenta presença e mensagens broadcast/unicast; o TCP sustenta
canais diretos com framing de mensagens. Detalhes em [05](05_udp_multicast.md)
e [06](06_tcp_conexoes.md).
+85
View File
@@ -0,0 +1,85 @@
# Comunicação UDP
A classe `SocketUDP` é responsável pela comunicação UDP da aplicação.
Ela utiliza um `MulticastSocket` associado à porta configurada pela aplicação e
ingressa no grupo multicast `224.0.0.3`. Por ser baseada em UDP, a comunicação não
estabelece uma conexão permanente entre os peers.
A classe estende `Thread`, mantendo um processo contínuo de recepção de datagramas
enquanto a aplicação está em execução.
O SocketUDP suporta dois modos de envio:
- **multicast**, destinado ao grupo de peers
- **unicast**, destinado diretamente ao endereço IP de um peer específico
### Inicialização
Durante sua inicialização, o `SocketUDP`:
1. obtém o endereço do grupo multicast;
2. cria um `MulticastSocket` utilizando a porta configurada na `MainWindow`;
3. configura os buffers de envio e recepção;
4. habilita o reuso do endereço;
5. ingressa no grupo multicast.
O tamanho dos buffers de comunicação é configurado para `256000` bytes.
### Envio de mensagens UDP
O `SocketUDP` suporta dois modos de envio de mensagens: multicast e unicast, ambos
baseados no mesmo mecanismo interno de criação e envio de DatagramPacket.
O método `sendMulticast()` envia uma mensagem para o grupo multicast da aplicação.
Nesse caso, o `DatagramPacket` é direcionado ao endereço multicast configurado,
permitindo que todas as instâncias participantes do grupo recebam a mensagem
simultaneamente.
Já o método `sendUnicast()` permite enviar uma mensagem diretamente para um peer
específico. Nesse caso, o `DatagramPacket` é direcionado ao endereço IP informado pelo
chamador, mantendo a mesma porta utilizada pela aplicação.
Ambos os métodos são, na prática, abstrações de um único mecanismo interno de envio,
que utiliza o mesmo processo de criação e envio de `DatagramPacket` por baixo dos
panos. A diferença entre eles está apenas no endereço de destino: enquanto o multicast
utiliza o grupo compartilhado, o unicast direciona o pacote a um IP específico.
### Recepção de mensagens
O método `receive()` mantém um loop aguardando novos datagramas.
Ao receber uma mensagem, o `SocketUDP` inicialmente tenta identificar se os dados
correspondem a uma `HeartbeatMessage`.
O processamento segue o seguinte fluxo:
1. O datagrama é recebido pelo `MulticastSocket`.
2. Os dados recebidos são analisados pelo método `tryParseHeartbeat()`.
3. Caso seja identificada uma `HeartbeatMessage`, ela é encaminhada ao
`HeartbeatManager`, juntamente com o endereço IP de origem.
4. Caso não seja um heartbeat, a mensagem é encaminhada para todos os plugins
carregados.
5. Cada plugin decide se a mensagem recebida pertence à sua funcionalidade.
Essa separação mantém as mensagens de infraestrutura, como heartbeat, sob
responsabilidade do núcleo da aplicação, enquanto as mensagens específicas das
funcionalidades são processadas pelos plugins.
### Identificação de mensagens de heartbeat
O método `tryParseHeartbeat()` tenta desserializar os dados recebidos e verificar se o
objeto resultante é uma instância de `HeartbeatMessage`.
Caso a mensagem não possa ser desserializada ou não corresponda ao tipo esperado, o
método retorna `null`. Nesse caso, a mensagem é tratada como uma mensagem destinada aos
plugins.
### Encerramento
O método `close()` interrompe a execução do processo de recepção, remove a aplicação
do grupo multicast e fecha o `MulticastSocket`.
A variável `running` é utilizada para sinalizar que o processo de recepção deve ser
encerrado.
+192
View File
@@ -0,0 +1,192 @@
# Comunicação TCP
A comunicação TCP é implementada pela classe `SocketTCP`.
Diferentemente do UDP, o TCP estabelece conexões entre dois peers. Essas conexões
são mantidas enquanto forem necessárias e podem ser reutilizadas para o envio de
múltiplas mensagens.
A arquitetura utiliza uma conexão TCP persistente por endereço de peer.
O SocketTCP possui um mapa de conexões:
`InetAddress` -> `TcpConnection`
Esse mapa permite localizar rapidamente uma conexão existente para determinado peer e
reutilizá-la durante novos envios.
### SocketTCP
A classe `SocketTCP` estende `Thread` e implementa `PeerListener`.
Ao ser criada, ela inicializa um `ServerSocket` utilizando a porta configurada na
aplicação.
O servidor TCP permanece aguardando novas conexões enquanto a aplicação estiver
em execução.
### Envio de mensagens TCP
O método `send()` recebe uma mensagem serializada e o endereço IP do peer destinatário.
Antes de enviar a mensagem, o `SocketTCP` verifica se já existe uma conexão válida com
o peer.
O comportamento é:
1. Procurar uma conexão existente no mapa de conexões.
2. Verificar se a conexão está fechada.
3. Caso não exista uma conexão válida, criar um novo Socket TCP para o endereço do peer.
4. Encapsular o socket em uma `TcpConnection`.
5. Registrar a conexão no mapa.
6. Iniciar uma tarefa de recepção para essa conexão.
7. Enviar a mensagem pela conexão.
Assim, uma conexão TCP existente pode ser reutilizada para várias mensagens, evitando
a criação de um novo socket para cada envio.
### Recepção de conexões
O método `receive()` permanece aguardando novas conexões através de
`ServerSocket.accept()`.
Quando uma conexão é aceita:
O endereço IP do peer é obtido.
Um objeto `TcpConnection` é criado para encapsular o socket.
A conexão é registrada no mapa de conexões.
Caso já exista uma conexão associada ao mesmo endereço, a conexão anterior é encerrada.
Uma tarefa independente de recepção é criada para a nova conexão.
Esse modelo permite que o servidor aceite múltiplas conexões sem bloquear o
processamento das conexões já existentes.
### Recepção concorrente
A recepção das mensagens TCP é realizada de maneira concorrente por meio de um
`ExecutorService`.
Cada conexão possui uma tarefa própria responsável por receber continuamente as
mensagens.
O fluxo de recepção é:
```
SocketTCP
|
+-- Peer A → TcpConnection → tarefa de recepção
|
+-- Peer B → TcpConnection → tarefa de recepção
|
+-- Peer C → TcpConnection → tarefa de recepção
```
Dessa forma, uma conexão bloqueada ou aguardando dados não impede que outras conexões
continuem sendo processadas.
Quando uma mensagem é recebida por uma `TcpConnection`, ela é encaminhada para todos os
plugins registrados na `MainWindow`.
O plugin responsável pela funcionalidade deve identificar o tipo da mensagem e
realizar o processamento correspondente.
### TcpConnection
A classe TcpConnection encapsula uma conexão TCP individual entre dois peers.
Ela mantém:
o Socket utilizado na conexão;
um DataInputStream para recepção;
um DataOutputStream para envio.
Além de encapsular esses recursos, a classe é responsável pelo framing das mensagens.
### Framing das mensagens TCP
A classe `TcpConnection` encapsula uma conexão TCP individual entre dois peers.
Ela mantém:
1. o `Socket` utilizado na conexão;
2. um `DataInputStream` para recepção;
3. um `DataOutputStream` para envio.
Além de encapsular esses recursos, a classe é responsável pelo framing das mensagens.
Framing das mensagens TCP
O protocolo TCP fornece um fluxo contínuo de bytes e não preserva os limites das
mensagens enviadas pela aplicação.
Por esse motivo, é adicionado o tamanho da mensagem antes do conteúdo.
O formato utilizado é:
```
+----------------------+----------------------+
| tamanho da mensagem | dados da mensagem |
+----------------------+----------------------+
4 bytes N bytes
```
O tamanho é enviado como um `int`, seguido pelos bytes da mensagem.
No envio, o método `TcpConnection.send()`:
1. obtém o tamanho do vetor de bytes;
2. escreve o tamanho no `DataOutputStream`;
3. escreve os dados da mensagem;
4. realiza `flush()` no stream.
O acesso ao stream de saída é sincronizado para evitar que envios concorrentes
misturem seus dados.
Na recepção, o método `receive()`:
1. lê o tamanho da mensagem;
2. verifica se o tamanho é válido;
3. lê a quantidade correspondente de bytes;
4. retorna o vetor de bytes completo.
Esse mecanismo permite que várias mensagens sejam transmitidas pela mesma conexão
TCP sem que o receptor perca a delimitação entre elas.
### Integração entre TCP e gerenciamento de peers
O `SocketTCP` implementa a interface PeerListener para acompanhar as alterações na
lista de peers ativos.
O `HeartbeatManager` mantém a lista de peers atualmente detectados na rede. Quando
essa lista é alterada, o SocketTCP recebe a notificação por meio do método
`onPeersChanged()`.
O método obtém os endereços IP dos peers atualmente ativos e compara esses endereços
com as conexões TCP existentes.
Quando uma conexão pertence a um peer que não está mais ativo:
a conexão é removida do mapa;
a conexão TCP é encerrada;
os recursos associados são liberados.
Esse mecanismo impede que conexões TCP permaneçam abertas indefinidamente para peers
que já deixaram de participar da rede.
O fluxo de integração pode ser representado da seguinte forma:
```
HeartbeatManager
|
| peers ativos alterados
|
v
SocketTCP.onPeersChanged()
|
| compara peers ativos
|
v
+---- peer ativo ------> mantém conexão
|
+---- peer inativo ----> fecha conexão
```
@@ -0,0 +1,18 @@
# Mensagens
### Message
A classe `Message` representa a estrutura base das mensagens trocadas pela aplicação.
Todas as mensagens utilizadas pelo sistema devem herdar dessa classe. Ela implementa
`Serializable`, permitindo que objetos de mensagem sejam convertidos em vetores de
bytes para transmissão pela rede.
A serialização é realizada pelo método `toByteArray()`, que transforma uma instância
da mensagem em uma representação binária enviada pelos mecanismos de comunicação.
As mensagens específicas devem estender essa classe, adicionando os atributos e
comportamentos necessários para cada funcionalidade.
A camada de transporte não depende do conteúdo específico das mensagens. Tanto o
UDP quanto o TCP recebem mensagens serializadas como `byte[]`.
@@ -0,0 +1,62 @@
# Heartbeat
### Heartbeat
O mecanismo de heartbeat é utilizado para identificar quais usuários estão ativos
na rede.
A aplicação envia periodicamente uma `HeartbeatMessage` contendo informações do usuário,
um identificador único da instância da aplicação e um timestamp.
Essas mensagens são transmitidas utilizando a comunicação UDP multicast.
Quando outra instância recebe uma mensagem de heartbeat, o `SocketUDP` identifica
o tipo da mensagem e encaminha o objeto ao `HeartbeatManager`, juntamente com o endereço
IP de origem.
O `HeartbeatManager` registra ou atualiza o peer correspondente, utilizando o
identificador da instância para diferenciá-lo das demais.
Um peer é considerado inativo quando permanece mais de 15 segundos sem receber um novo
heartbeat. O `HeartbeatManager` então remove o peer da lista e notifica todos os
componentes registrados como listeners de mudanças de peers.
Esses listeners podem incluir diferentes partes da aplicação, como componentes
da interface gráfica, mecanismos de comunicação (como o `SocketTCP`) e também plugins
que desejem reagir à presença ou ausência de usuários na rede.
Dessa forma, o sistema permite que tanto o núcleo da aplicação quanto extensões
externas sejam notificados sobre alterações no estado da rede, mantendo a arquitetura
flexível e extensível.
O fluxo completo é:
```
HeartbeatManager
|
| cria HeartbeatMessage
|
v
Message.toByteArray()
|
v
SocketUDP.sendMulticast()
|
v
UDP Multicast
|
v
SocketUDP.receive()
|
v
HeartbeatManager.receiveHeartbeat()
|
v
Lista de peers ativos
|
+----> Interface de usuários online
|
+----> SocketTCP (PeerListener)
|
+----> Plugins (PeerListener)
```
@@ -0,0 +1,60 @@
# Sistema de plugins
O sistema de plugins permite extensão modular sem alterar o núcleo. O contrato
está em `pitiupi.models.Plugin` (classe abstrata).
## Interface Plugin
Métodos obrigatórios:
| Método | Função |
| --- | --- |
| `getName()`, `getAuthor()`, `getVersion()`, `getIdentifier()` | Identificação (o `identifier` distingue plugins em conexões TCP dedicadas) |
| `createPlugin(ConnectionManager, GuiManager)` | Inicialização: menus, janelas, uso da rede |
| `getMessages()` | Lista de classes `Message` que o plugin desserializa |
| `receiveMessage(Message)` | Processamento após o `PluginManager` identificar o tipo |
Métodos opcionais (TCP avançado):
| Método | Função |
| --- | --- |
| `receiveAllTypes()` | Receber todas as mensagens públicas |
| `receivePrivateConnection(TcpConnection)` | Aceitar conexão TCP direcionada ao plugin |
| `receiveBytes(TcpConnection, byte[])` | Ler payload bruto na conexão privada |
| `onSocketClosed(TcpConnection)` | Limpar estado ao fechar socket |
### Descoberta de plugins
Para serem reconhecidos, os plugins devem possuir os arquivos de configuração
do Java Service Provider Interface (SPI):
```
MeusPlugins
|
+---META-INF
|
+---services
|
+---pitiupi.models.Plugin
```
O arquivo `pitiupi.models.Plugin` deve conter o nome completo das classes que
implementam a interface Plugin:
```
MeusPlugins.plugins.NomeDaClasse
```
Para serem carregados, os plugins devem estar empacotados como arquivos JAR
e colocados no diretório plugins da aplicação.
### Carregamento de plugins
O `PluginManager.loadPlugins()` (chamado pela `MainWindow`):
1. Lista arquivos `.jar` em `pitiupi/plugins/` (diretório de trabalho = pasta do núcleo).
2. Para cada JAR, cria um `URLClassLoader` e usa `ServiceLoader.load(Plugin.class, classLoader)`.
3. Chama `createPlugin(connectionManager, guiManager)` em cada instância.
4. Na recepção UDP/TCP, tenta desserializar com `PluginObjectInputStream` e despacha para plugins que declararam o tipo em `getMessages()`.
Implementação: `pitiupi/src/pitiupi/control/plugins/PluginManager.java`.
+75
View File
@@ -0,0 +1,75 @@
# Tutorial — criar um plugin
Este guia complementa [09_sistema_de_plugins.md](09_sistema_de_plugins.md). Use `pitiupi.chat` como modelo.
## 1. Novo projeto
- Copie a pasta `pitiupi.chat` para `pitiupi.meuPlugin` (nome à escolha).
- Ajuste `project.properties` / `build.xml` com o nome do JAR de saída.
- Mantenha `javac.classpath` apontando para `../pitiupi/dist/pitiupi.jar`.
## 2. Classe principal
```java
public class MeuPlugin extends Plugin {
@Override
public void createPlugin(ConnectionManager cm, GuiManager gui) {
// registrar menus, abrir janelas
}
@Override
public List<Class<? extends Message>> getMessages() {
return List.of(MinhaMessage.class);
}
@Override
public void receiveMessage(Message message) {
if (message instanceof MinhaMessage m) { /* ... */ }
}
// getName, getAuthor, getVersion, getIdentifier
}
```
## 3. Mensagem
```java
public class MinhaMessage extends Message implements Serializable {
// campos + serialVersionUID recomendado
}
```
Envio típico via `ConnectionManager` (multicast ou unicast UDP, ou TCP conforme o caso).
## 4. Service Provider Interface
Arquivo `src/META-INF/services/pitiupi.models.Plugin`:
```
pacote.MeuPlugin
```
## 5. Build e instalação
```bash
cd pitiupi.meuPlugin && ant jar
ln -sf ../../pitiupi.meuPlugin/dist/*.jar ../pitiupi/plugins/
```
Reinicie o Pitiupi após trocar JARs.
## 6. Teste
Duas instâncias, mesma rede, portas distintas se no mesmo host. Ver [00_quickstart.md](00_quickstart.md).
## TCP dedicado (avançado)
Se o plugin precisa de canal TCP com handshake por `getIdentifier()`, estude `pitiupi.mpChat` e os métodos opcionais em `Plugin` (`receivePrivateConnection`, `receiveBytes`). Ver [11_plugins_referencia.md](11_plugins_referencia.md).
## Erros frequentes
| Problema | Causa provável |
| --- | --- |
| Plugin não aparece | SPI ausente ou JAR fora de `plugins/` |
| `ClassNotFoundException` na recepção | Classe da mensagem não no JAR do remetente/destino |
| Peers não se veem | Multicast bloqueado — [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md) |
@@ -0,0 +1,59 @@
# Plugins de referência
Três exemplos no repositório para comparar estilos de integração. Todos devem ser empacotados em `pitiupi/plugins/`.
## Comparativo
| | pitiupi.chat | pitiupi.screenCast | pitiupi.mpChat |
| --- | --- | --- | --- |
| Build | Ant | Ant | Make |
| Classe | `ChatPlugin` | `ScreenCastPlugin` | `MultiProtocolChat` |
| Categoria menu | `Social` | (menu custom via `GuiManager`) | `Social` (multicast + unicast) |
| Mensagem | `ChatMessage` | `ScreenCastMessage` | `MpChatMessage` |
| Transporte principal | UDP (via `ConnectionManager`) | UDP / payloads maiores | UDP público + **TCP privado** |
| TCP avançado | Não | Depende da versão do módulo | `receivePrivateConnection`, `receiveBytes` |
## pitiupi.chat
- **Referência canônica** alinhada ao contrato atual `Plugin` + `ConnectionManager` / `GuiManager`.
- `createPlugin` registra `ChatMenu` em `MenuCategory.Social`.
- `getMessages()` retorna `ChatMessage.class`; `receiveMessage` atualiza `ChatWindow`.
Caminhos: `pitiupi.chat/src/pitiupi/chat/plugin/ChatPlugin.java`.
## pitiupi.screenCast
- Demonstra compartilhamento de tela / imagens (`ScreenCastWindow`, `ScreenCastMessage`).
- Útil para discutir **tamanho de datagramas**, compressão (`ImageTransformation`) e limites UDP.
Caminhos: `pitiupi.screenCast/src/pitiupi/screenCast/`. Alinhado ao contrato `Plugin` + `ConnectionManager` / `GuiManager` (como `ChatPlugin`).
## pitiupi.mpChat
- Chat **público** (multicast / unicast UDP) e **privado** (TCP).
- `createPlugin(ConnectionManager, GuiManager)` como nos demais plugins; usa `ConnectionManager.connectPluginTcp` para abrir canal privado.
- Sobrescreve hooks TCP do `Plugin` (`receivePrivateConnection`, `receiveBytes`, `onSocketClosed`).
Caminhos: `pitiupi.mpChat/src/plugin/MultiProtocolChat.java`.
## Fluxo UDP (todos os plugins)
```mermaid
sequenceDiagram
participant App as MainWindow
participant UDP as SocketUDP
participant HB as HeartbeatManager
participant PM as PluginManager
participant Peer as OutroPeer
App->>UDP: join multicast
UDP->>HB: HeartbeatMessage
Peer->>UDP: Message plugin
UDP->>PM: handleMessage bytes
PM->>PM: deserialize + dispatch
```
Heartbeat não passa pelos plugins — ver [08_heartbeat_e_presenca.md](08_heartbeat_e_presenca.md).
## Próximo passo
Criar plugin próprio: [10_tutorial_plugin.md](10_tutorial_plugin.md).
@@ -0,0 +1,35 @@
# Menus — unicast e multicast
## MenuCategory
Enum em `pitiupi.models.Menus.MenuCategory`:
| Valor | Uso sugerido |
| --- | --- |
| `Jogos` | Plugins de jogos entre peers |
| `Social` | Chat, presença, mensagens sociais |
| `Streaming` | Screen cast, mídia |
## Registrar menu (padrão atual)
Via `GuiManager.createMenu(MenuCategory category, Menu menu)` — ver `ChatPlugin`:
```java
guiManager.createMenu(MenuCategory.Social, new ChatMenu(chatWindow));
```
A hierarquia de menus unicast/multicast do núcleo (`UnicastMenu`, `MulticastMenu`, etc.) permite ações por peer ou broadcast; plugins de referência encapsulam isso em classes `*Menu` próprias.
## Unicast vs multicast na aplicação
| Modo | API típica | Semântica |
| --- | --- | --- |
| Multicast | `ConnectionManager` → `SocketUDP.sendMulticast` | Todos no grupo na porta configurada |
| Unicast UDP | envio para IP de um peer | Um destinatário, sem estabelecer TCP |
| TCP | `ConnectionManager` / `SocketTCP` | Canal persistente, framing 4+N bytes |
Detalhes de rede: [05_udp_multicast.md](05_udp_multicast.md), [06_tcp_conexoes.md](06_tcp_conexoes.md).
## mpChat
`MulticastMpChatMenu` e `UnicastMpChatMenu` mostram como expor as duas modalidades no mesmo plugin — [11_plugins_referencia.md](11_plugins_referencia.md).
@@ -0,0 +1,42 @@
# Guia para desenvolvedor e agente
## Leitura mínima antes de mudar código
1. [00_quickstart.md](00_quickstart.md) — build e execução
2. [04_inicializacao_e_gui.md](04_inicializacao_e_gui.md) — boot e diagrama
3. [09_sistema_de_plugins.md](09_sistema_de_plugins.md) — contrato de extensão
4. [14_arquitetura_e_manutencao.md](14_arquitetura_e_manutencao.md) — mapa de pacotes
## Por tipo de tarefa
| Tarefa | Ler primeiro |
| --- | --- |
| Novo plugin (aluno) | [10_tutorial_plugin.md](10_tutorial_plugin.md), [11_plugins_referencia.md](11_plugins_referencia.md) |
| Bug UDP / multicast | [05_udp_multicast.md](05_udp_multicast.md), [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md) |
| Bug TCP / conexão | [06_tcp_conexoes.md](06_tcp_conexoes.md) |
| Presença / lista de usuários | [08_heartbeat_e_presenca.md](08_heartbeat_e_presenca.md) |
| Desserialização / mensagens | [07_mensagens_e_serializacao.md](07_mensagens_e_serializacao.md) |
## Gate de build
Compilar núcleo e os três plugins de referência:
```bash
cd pitiupi && ant jar
cd ../pitiupi.chat && ant jar
cd ../pitiupi.screenCast && ant jar
cd ../pitiupi.mpChat && make
```
Requer JDK, Ant e Make no PATH. Teste manual em rede: [15_testes_e_gate.md](15_testes_e_gate.md).
## Convenções
- Código e identificadores em inglês (pacotes `pitiupi.*`).
- Documentação em português (BR) em `docs/`.
- Evitar alterar o núcleo para necessidades de um único plugin — preferir extensão via `Plugin`.
## Decisões e débitos
- [decision-register.md](../decision-register.md)
- [engineering_backlog.md](engineering_backlog.md)
@@ -0,0 +1,42 @@
# Arquitetura e manutenção
## Onde editar
| Comportamento | Pacote / classe |
| --- | --- |
| Login, porta, nome | `pitiupi.GUI.Login` |
| Orquestração GUI + plugins | `pitiupi.GUI.MainWindow` |
| Barra de menus base | `pitiupi.GUI.MenuBar` |
| Usuários online | `pitiupi.GUI.OnlineUsersPanel`, `HeartbeatManager` |
| Facade rede para plugins | `pitiupi.control.ConnectionManager` |
| Registro de menus | `pitiupi.control.GuiManager` |
| UDP | `pitiupi.net.udp.SocketUDP` |
| TCP servidor e mapa de conexões | `pitiupi.net.tcp.SocketTCP`, `TcpConnectionManager` |
| Conexão TCP por peer | `pitiupi.net.tcp.TcpConnection` |
| Handshake / fábrica TCP | `pitiupi.net.tcp.ConnectionFactory`, `HandshakeMessage` |
| Heartbeat | `pitiupi.control.HeartbeatManager`, `HeartbeatMessage` |
| Carregar JARs, despacho | `pitiupi.control.plugins.PluginManager` |
| Contrato plugin | `pitiupi.models.Plugin`, `pitiupi.models.Messages.Message` |
| Menus por categoria | `pitiupi.models.Menus.*` |
## Dependências entre módulos
```
pitiupi (núcleo)
↑ classpath
pitiupi.chat | pitiupi.screenCast | pitiupi.mpChat
```
Plugins **não** devem ser dependência do núcleo.
## Alterações arriscadas
- Mudar grupo multicast ou formato de heartbeat → quebra interoperabilidade na sala.
- Mudar framing TCP → todos os plugins que usam TCP precisam acompanhar.
- Alterar nome do arquivo SPI (`pitiupi.models.Plugin`) → todos os JARs de plugin.
Registrar decisão em [decision-register.md](../decision-register.md).
## Documentação viva
Ao mudar protocolo ou API pública do núcleo, atualizar o número correspondente em [documentation/README.md](README.md) e, se aplicável, [11_plugins_referencia.md](11_plugins_referencia.md).
+38
View File
@@ -0,0 +1,38 @@
# Testes e gate
## Estado atual
Não há suíte JUnit integrada ao repositório. A validação principal é:
1. **Build** de todos os módulos.
2. **Teste manual** com duas instâncias na rede.
## Gate de build (recomendado)
Sequência:
```bash
cd pitiupi && ant jar
cd ../pitiupi.chat && ant jar
cd ../pitiupi.screenCast && ant jar
cd ../pitiupi.mpChat && make
```
Falha se `java`, **`javac`**, Ant ou Make estiverem ausentes (instale `openjdk-*-jdk`, não só JRE).
## Checklist manual (rede)
- [ ] Duas instâncias com portas distintas (mesmo host) ou dois hosts na mesma LAN
- [ ] Nomes visíveis no painel de usuários em até ~15 s
- [ ] Plugin de referência troca mensagens entre os dois
- [ ] Encerrar uma instância remove o peer da lista após timeout de heartbeat
## Checklist plugin de aluno
- [ ] JAR em `plugins/` + SPI correto
- [ ] README do módulo com build e formato das mensagens
- [ ] Demonstração mínima professor ↔ aluno ou aluno ↔ aluno
## Melhorias futuras
Ver [engineering_backlog.md](engineering_backlog.md) (testes automatizados, CI).
@@ -0,0 +1,52 @@
# Fundamentação — redes e código
Mapa entre conceitos de **Redes de Computadores** e o Pitiupi. Aprofundamento: [references/README.md](../references/README.md).
## Camada de aplicação P2P
| Conceito | No Pitiupi |
| --- | --- |
| Aplicação distribuída sem servidor dedicado | Cada peer executa o mesmo núcleo + plugins |
| Identidade do participante | Nome no login + `HeartbeatMessage` (instância) |
| Broadcast lógico | UDP multicast no grupo `224.0.0.3` |
| Comunicação 1:1 | UDP unicast ou TCP com conexão persistente |
## UDP
- Sem conexão; mensagens delimitadas por datagrama.
- Multicast economiza envios para todos os peers do grupo.
- Limitações: perda, ordem, MTU — relevante para plugins com imagens (screenCast).
Doc: [05_udp_multicast.md](05_udp_multicast.md).
## TCP
- Conexão orientada a fluxo; o núcleo adiciona **framing** (tamanho + payload).
- Uma conexão por endereço de peer reutilizada para várias mensagens.
- Fechamento quando peer some do heartbeat.
Doc: [06_tcp_conexoes.md](06_tcp_conexoes.md).
## Presença
Heartbeats periódicos em UDP; timeout ~15 s. Analogia com “keepalive” de presença, não com camada de transporte confiável.
Doc: [08_heartbeat_e_presenca.md](08_heartbeat_e_presenca.md).
## Serialização
Objetos Java `Serializable` em `byte[]` — acoplamento de versão de classes entre peers (mesmo plugin instalado).
Doc: [07_mensagens_e_serializacao.md](07_mensagens_e_serializacao.md).
## Extensibilidade
SPI e classloader por JAR — padrão de plug-in em aplicações Java.
Doc: [09_sistema_de_plugins.md](09_sistema_de_plugins.md).
## Leitura complementar
- [references/notas/udp_vs_tcp.md](../references/notas/udp_vs_tcp.md)
- [references/notas/multicast_lan.md](../references/notas/multicast_lan.md)
- [references/notas/java_spi_plugins.md](../references/notas/java_spi_plugins.md)
+27
View File
@@ -0,0 +1,27 @@
# Documentação técnica — Pitiupi
Referência para **código Java**, **rede P2P** e **plugins**. Índice geral: [docs/README.md](../README.md).
## Ordem sugerida
| # | Arquivo | Conteúdo |
| --- | --- | --- |
| 0 | [00_quickstart.md](00_quickstart.md) | Build, plugins, duas instâncias |
| 1 | [01_visao_disciplina_e_tp.md](01_visao_disciplina_e_tp.md) | Uso na disciplina e trabalho prático |
| 2 | [02_modulos_do_repositorio.md](02_modulos_do_repositorio.md) | Núcleo e três plugins de referência |
| 3 | [03_build_ant_netbeans.md](03_build_ant_netbeans.md) | Ant, NetBeans, Makefile mpChat |
| 4 | [04_inicializacao_e_gui.md](04_inicializacao_e_gui.md) | Boot, MainWindow, diagrama |
| 5 | [05_udp_multicast.md](05_udp_multicast.md) | SocketUDP |
| 6 | [06_tcp_conexoes.md](06_tcp_conexoes.md) | SocketTCP, TcpConnection, framing |
| 7 | [07_mensagens_e_serializacao.md](07_mensagens_e_serializacao.md) | Classe Message |
| 8 | [08_heartbeat_e_presenca.md](08_heartbeat_e_presenca.md) | HeartbeatManager |
| 9 | [09_sistema_de_plugins.md](09_sistema_de_plugins.md) | Plugin, PluginManager, SPI |
| 10 | [10_tutorial_plugin.md](10_tutorial_plugin.md) | Passo a passo novo plugin |
| 11 | [11_plugins_referencia.md](11_plugins_referencia.md) | chat, screenCast, mpChat |
| 12 | [12_menus_unicast_multicast.md](12_menus_unicast_multicast.md) | MenuCategory e ações |
| 13 | [13_guia_dev_e_agente.md](13_guia_dev_e_agente.md) | Leitura para dev/agente |
| 14 | [14_arquitetura_e_manutencao.md](14_arquitetura_e_manutencao.md) | Onde editar cada comportamento |
| 15 | [15_testes_e_gate.md](15_testes_e_gate.md) | Build gate e testes manuais |
| 16 | [16_fundamentacao_redes.md](16_fundamentacao_redes.md) | Teoria ↔ código |
| — | [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md) | Portas, multicast, WSL |
| — | [engineering_backlog.md](engineering_backlog.md) | Débitos técnicos |
@@ -0,0 +1,38 @@
# Apêndice — portas e multicast
## Configuração
- **Nome** e **porta** definidos no diálogo de login (`Login`).
- A mesma porta é usada para UDP (multicast socket) e TCP (`ServerSocket`).
- Preferências persistidas localmente (`java.util.prefs`).
## Multicast
- Grupo: **`224.0.0.3`** (fixo no código do núcleo).
- Buffer UDP: **256000** bytes (configuração em `SocketUDP`).
Todos os peers na mesma LAN devem usar a **mesma porta** para participar do mesmo “grupo lógico” da aula (portas diferentes = grupos isolados).
## Mesmo computador
Execute duas instâncias com **portas diferentes** (ex.: 5000 e 5001). Mesmo nome de usuário em duas instâncias pode confundir a lista — use nomes distintos.
## Firewall e Wi‑Fi
- Bloqueio de multicast em redes de visitantes ou isolamento AP→AP impede descoberta.
- Em laboratório, preferir cabo ou Wi‑Fi com multicast habilitado.
## WSL2
Executar o Pitiupi no WSL e peers no Windows (ou vice-versa) pode falhar em multicast. Preferir:
- todos os peers no mesmo ambiente (Linux nativo ou VMs), ou
- dois PCs físicos na mesma sub-rede.
## Sintomas
| Sintoma | Verificar |
| --- | --- |
| Lista de usuários vazia | Porta igual entre peers? Mesma sub-rede? Firewall? |
| Plugin não recebe | Desserialização / SPI / JAR em `plugins/` |
| TCP falha | Peer ativo no heartbeat? IP correto? |
+11
View File
@@ -0,0 +1,11 @@
# Engineering backlog
| Item | Prioridade | Notas |
| --- | --- | --- |
| Completar tutorial legado em `pitiupi/docs/PluginTutorial.md` | Média | Substituído por [10_tutorial_plugin.md](10_tutorial_plugin.md) |
| Testes JUnit para desserialização e framing TCP | Média | Sem rede real |
| CI (GitHub Actions) com `ant jar` + `make` | Baixa | Requer imagem com Ant |
Itens concluídos recentemente: alinhamento de `pitiupi.mpChat` a `ConnectionManager` / `GuiManager`; documentação de JDK mínimo no [README](../../README.md) e [00_quickstart.md](00_quickstart.md).
Ao fechar um item, atualizar esta tabela e o doc técnico relacionado.
+18
View File
@@ -0,0 +1,18 @@
# Referências — Pitiupi
Bibliografia e notas curtas para apoiar a disciplina de Redes de Computadores.
| Recurso | Descrição |
| --- | --- |
| [bibliografia.bib](bibliografia.bib) | Entradas BibTeX (livro-texto, RFCs, documentação Java) |
| [notas/](notas/) | Resumos em PT-BR ligados ao código |
| [pdfs/](pdfs/) | PDFs locais (não versionados se grandes; ver `.gitkeep`) |
## Notas disponíveis
- [udp_vs_tcp.md](notas/udp_vs_tcp.md)
- [multicast_lan.md](notas/multicast_lan.md)
- [java_serializacao.md](notas/java_serializacao.md)
- [java_spi_plugins.md](notas/java_spi_plugins.md)
Voltar ao índice: [docs/README.md](../README.md).
+24
View File
@@ -0,0 +1,24 @@
@book{tanenbaum2011,
title = {Computer Networks},
author = {Tanenbaum, Andrew S. and Wetherall, David J.},
edition = {5},
year = {2011},
publisher = {Prentice Hall},
note = {UDP, TCP, multicast — conceitos gerais}
}
@misc{rfc1112,
title = {Host Extensions for IP Multicasting},
author = {Deering, S.},
howpublished = {RFC 1112},
year = {1989},
url = {https://www.rfc-editor.org/rfc/rfc1112}
}
@book{oracle_networking,
title = {Java Platform, Standard Edition Documentation: Networking},
author = {{Oracle}},
year = {2024},
url = {https://docs.oracle.com/en/java/javase/},
note = {MulticastSocket, Socket, serialization}
}
@@ -0,0 +1,11 @@
# Serialização Java (nota)
O Pitiupi serializa objetos `Message` (Java `Serializable`) para `byte[]` antes do envio.
Implicações:
- Mesma versão de classe nos peers (mesmo JAR de plugin).
- `serialVersionUID` explícito evita incompatibilidade silenciosa.
- Desserialização no núcleo usa `PluginObjectInputStream` com classloader do plugin.
Doc: [07_mensagens_e_serializacao.md](../../documentation/07_mensagens_e_serializacao.md).
@@ -0,0 +1,9 @@
# Java SPI — plugins (nota)
O núcleo usa `ServiceLoader` para localizar implementações de `pitiupi.models.Plugin` declaradas em:
`META-INF/services/pitiupi.models.Plugin`
Uma linha por classe concreta (FQCN). O JAR deve estar em `pitiupi/plugins/`.
Doc: [09_sistema_de_plugins.md](../../documentation/09_sistema_de_plugins.md).
+12
View File
@@ -0,0 +1,12 @@
# Multicast em LAN (nota)
IP multicast envia um datagrama que roteadores/switches encaminham aos assinantes do grupo (ex.: `224.0.0.3`).
Na sala de aula:
- Todos os peers devem estar na mesma sub-rede e permitir IGMP/multicast.
- Wi‑Fi com “client isolation” impede descoberta.
Operação no código: [appendix_portas_e_multicast.md](../../documentation/appendix_portas_e_multicast.md).
RFC introdutória: ver entrada `rfc1112` em [bibliografia.bib](../bibliografia.bib).
+10
View File
@@ -0,0 +1,10 @@
# UDP vs TCP (nota)
| | UDP | TCP |
| --- | --- | --- |
| Conexão | Não | Sim |
| Confiabilidade | Melhor esforço | Entrega ordenada na conexão |
| Delimitação de mensagem | Por datagrama | Fluxo de bytes — Pitiupi usa framing 4+N |
| Uso no Pitiupi | Heartbeat, multicast, plugins | Canais diretos peer-a-peer |
Ver [05_udp_multicast.md](../../documentation/05_udp_multicast.md) e [06_tcp_conexoes.md](../../documentation/06_tcp_conexoes.md).
View File
Whitespace-only changes.
-6
View File
@@ -1,6 +0,0 @@
#Fri, 02 Oct 2026 20:11:21 -0300
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi.chat=
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi=
@@ -1 +0,0 @@
pitiupi.chat.plugin.ChatPlugin
@@ -1,8 +1,8 @@
package GUI;
import net.MpChatMessage;
import pitiupi.control.ConnectionManager;
import pitiupi.net.tcp.TcpConnection;
import pitiupi.plugin.Pitiupi;
import javax.swing.*;
import java.awt.*;
@@ -12,17 +12,17 @@ import java.util.logging.Logger;
public class PrivateChatWindow extends JFrame {
private final Pitiupi pitiupi;
private final ConnectionManager connectionManager;
private final TcpConnection connection;
private final JTextArea receivedMessagesArea;
private final JTextField messageField;
public PrivateChatWindow(
Pitiupi pitiupi,
ConnectionManager connectionManager,
TcpConnection connection
) {
this.pitiupi = pitiupi;
this.connectionManager = connectionManager;
this.connection = connection;
setTitle("Chat privado");
@@ -81,7 +81,7 @@ public class PrivateChatWindow extends JFrame {
private void sendTCP() {
try {
String nome = pitiupi.getUserName();
String nome = connectionManager.getUserName();
MpChatMessage message = new MpChatMessage(
nome,
+7 -8
View File
@@ -2,7 +2,6 @@ package GUI;
import net.MpChatMessage;
import pitiupi.control.ConnectionManager;
import pitiupi.plugin.Pitiupi;
import javax.swing.*;
import javax.swing.text.BadLocationException;
@@ -15,14 +14,14 @@ import java.util.logging.Level;
import java.util.logging.Logger;
public class PublicChatWindow extends JFrame {
private final Pitiupi pitiupi;
private final ConnectionManager connectionManager;
private JTextPane receivedMessagesArea;
private JTextField messageField;
private JTextField ipField;
public PublicChatWindow(Pitiupi pitiupi) {
this.pitiupi = pitiupi;
public PublicChatWindow(ConnectionManager connectionManager) {
this.connectionManager = connectionManager;
initComponents();
}
@@ -120,7 +119,7 @@ public class PublicChatWindow extends JFrame {
private MpChatMessage createMessage(String tipoMensagem) {
try {
String nome = pitiupi.getUserName();
String nome = connectionManager.getUserName();
String ip = InetAddress.getLocalHost().getHostAddress();
String message = messageField.getText();
@@ -177,7 +176,7 @@ public class PublicChatWindow extends JFrame {
InetAddress ip = InetAddress.getByName(ipField.getText());
pitiupi.sendTcp(msg, ip);
connectionManager.sendTcp(msg, ip);
receiveMessage(msg);
} catch (Exception ex) {
@@ -193,7 +192,7 @@ public class PublicChatWindow extends JFrame {
InetAddress ip = InetAddress.getByName(ipField.getText());
pitiupi.sendUdpUnicast(msg, ip);
connectionManager.sendUdpUnicast(msg, ip);
receiveMessage(msg);
} catch (Exception ex) {
@@ -207,7 +206,7 @@ public class PublicChatWindow extends JFrame {
try {
MpChatMessage msg = createMessage("UDP Multicast");
pitiupi.sendUdpMulticast(msg);
connectionManager.sendUdpMulticast(msg);
} catch (Exception ex) {
Logger.getLogger(
@@ -1,73 +0,0 @@
package control;
import GUI.ChatMenu;
import GUI.ChatWindow;
import GUI.PrivateChatWindow;
import GUI.PublicChatWindow;
import net.MpChatMessage;
import pitiupi.control.ConnectionManager;
import pitiupi.control.PeerListener;
import pitiupi.models.User;
import pitiupi.net.tcp.TcpConnection;
import javax.swing.*;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.List;
public class ChatController implements PeerListener {
ConnectionManager connectionManager;
ChatMenu chatMenu;
PublicChatWindow publicChatWindow;
private final HashMap<TcpConnection, ChatWindow> chatWindows;
public ChatController(ConnectionManager connectionManager, ChatMenu chatMenu) {
this.connectionManager = connectionManager;
publicChatWindow = new PublicChatWindow(connectionManager);
chatMenus = new HashMap<>();
chatWindows = new HashMap<>();
this.chatMenu = chatMenu;
this.chatMenu.newMenuItem(new JMenuItem("Show global chat"), publicChatWindow);
}
public void addChatWindow(TcpConnection tcpConnection) {
if (chatWindows.containsKey(tcpConnection)) return;
PrivateChatWindow newChat = new PrivateChatWindow(connectionManager, tcpConnection);
chatWindows.put(tcpConnection, newChat);
JMenuItem newMenuItem = new JMenuItem();
chatMenus.put(tcpConnection, newMenuItem);
newMenuItem.setText(tcpConnection.getAddress().toString());
chatMenu.newMenuItem(newMenuItem, newChat);
}
public void removeChatWindow(TcpConnection tcpConnection) {
ChatWindow removedWindow = chatWindows.remove(tcpConnection);
JMenuItem removedMenu = chatMenus.remove(tcpConnection);
chatMenu.removeMenuItem(removedMenu);
removedWindow.setVisible(false);
removedWindow.dispose();
}
public void receivePublic(MpChatMessage message) {
publicChatWindow.receiveMessage(message);
}
public void receivePrivate(TcpConnection tcpConnection, MpChatMessage message) {
chatWindows.get(tcpConnection).receiveMessage(message);
}
public void openChatWindow(InetAddress address) {
}
@Override
public void onPeersChanged(List<User> list) {
}
}
@@ -2,9 +2,9 @@ package control;
import GUI.PrivateChatWindow;
import net.MpChatMessage;
import pitiupi.control.ConnectionManager;
import pitiupi.models.User;
import pitiupi.net.tcp.TcpConnection;
import pitiupi.plugin.Pitiupi;
import plugin.MultiProtocolChat;
import java.util.HashMap;
@@ -14,7 +14,7 @@ import java.util.Set;
public class PrivateChatController {
private final Pitiupi pitiupi;
private final ConnectionManager connectionManager;
private final MultiProtocolChat multiProtocolChat;
/*
@@ -29,16 +29,20 @@ public class PrivateChatController {
private final Set<String> pendingConnections;
public PrivateChatController(
Pitiupi pitiupi,
ConnectionManager connectionManager,
MultiProtocolChat multiProtocolChat
) {
this.pitiupi = pitiupi;
this.connectionManager = connectionManager;
this.multiProtocolChat = multiProtocolChat;
this.windows = new HashMap<>();
this.pendingConnections = new HashSet<>();
}
private static String addressKey(TcpConnection connection) {
return connection.getAddress().getHostAddress();
}
public void openChat(User user) {
String address = user.getAddress().getHostAddress();
@@ -70,7 +74,7 @@ public class PrivateChatController {
}
public void receiveMessage(TcpConnection connection, MpChatMessage message) {
String address = connection.getAddress().toString();
String address = addressKey(connection);
PrivateChatWindow window = windows.get(address);
@@ -85,12 +89,12 @@ public class PrivateChatController {
* Deve ser chamado quando uma nova conexão privada chegar.
*/
public void connectionReceived(TcpConnection connection) {
String address = connection.getAddress().toString();
String address = addressKey(connection);
pendingConnections.remove(address);
PrivateChatWindow window = new PrivateChatWindow(
pitiupi,
connectionManager,
connection
);
@@ -105,7 +109,7 @@ public class PrivateChatController {
* Fecha a janela e remove o relacionamento.
*/
public void closeChat(TcpConnection connection) {
String address = connection.getAddress().toString();
String address = addressKey(connection);
PrivateChatWindow window = windows.remove(address);
@@ -113,4 +117,4 @@ public class PrivateChatController {
window.dispose();
}
}
}
}
@@ -1,16 +1,16 @@
package plugin;
import GUI.MulticastMpChatMenu;
import GUI.PrivateChatWindow;
import GUI.PublicChatWindow;
import GUI.UnicastMpChatMenu;
import control.PrivateChatController;
import net.MpChatMessage;
import pitiupi.control.ConnectionManager;
import pitiupi.control.GuiManager;
import pitiupi.models.Menus.MenuCategory;
import pitiupi.models.Messages.Message;
import pitiupi.net.tcp.TcpConnection;
import pitiupi.models.Plugin;
import pitiupi.plugin.Pitiupi;
import pitiupi.net.tcp.TcpConnection;
import javax.swing.*;
import java.io.ByteArrayInputStream;
@@ -20,7 +20,7 @@ import java.net.InetAddress;
import java.util.List;
public class MultiProtocolChat extends Plugin {
Pitiupi pitiupi;
private ConnectionManager connectionManager;
PrivateChatController privateChatController;
PublicChatWindow publicChatWindow;
@@ -46,18 +46,18 @@ public class MultiProtocolChat extends Plugin {
}
@Override
public void createPlugin(Pitiupi pitiupi) {
this.pitiupi = pitiupi;
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
this.connectionManager = connectionManager;
privateChatController = new PrivateChatController(pitiupi, this);
privateChatController = new PrivateChatController(connectionManager, this);
publicChatWindow = new PublicChatWindow(pitiupi);
publicChatWindow = new PublicChatWindow(connectionManager);
MulticastMpChatMenu multicastMpChatMenu = new MulticastMpChatMenu(publicChatWindow);
UnicastMpChatMenu unicastMpChatMenu = new UnicastMpChatMenu(privateChatController);
pitiupi.createMenu(MenuCategory.Social, multicastMpChatMenu);
pitiupi.createMenu(MenuCategory.Social, unicastMpChatMenu);
guiManager.createMenu(MenuCategory.Social, multicastMpChatMenu);
guiManager.createMenu(MenuCategory.Social, unicastMpChatMenu);
}
@Override
@@ -102,7 +102,11 @@ public class MultiProtocolChat extends Plugin {
public void openConnection(String ip) {
try {
pitiupi.connectTcp(InetAddress.getByName(ip), getIdentifier(), getIdentifier());
connectionManager.connectPluginTcp(
InetAddress.getByName(ip),
getIdentifier(),
getIdentifier()
);
}
catch (Exception e) {
System.out.println("Error opening connection: " + e.getMessage());
@@ -1,6 +0,0 @@
#Tue, 29 Sep 2026 11:04:58 -0300
C\:\\Users\\Gustavo\\OneDrive\\Documentos\\Faculdade\\2026_2\\Monitoria\\pitiupi\\pitiupi.screenCast=
C\:\\Users\\Gustavo\\OneDrive\\Documentos\\Faculdade\\2026_2\\Monitoria\\pitiupi\\pitiupi=
@@ -1 +0,0 @@
pitiupi.screenCast.plugin.ScreenCastPlugin
-32
View File
@@ -1,32 +0,0 @@
========================
BUILD OUTPUT DESCRIPTION
========================
When you build an Java application project that has a main class, the IDE
automatically copies all of the JAR
files on the projects classpath to your projects dist/lib folder. The IDE
also adds each of the JAR files to the Class-Path element in the application
JAR files manifest file (MANIFEST.MF).
To run the project from the command line, go to the dist folder and
type the following:
java -jar "pitiupi.screenCast.jar"
To distribute this project, zip up the dist folder (including the lib folder)
and distribute the ZIP file.
Notes:
* If two JAR files on the project classpath have the same name, only the first
JAR file is copied to the lib folder.
* Only JAR files are copied to the lib folder.
If the classpath contains other types of files or folders, these files (folders)
are not copied.
* If a library on the projects classpath also has a Class-Path element
specified in the manifest,the content of the Class-Path element has to be on
the projects runtime path.
* To set a main class in a standard Java project, right-click the project node
in the Projects window and choose Properties. Then click Run and enter the
class name in the Main Class field. Alternatively, you can manually type the
class name in the manifest Main-Class element.
@@ -4,28 +4,49 @@
*/
package pitiupi.screenCast.GUI;
import java.awt.event.ActionEvent;
import javax.swing.JMenu;
import java.util.List;
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;
import pitiupi.models.Menus.MulticastMenus.MulticastMenu;
import pitiupi.models.Menus.MulticastMenus.MulticastSubMenu;
/**
*
* @author flavio
*/
public class ScreenCastMenu extends JMenu {
public class ScreenCastMenu extends MulticastSubMenu {
private final ScreenCastWindow screenCastWindow;
private final javax.swing.JMenuItem menuItem;
private final ScreenCastShowMenu showMenu;
public ScreenCastMenu(ScreenCastWindow screenCastWindow) {
this.screenCastWindow = screenCastWindow;
this.setText("Screen Cast");
menuItem = new javax.swing.JMenuItem();
menuItem.setText("ScreenCast Window");
menuItem.addActionListener(this::chatMenuActionPerformed);
this.add(menuItem);
this.showMenu = new ScreenCastShowMenu(screenCastWindow);
}
private void chatMenuActionPerformed(ActionEvent ae) {
this.screenCastWindow.setVisible(true);
@Override
public String getText() {
return "Screen Cast";
}
@Override
public List<MulticastMenu> getMenus() {
return List.of(showMenu);
}
private static final class ScreenCastShowMenu extends MulticastActionMenu {
private final ScreenCastWindow screenCastWindow;
ScreenCastShowMenu(ScreenCastWindow screenCastWindow) {
this.screenCastWindow = screenCastWindow;
}
@Override
public String getText() {
return "ScreenCast Window";
}
@Override
public void onClick() {
screenCastWindow.setVisible(true);
}
}
}
@@ -22,8 +22,6 @@ import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import pitiupi.GUI.MainWindow;
import pitiupi.net.Message;
/**
*
@@ -5,7 +5,7 @@
*/
package pitiupi.screenCast.net;
import pitiupi.net.Message;
import pitiupi.models.Messages.Message;
/**
*
@@ -46,7 +46,7 @@ public class ScreenCastMessage extends Message {
}
/**
* @param imageByte the image to set
* @param imageByte the imageByte to set
*/
public void setImageByte(byte[] imageByte) {
this.imageByte = imageByte;
@@ -4,14 +4,13 @@
*/
package pitiupi.screenCast.plugin;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import pitiupi.GUI.MainWindow;
import java.util.List;
import pitiupi.control.ConnectionManager;
import pitiupi.control.GuiManager;
import pitiupi.net.Message;
import pitiupi.plugin.Plugin;
import pitiupi.models.Menus.MenuCategory;
import pitiupi.models.Messages.Message;
import pitiupi.models.Plugin;
import pitiupi.screenCast.GUI.ScreenCastMenu;
import pitiupi.screenCast.GUI.ScreenCastWindow;
import pitiupi.screenCast.net.ScreenCastMessage;
@@ -20,7 +19,7 @@ import pitiupi.screenCast.net.ScreenCastMessage;
*
* @author flavio
*/
public class ScreenCastPlugin implements Plugin {
public class ScreenCastPlugin extends Plugin {
private ScreenCastWindow screenCastWindow;
@@ -47,26 +46,18 @@ public class ScreenCastPlugin implements Plugin {
@Override
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
this.screenCastWindow = new ScreenCastWindow(connectionManager, guiManager);
guiManager.addMenu(new ScreenCastMenu(screenCastWindow));
guiManager.createMenu(MenuCategory.Social, new ScreenCastMenu(screenCastWindow));
}
@Override
public void receiveMessage(byte[] message) {
ByteArrayInputStream bis = new ByteArrayInputStream(message);
ObjectInputStream ois;
try {
ois = new ObjectInputStream(bis);
if (ois.readObject() instanceof ScreenCastMessage msg) {
this.screenCastWindow.receiveScreenCastMessage(msg);
}
} catch (IOException | ClassNotFoundException ex) {
System.out.println(ex.toString());
public List<Class<? extends Message>> getMessages() {
return List.of(ScreenCastMessage.class);
}
@Override
public void receiveMessage(Message message) {
if (message instanceof ScreenCastMessage msg) {
this.screenCastWindow.receiveScreenCastMessage(msg);
}
}
@Override
public Message getMessage() {
return new ScreenCastMessage();
}
}
+3 -545
View File
@@ -1,547 +1,5 @@
# Visão geral
# Documentação (legado)
O `Pitiupi` é uma plataforma P2P baseada em uma arquitetura de plugins,
desenvolvida para permitir a criação de aplicações distribuídas sobre uma
infraestrutura comum de comunicação em rede.
Este arquivo foi substituído pela árvore em **[docs/](../../docs/README.md)**.
A aplicação fornece mecanismos de descoberta de usuários, comunicação entre
peers, troca de mensagens serializadas e integração de extensões por meio de
plugins. A comunicação de rede utiliza dois protocolos de transporte: UDP,
empregado principalmente na descoberta e comunicação multicast entre as
instâncias, e TCP, utilizado para comunicação direta e persistente entre peers.
Dessa forma, novos comportamentos podem ser adicionados por meio de plugins
sem modificar o núcleo da aplicação.
O projeto foi desenvolvido como suporte às práticas da disciplina de Redes de
Computadores, servindo também como base para experimentação de aplicações
distribuídas e desenvolvimento de projetos acadêmicos.
# Arquitetura geral
A aplicação é dividida em três grupos principais:
- **Núcleo da aplicação**: A `MainWindow` atua como ponto central da aplicação,
sendo responsável por inicializar e integrar esses componentes durante a
execução.
- **Sistema de plugins**: responsável por adicionar funcionalidades sem
alterar o núcleo da aplicação.
- **Comunicação de rede**: responsável pela troca de mensagens entre
instâncias da aplicação.
## Inicialização da aplicação
1. A MainWindow é criada.
2. O SocketUDP é inicializado, associa-se à porta configurada e ingressa no
grupo multicast.
3. O SocketTCP é inicializado e cria um ServerSocket na porta da aplicação
para aceitar conexões TCP.
4. Os mecanismos de comunicação iniciam seus respectivos processos de recepção.
5. O HeartbeatManager é criado e inicia o envio periódico de heartbeats.
6. O painel de usuários online é registrado como listener do HeartbeatManager.
7. O PluginLoader procura e carrega os plugins disponíveis.
# Sistema de comunicação
A comunicação entre as instâncias da aplicação é realizada por meio de dois
mecanismos complementares:
- **UDP multicast/unicast**, implementado por `SocketUDP`
- **TCP**, implementado por `SocketTCP` e `TcpConnection`
Ambos os mecanismos utilizam mensagens serializadas em `byte[]`, permitindo que
os plugins permaneçam independentes dos detalhes do protocolo de transporte.
## Comunicação UDP
A classe `SocketUDP` é responsável pela comunicação UDP da aplicação.
Ela utiliza um `MulticastSocket` associado à porta configurada pela aplicação e
ingressa no grupo multicast `224.0.0.3`. Por ser baseada em UDP, a comunicação não
estabelece uma conexão permanente entre os peers.
A classe estende `Thread`, mantendo um processo contínuo de recepção de datagramas
enquanto a aplicação está em execução.
O SocketUDP suporta dois modos de envio:
- **multicast**, destinado ao grupo de peers
- **unicast**, destinado diretamente ao endereço IP de um peer específico
### Inicialização
Durante sua inicialização, o `SocketUDP`:
1. obtém o endereço do grupo multicast;
2. cria um `MulticastSocket` utilizando a porta configurada na `MainWindow`;
3. configura os buffers de envio e recepção;
4. habilita o reuso do endereço;
5. ingressa no grupo multicast.
O tamanho dos buffers de comunicação é configurado para `256000` bytes.
### Envio de mensagens UDP
O `SocketUDP` suporta dois modos de envio de mensagens: multicast e unicast, ambos
baseados no mesmo mecanismo interno de criação e envio de DatagramPacket.
O método `sendMulticast()` envia uma mensagem para o grupo multicast da aplicação.
Nesse caso, o `DatagramPacket` é direcionado ao endereço multicast configurado,
permitindo que todas as instâncias participantes do grupo recebam a mensagem
simultaneamente.
Já o método `sendUnicast()` permite enviar uma mensagem diretamente para um peer
específico. Nesse caso, o `DatagramPacket` é direcionado ao endereço IP informado pelo
chamador, mantendo a mesma porta utilizada pela aplicação.
Ambos os métodos são, na prática, abstrações de um único mecanismo interno de envio,
que utiliza o mesmo processo de criação e envio de `DatagramPacket` por baixo dos
panos. A diferença entre eles está apenas no endereço de destino: enquanto o multicast
utiliza o grupo compartilhado, o unicast direciona o pacote a um IP específico.
### Recepção de mensagens
O método `receive()` mantém um loop aguardando novos datagramas.
Ao receber uma mensagem, o `SocketUDP` inicialmente tenta identificar se os dados
correspondem a uma `HeartbeatMessage`.
O processamento segue o seguinte fluxo:
1. O datagrama é recebido pelo `MulticastSocket`.
2. Os dados recebidos são analisados pelo método `tryParseHeartbeat()`.
3. Caso seja identificada uma `HeartbeatMessage`, ela é encaminhada ao
`HeartbeatManager`, juntamente com o endereço IP de origem.
4. Caso não seja um heartbeat, a mensagem é encaminhada para todos os plugins
carregados.
5. Cada plugin decide se a mensagem recebida pertence à sua funcionalidade.
Essa separação mantém as mensagens de infraestrutura, como heartbeat, sob
responsabilidade do núcleo da aplicação, enquanto as mensagens específicas das
funcionalidades são processadas pelos plugins.
### Identificação de mensagens de heartbeat
O método `tryParseHeartbeat()` tenta desserializar os dados recebidos e verificar se o
objeto resultante é uma instância de `HeartbeatMessage`.
Caso a mensagem não possa ser desserializada ou não corresponda ao tipo esperado, o
método retorna `null`. Nesse caso, a mensagem é tratada como uma mensagem destinada aos
plugins.
### Encerramento
O método `close()` interrompe a execução do processo de recepção, remove a aplicação
do grupo multicast e fecha o `MulticastSocket`.
A variável `running` é utilizada para sinalizar que o processo de recepção deve ser
encerrado.
## Comunicação TCP
A comunicação TCP é implementada pela classe `SocketTCP`.
Diferentemente do UDP, o TCP estabelece conexões entre dois peers. Essas conexões
são mantidas enquanto forem necessárias e podem ser reutilizadas para o envio de
múltiplas mensagens.
A arquitetura utiliza uma conexão TCP persistente por endereço de peer.
O SocketTCP possui um mapa de conexões:
`InetAddress` -> `TcpConnection`
Esse mapa permite localizar rapidamente uma conexão existente para determinado peer e
reutilizá-la durante novos envios.
### SocketTCP
A classe `SocketTCP` estende `Thread` e implementa `PeerListener`.
Ao ser criada, ela inicializa um `ServerSocket` utilizando a porta configurada na
aplicação.
O servidor TCP permanece aguardando novas conexões enquanto a aplicação estiver
em execução.
### Envio de mensagens TCP
O método `send()` recebe uma mensagem serializada e o endereço IP do peer destinatário.
Antes de enviar a mensagem, o `SocketTCP` verifica se já existe uma conexão válida com
o peer.
O comportamento é:
1. Procurar uma conexão existente no mapa de conexões.
2. Verificar se a conexão está fechada.
3. Caso não exista uma conexão válida, criar um novo Socket TCP para o endereço do peer.
4. Encapsular o socket em uma `TcpConnection`.
5. Registrar a conexão no mapa.
6. Iniciar uma tarefa de recepção para essa conexão.
7. Enviar a mensagem pela conexão.
Assim, uma conexão TCP existente pode ser reutilizada para várias mensagens, evitando
a criação de um novo socket para cada envio.
### Recepção de conexões
O método `receive()` permanece aguardando novas conexões através de
`ServerSocket.accept()`.
Quando uma conexão é aceita:
O endereço IP do peer é obtido.
Um objeto `TcpConnection` é criado para encapsular o socket.
A conexão é registrada no mapa de conexões.
Caso já exista uma conexão associada ao mesmo endereço, a conexão anterior é encerrada.
Uma tarefa independente de recepção é criada para a nova conexão.
Esse modelo permite que o servidor aceite múltiplas conexões sem bloquear o
processamento das conexões já existentes.
### Recepção concorrente
A recepção das mensagens TCP é realizada de maneira concorrente por meio de um
`ExecutorService`.
Cada conexão possui uma tarefa própria responsável por receber continuamente as
mensagens.
O fluxo de recepção é:
```
SocketTCP
|
+-- Peer A → TcpConnection → tarefa de recepção
|
+-- Peer B → TcpConnection → tarefa de recepção
|
+-- Peer C → TcpConnection → tarefa de recepção
```
Dessa forma, uma conexão bloqueada ou aguardando dados não impede que outras conexões
continuem sendo processadas.
Quando uma mensagem é recebida por uma `TcpConnection`, ela é encaminhada para todos os
plugins registrados na `MainWindow`.
O plugin responsável pela funcionalidade deve identificar o tipo da mensagem e
realizar o processamento correspondente.
### TcpConnection
A classe TcpConnection encapsula uma conexão TCP individual entre dois peers.
Ela mantém:
o Socket utilizado na conexão;
um DataInputStream para recepção;
um DataOutputStream para envio.
Além de encapsular esses recursos, a classe é responsável pelo framing das mensagens.
### Framing das mensagens TCP
A classe `TcpConnection` encapsula uma conexão TCP individual entre dois peers.
Ela mantém:
1. o `Socket` utilizado na conexão;
2. um `DataInputStream` para recepção;
3. um `DataOutputStream` para envio.
Além de encapsular esses recursos, a classe é responsável pelo framing das mensagens.
Framing das mensagens TCP
O protocolo TCP fornece um fluxo contínuo de bytes e não preserva os limites das
mensagens enviadas pela aplicação.
Por esse motivo, é adicionado o tamanho da mensagem antes do conteúdo.
O formato utilizado é:
```
+----------------------+----------------------+
| tamanho da mensagem | dados da mensagem |
+----------------------+----------------------+
4 bytes N bytes
```
O tamanho é enviado como um `int`, seguido pelos bytes da mensagem.
No envio, o método `TcpConnection.send()`:
1. obtém o tamanho do vetor de bytes;
2. escreve o tamanho no `DataOutputStream`;
3. escreve os dados da mensagem;
4. realiza `flush()` no stream.
O acesso ao stream de saída é sincronizado para evitar que envios concorrentes
misturem seus dados.
Na recepção, o método `receive()`:
1. lê o tamanho da mensagem;
2. verifica se o tamanho é válido;
3. lê a quantidade correspondente de bytes;
4. retorna o vetor de bytes completo.
Esse mecanismo permite que várias mensagens sejam transmitidas pela mesma conexão
TCP sem que o receptor perca a delimitação entre elas.
### Integração entre TCP e gerenciamento de peers
O `SocketTCP` implementa a interface PeerListener para acompanhar as alterações na
lista de peers ativos.
O `HeartbeatManager` mantém a lista de peers atualmente detectados na rede. Quando
essa lista é alterada, o SocketTCP recebe a notificação por meio do método
`onPeersChanged()`.
O método obtém os endereços IP dos peers atualmente ativos e compara esses endereços
com as conexões TCP existentes.
Quando uma conexão pertence a um peer que não está mais ativo:
a conexão é removida do mapa;
a conexão TCP é encerrada;
os recursos associados são liberados.
Esse mecanismo impede que conexões TCP permaneçam abertas indefinidamente para peers
que já deixaram de participar da rede.
O fluxo de integração pode ser representado da seguinte forma:
```
HeartbeatManager
|
| peers ativos alterados
|
v
SocketTCP.onPeersChanged()
|
| compara peers ativos
|
v
+---- peer ativo ------> mantém conexão
|
+---- peer inativo ----> fecha conexão
```
### Message
A classe `Message` representa a estrutura base das mensagens trocadas pela aplicação.
Todas as mensagens utilizadas pelo sistema devem herdar dessa classe. Ela implementa
`Serializable`, permitindo que objetos de mensagem sejam convertidos em vetores de
bytes para transmissão pela rede.
A serialização é realizada pelo método `toByteArray()`, que transforma uma instância
da mensagem em uma representação binária enviada pelos mecanismos de comunicação.
As mensagens específicas devem estender essa classe, adicionando os atributos e
comportamentos necessários para cada funcionalidade.
A camada de transporte não depende do conteúdo específico das mensagens. Tanto o
UDP quanto o TCP recebem mensagens serializadas como `byte[]`.
### Heartbeat
O mecanismo de heartbeat é utilizado para identificar quais usuários estão ativos
na rede.
A aplicação envia periodicamente uma `HeartbeatMessage` contendo informações do usuário,
um identificador único da instância da aplicação e um timestamp.
Essas mensagens são transmitidas utilizando a comunicação UDP multicast.
Quando outra instância recebe uma mensagem de heartbeat, o `SocketUDP` identifica
o tipo da mensagem e encaminha o objeto ao `HeartbeatManager`, juntamente com o endereço
IP de origem.
O `HeartbeatManager` registra ou atualiza o peer correspondente, utilizando o
identificador da instância para diferenciá-lo das demais.
Um peer é considerado inativo quando permanece mais de 15 segundos sem receber um novo
heartbeat. O `HeartbeatManager` então remove o peer da lista e notifica todos os
componentes registrados como listeners de mudanças de peers.
Esses listeners podem incluir diferentes partes da aplicação, como componentes
da interface gráfica, mecanismos de comunicação (como o `SocketTCP`) e também plugins
que desejem reagir à presença ou ausência de usuários na rede.
Dessa forma, o sistema permite que tanto o núcleo da aplicação quanto extensões
externas sejam notificados sobre alterações no estado da rede, mantendo a arquitetura
flexível e extensível.
O fluxo completo é:
```
HeartbeatManager
|
| cria HeartbeatMessage
|
v
Message.toByteArray()
|
v
SocketUDP.sendMulticast()
|
v
UDP Multicast
|
v
SocketUDP.receive()
|
v
HeartbeatManager.receiveHeartbeat()
|
v
Lista de peers ativos
|
+----> Interface de usuários online
|
+----> SocketTCP (PeerListener)
|
+----> Plugins (PeerListener)
```
## Sistema de plugins
O sistema de plugins é responsável por permitir a extensão das funcionalidades da
aplicação de forma modular, sem a necessidade de alterações no núcleo do sistema.
Ele define um contrato comum que deve ser seguido por todas as extensões, garantindo
que possam ser carregadas dinamicamente e integradas à comunicação e à interface da
aplicação.
### Interface Plugin
A interface `Plugin` define o contrato que deve ser implementado por qualquer
extensão da aplicação.
Um plugin deve fornecer informações básicas de identificação (`name`,
`author` e `version`), inicializar seu estado através de `createPlugin()` e
participar do sistema de comunicação utilizando mensagens derivadas de
`Message`.
Os principais métodos são:
#### Identificação
Os métodos:
- `getName()`
- `getAuthor()`
- `getVersion()`
fornecem informações utilizadas pela aplicação para identificar o plugin.
#### Inicialização
`createPlugin(MainWindow window)`
É chamado após o carregamento do plugin e permite que ele registre seus
componentes na aplicação, como menus ou elementos de interface.
#### Comunicação
`getMessage()`
Deve retornar uma mensagem utilizada pelo plugin para comunicação com outras
instâncias da aplicação.
`receiveMessage(byte[] message)`
É chamado pela aplicação sempre que uma mensagem é recebida. Cada plugin deve
verificar se a mensagem pertence ao seu tipo e realizar o processamento
correspondente.
### Descoberta de plugins
Para serem reconhecidos, os plugins devem possuir os arquivos de configuração
do Java Service Provider Interface (SPI):
```
MeusPlugins
|
+---META-INF
|
+---services
|
+---pitiupi.models.Plugin
```
O arquivo `pitiupi.models.Plugin` deve conter o nome completo das classes que
implementam a interface Plugin:
```
MeusPlugins.plugins.NomeDaClasse
```
Para serem carregados, os plugins devem estar empacotados como arquivos JAR
e colocados no diretório plugins da aplicação.
### Carregamento de plugins
Ao iniciar a aplicação, o `PluginLoader` executa os seguintes passos:
1. Procura arquivos `.jar` dentro do diretório `plugins`.
2. Cria um `URLClassLoader` contendo os JARs encontrados.
3. Utiliza `ServiceLoader` para localizar classes que implementam
`Plugin`.
4. Instancia cada plugin encontrado.
5. Chama o método `createPlugin(MainWindow)`.
6. Registra o plugin na lista de plugins ativos da aplicação.
## Interface gráfica
A interface gráfica da aplicação é baseada em Swing e é centralizada na
classe `MainWindow`.
Durante a inicialização, cada plugin recebe uma referência para a
`MainWindow` através do método `createPlugin(MainWindow)`.
Essa referência permite integrar componentes gráficos à aplicação, sendo a
principal forma de extensão a adição de menus à barra de menus:
```
JMenu menu = new JMenu("Meu Plugin");
window.addMenu(menu);
```
Visão geral da comunicação
A arquitetura de comunicação pode ser resumida da seguinte maneira:
```
+------------------+
| MainWindow |
+--------+---------+
|
+--------------------+--------------------+
| | |
v v v
HeartbeatManager SocketUDP SocketTCP
| | |
| | |
v v v
Heartbeat UDP Multicast TCP Connections
| |
| |
+---------+----------+
|
v
Plugins
|
v
Message
```
O UDP multicast fornece a infraestrutura de descoberta e manutenção da presença dos
peers, além de permitir comunicação multicast e unicast.
O TCP fornece canais diretos e persistentes entre peers, com uma conexão reutilizável
por endereço.
Os dois mecanismos encaminham as mensagens destinadas às funcionalidades da aplicação
para os plugins, mantendo separadas as responsabilidades de transporte, infraestrutura
e lógica específica de cada extensão.
Conteúdo técnico fatiado em [docs/documentation/](../../docs/documentation/README.md).
+4 -1
View File
@@ -1,2 +1,5 @@
# Como criar plugins para o Share my Sheet
# Tutorial de plugin (legado)
Use o tutorial atualizado:
**[docs/documentation/10_tutorial_plugin.md](../../docs/documentation/10_tutorial_plugin.md)**
+10
View File
@@ -0,0 +1,10 @@
# Documentação movida
A documentação canônica do Pitiupi está na raiz do repositório:
**[../../docs/README.md](../../docs/README.md)**
Arquivos legados nesta pasta:
- [Documentation.md](Documentation.md) — redirecionamento
- [PluginTutorial.md](PluginTutorial.md) — redirecionamento
+18
View File
@@ -0,0 +1,18 @@
# Plugins instalados
O núcleo carrega arquivos `.jar` desta pasta ao iniciar (relativo ao diretório de trabalho — execute `java -jar dist/pitiupi.jar` a partir de `pitiupi/`).
## Desenvolvimento
Após compilar os módulos de referência, use symlinks (Linux):
```bash
cd pitiupi/plugins
ln -sf ../../pitiupi.chat/dist/pitiupi.chat.jar pitiupi.chat.jar
ln -sf ../../pitiupi.screenCast/dist/pitiupi.screenCast.jar pitiupi.screenCast.jar
ln -sf ../../pitiupi.mpChat/dist/mpChat.jar mpChat.jar
```
Ordem de build: núcleo (`pitiupi`) primeiro, depois cada plugin. Ver [docs/documentation/00_quickstart.md](../../docs/documentation/00_quickstart.md).
Os JARs gerados não devem ser commitados — apenas este README e symlinks locais.