atualizar javadoc dos das classes SocketTCP, ConnectionManager, ConnectionFactory e TcpConnection

This commit is contained in:
GustavoHMDS 2026-09-09 00:14:15 -03:00
parent 93ac292f72
commit b5bbd1df2d
4 changed files with 447 additions and 44 deletions

View File

@ -11,10 +11,44 @@ import java.net.SocketTimeoutException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
/**
* Fábrica responsável pela criação e configuração de conexões TCP.
*
* <p>Esta classe centraliza o estabelecimento de conexões de saída e a
* configuração de conexões recebidas. Antes de uma conexão ser considerada
* válida, é realizado um handshake entre os peers para identificar o remetente,
* o destinatário e se a conexão é privada.</p>
*
* <p>Durante o handshake é utilizado um timeout menor, definido por
* {@link #HANDSHAKE_TIMEOUT}. Após a validação bem-sucedida, a conexão passa
* a utilizar o timeout padrão definido por {@link #STANDARD_TIMEOUT}.</p>
*/
public class ConnectionFactory { public class ConnectionFactory {
private static final int HANDSHAKE_TIMEOUT = 5000; private static final int HANDSHAKE_TIMEOUT = 5000;
private static final int STANDARD_TIMEOUT = 300_000; private static final int STANDARD_TIMEOUT = 300_000;
/**
* Cria uma conexão TCP de saída com o peer de destino e realiza o handshake
* necessário para validá-la.
*
* <p>A conexão é configurada com os identificadores do remetente e do
* destinatário e com o tipo indicado pelo parâmetro {@code isPrivate}.
* Após o estabelecimento do socket, o método envia um
* {@link HandshakeMessage} e aguarda uma resposta válida.</p>
*
* <p>Se o handshake for validado com sucesso, o timeout da conexão é
* alterado de {@link #HANDSHAKE_TIMEOUT} para {@link #STANDARD_TIMEOUT}
* e a conexão é retornada. Caso contrário, {@code null} é retornado.</p>
*
* @param address endereço IP do peer de destino.
* @param port porta TCP na qual o peer de destino está aguardando conexões.
* @param sender identificador do remetente da conexão.
* @param receiver identificador do destinatário da conexão.
* @param isPrivate {@code true} se a conexão deve ser privada;
* {@code false} se deve ser pública.
* @return a conexão TCP estabelecida e validada ou {@code null} caso
* não seja possível estabelecer ou validar a conexão.
*/
public static TcpConnection createConnection(InetAddress address, int port, String sender, String receiver, boolean isPrivate) { public static TcpConnection createConnection(InetAddress address, int port, String sender, String receiver, boolean isPrivate) {
try { try {
TcpConnection connection; TcpConnection connection;
@ -42,6 +76,22 @@ public class ConnectionFactory {
return null; return null;
} }
/**
* Configura uma conexão TCP recebida e valida o handshake enviado pelo
* peer remoto.
*
* <p>O handshake recebido é utilizado para obter e configurar os
* identificadores do remetente e do destinatário, bem como o tipo da
* conexão. Após a validação bem-sucedida, o timeout da conexão é alterado
* para {@link #STANDARD_TIMEOUT}.</p>
*
* <p>O {@link Socket} recebido deve representar uma conexão TCP aceita
* pelo servidor.</p>
*
* @param socket socket TCP correspondente à conexão recebida.
* @return a conexão TCP configurada e validada ou {@code null} caso
* o handshake seja inválido ou não possa ser processado.
*/
public static TcpConnection receiveConnection(Socket socket) { public static TcpConnection receiveConnection(Socket socket) {
try { try {
TcpConnection connection; TcpConnection connection;
@ -66,6 +116,27 @@ public class ConnectionFactory {
return null; return null;
} }
/**
* Aguarda e valida a mensagem de handshake de uma conexão TCP.
*
* <p>Durante a validação, a conexão utiliza {@link #HANDSHAKE_TIMEOUT}
* como limite para o recebimento do handshake. A mensagem recebida deve
* ser uma instância de {@link Message} e, especificamente, de
* {@link HandshakeMessage}. Mensagens de sistema ou mensagens de outros
* tipos são rejeitadas.</p>
*
* <p>Quando o handshake é recebido, seus campos de remetente e destinatário
* são validados por {@link #validateHandshake(HandshakeMessage, TcpConnection)}.
* Caso a validação seja bem-sucedida, o próprio handshake é retornado.</p>
*
* <p>Em caso de timeout, mensagem inválida ou erro durante o processamento,
* uma mensagem de sistema apropriada pode ser enviada ao peer e
* {@code null} é retornado.</p>
*
* @param connection conexão TCP da qual o handshake será recebido.
* @return o {@link HandshakeMessage} validado ou {@code null} caso o
* handshake não seja recebido ou seja inválido.
*/
private static HandshakeMessage validateConnection(TcpConnection connection) { private static HandshakeMessage validateConnection(TcpConnection connection) {
try { try {
connection.setTimeout(HANDSHAKE_TIMEOUT); connection.setTimeout(HANDSHAKE_TIMEOUT);
@ -113,6 +184,20 @@ public class ConnectionFactory {
return null; return null;
} }
/**
* Valida os identificadores presentes em uma mensagem de handshake.
*
* <p>O remetente e o destinatário devem estar presentes e não podem ser
* vazios ou conter apenas espaços em branco. Quando um dos identificadores
* é inválido, uma mensagem de rejeição é enviada ao peer e a validação
* falha.</p>
*
* @param handshake mensagem de handshake a ser validada.
* @param connection conexão associada ao handshake, utilizada para enviar
* uma mensagem de rejeição quando necessário.
* @return {@code true} se os identificadores do remetente e do destinatário
* forem válidos; {@code false} caso contrário.
*/
private static boolean validateHandshake(HandshakeMessage handshake, TcpConnection connection) { private static boolean validateHandshake(HandshakeMessage handshake, TcpConnection connection) {
String sender = handshake.getSender(); String sender = handshake.getSender();
String receiver = handshake.getReceiver(); String receiver = handshake.getReceiver();

View File

@ -14,11 +14,27 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class ConnectionManager implements PeerListener { /**
* Gerencia as conexões TCP estabelecidas com os peers da aplicação.
*
* <p>As conexões públicas são armazenadas individualmente por endereço IP,
* enquanto as conexões privadas são agrupadas por endereço, permitindo que
* um mesmo peer mantenha múltiplas conexões privadas simultaneamente.</p>
*
* <p>Além de manter o registro das conexões ativas, esta classe é responsável
* por encerrá-las quando necessário e por remover do gerenciamento as
* conexões que forem fechadas. Também acompanha as alterações na lista de
* peers ativos para encerrar conexões associadas a peers que deixaram de
* estar disponíveis.</p>
*/
class ConnectionManager implements PeerListener {
private final Logger logger; private final Logger logger;
private final Map<InetAddress, TcpConnection> publicConnections; private final Map<InetAddress, TcpConnection> publicConnections;
private final Map<InetAddress, List<TcpConnection>> privateConnections; private final Map<InetAddress, List<TcpConnection>> privateConnections;
/**
* Cria um gerenciador de conexões TCP vazio.
*/
public ConnectionManager() { public ConnectionManager() {
publicConnections = new ConcurrentHashMap<>(); publicConnections = new ConcurrentHashMap<>();
privateConnections = new ConcurrentHashMap<>(); privateConnections = new ConcurrentHashMap<>();
@ -26,11 +42,28 @@ public class ConnectionManager implements PeerListener {
logger = Logger.getLogger(getClass().getName()); logger = Logger.getLogger(getClass().getName());
} }
/**
* Obtém a conexão pública registrada para um determinado peer.
*
* @param address endereço IP do peer.
* @return a conexão pública registrada para o endereço ou {@code null}
* caso não exista uma conexão pública registrada.
*/
public TcpConnection getPublicConnection(InetAddress address) { public TcpConnection getPublicConnection(InetAddress address) {
return publicConnections.get(address); return publicConnections.get(address);
} }
public void addPrivateConnection(InetAddress address, TcpConnection connection) { /**
* Registra uma conexão privada associada a um peer.
*
* <p>Um mesmo peer pode possuir múltiplas conexões privadas. Por isso,
* as conexões são armazenadas em uma lista associada ao endereço do
* peer.</p>
*
* @param connection conexão privada a ser registrada.
*/
public void addPrivateConnection(TcpConnection connection) {
List<TcpConnection> connections = List<TcpConnection> connections =
privateConnections.computeIfAbsent(connection.getAddress(), _ -> new ArrayList<>()); privateConnections.computeIfAbsent(connection.getAddress(), _ -> new ArrayList<>());
@ -38,17 +71,54 @@ public class ConnectionManager implements PeerListener {
connections.add(connection); connections.add(connection);
} }
} }
public void addPublicConnection(InetAddress address, TcpConnection connection) {
/**
* Registra uma conexão pública associada a um peer.
*
* <p>Cada peer pode possuir apenas uma conexão pública registrada.
* Quando existe uma conexão pública para o endereço da nova conexão,
* a conexão anterior é encerrada e substituída pela nova.</p>
*
* @param connection nova conexão pública a ser registrada.
*/
public void addPublicConnection(TcpConnection connection) {
TcpConnection oldConnection = publicConnections.put(connection.getAddress(), connection); TcpConnection oldConnection = publicConnections.put(connection.getAddress(), connection);
if (oldConnection != null && oldConnection != connection) { if (oldConnection != null && oldConnection != connection) {
closeConnection(oldConnection); closeConnection(oldConnection);
} }
} }
/**
* Envia uma mensagem de encerramento ao peer e fecha a conexão.
*
* <p>A mensagem enviada informa o motivo do encerramento antes que a
* conexão seja removida do gerenciamento e fechada.</p>
*
* @param connection conexão TCP a ser encerrada.
* @param reason motivo do encerramento que será informado ao peer.
*/
public void closeConnection(TcpConnection connection, String reason) { public void closeConnection(TcpConnection connection, String reason) {
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason); SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
closeConnection(connection); closeConnection(connection);
} }
/**
* Remove uma conexão do gerenciamento e encerra seu socket.
*
* <p>Para conexões privadas, a conexão é removida da lista associada ao
* endereço do peer. Caso essa seja a última conexão privada do endereço,
* a lista também é removida do mapa.</p>
*
* <p>Para conexões públicas, a conexão é removida do mapa somente se
* ainda for a conexão registrada para seu endereço.</p>
*
* <p>Se a conexão estiver fechada ou for {@code null}, nenhuma ação
* adicional de fechamento é realizada.</p>
*
* @param connection conexão TCP a ser removida e encerrada.
*/
public void closeConnection(TcpConnection connection) { public void closeConnection(TcpConnection connection) {
if (connection == null) return; if (connection == null) return;
if (connection.isPrivate()) { if (connection.isPrivate()) {
@ -73,6 +143,13 @@ public class ConnectionManager implements PeerListener {
} }
} }
/**
* Envia uma mensagem de encerramento para todas as conexões de um peer
* e, em seguida, fecha essas conexões.
*
* @param address endereço IP do peer cujas conexões serão encerradas.
* @param reason motivo do encerramento informado ao peer.
*/
public void closeByAdress(InetAddress address, String reason) { public void closeByAdress(InetAddress address, String reason) {
List<TcpConnection> connections = privateConnections.get(address); List<TcpConnection> connections = privateConnections.get(address);
for (TcpConnection connection : connections) { for (TcpConnection connection : connections) {
@ -81,6 +158,14 @@ public class ConnectionManager implements PeerListener {
closeByAdress(address); closeByAdress(address);
} }
/**
* Fecha e remove todas as conexões associadas a um determinado endereço.
*
* <p>Tanto a conexão pública quanto todas as conexões privadas associadas
* ao endereço são encerradas e removidas do gerenciamento.</p>
*
* @param address endereço IP do peer cujas conexões serão encerradas.
*/
public void closeByAdress(InetAddress address) { public void closeByAdress(InetAddress address) {
TcpConnection publicConnection = publicConnections.remove(address); TcpConnection publicConnection = publicConnections.remove(address);
@ -111,6 +196,12 @@ public class ConnectionManager implements PeerListener {
} }
} }
/**
* Envia uma mensagem de encerramento para todas as conexões privadas
* gerenciadas e, em seguida, fecha todas elas.
*
* @param reason motivo do encerramento informado aos peers.
*/
public void closeAllConnections(String reason) { public void closeAllConnections(String reason) {
for (List<TcpConnection> connections : privateConnections.values()) { for (List<TcpConnection> connections : privateConnections.values()) {
for (TcpConnection connection : connections) { for (TcpConnection connection : connections) {
@ -119,6 +210,14 @@ public class ConnectionManager implements PeerListener {
} }
closeAllConnections(); closeAllConnections();
} }
/**
* Fecha e remove todas as conexões públicas e privadas gerenciadas.
*
* <p>Após a execução, não devem permanecer conexões registradas nos
* mapas de conexões deste gerenciador.</p>
*/
public void closeAllConnections() { public void closeAllConnections() {
for (TcpConnection connection : publicConnections.values()) { for (TcpConnection connection : publicConnections.values()) {
closeConnection(connection); closeConnection(connection);
@ -137,11 +236,14 @@ public class ConnectionManager implements PeerListener {
/** /**
* Atualiza as conexões de acordo com os peers atualmente ativos. * Atualiza as conexões de acordo com os peers atualmente ativos.
* *
* <p>Conexões associadas a peers que não estão mais ativos são encerradas * <p>Os endereços presentes na lista de peers são utilizados como
* e removidas.</p> * referência para determinar quais conexões ainda devem permanecer
* ativas. Conexões públicas e privadas associadas a endereços que não
* estão mais presentes na lista de peers são encerradas e removidas.</p>
* *
* @param peers lista atualizada de peers ativos. * @param peers lista atualizada de peers ativos.
*/ */
@Override @Override
public void onPeersChanged(List<PeerInfo> peers) { public void onPeersChanged(List<PeerInfo> peers) {
Set<InetAddress> activePeers = peers.stream().map(PeerInfo::getAddress).collect(Collectors.toSet()); Set<InetAddress> activePeers = peers.stream().map(PeerInfo::getAddress).collect(Collectors.toSet());

View File

@ -47,11 +47,12 @@ public class SocketTCP extends Thread {
/** /**
* Cria o servidor TCP da aplicação. * Cria o servidor TCP da aplicação.
* *
* <p>O servidor utiliza a porta definida pela aplicação e inicia sem * <p>O servidor é criado na porta definida pela aplicação. As conexões
* estabelecer conexões com os peers. As conexões são criadas conforme * com os peers são estabelecidas sob demanda durante o envio ou aceitas
* necessário durante o envio ou recebidas de outros peers.</p> * quando iniciadas por outros peers.</p>
* *
* @param main janela principal da aplicação. * @param main janela principal da aplicação, usada para obter a porta,
* o identificador local e os plugins disponíveis.
*/ */
public SocketTCP(MainWindow main) { public SocketTCP(MainWindow main) {
this.main = main; this.main = main;
@ -66,6 +67,17 @@ public class SocketTCP extends Thread {
} }
} }
/**
* Envia uma mensagem por meio de uma conexão TCP pública com o peer de destino.
*
* <p>A conexão pública existente para o endereço informado é reutilizada.
* Caso não exista uma conexão válida, uma nova conexão é estabelecida antes
* do envio. Falhas durante o estabelecimento ou envio são registradas na
* saída padrão e não são propagadas ao chamador.</p>
*
* @param msg mensagem serializada em bytes que será enviada.
* @param destinationAddress endereço IP do peer que receberá a mensagem.
*/
public void send(byte[] msg, InetAddress destinationAddress) { public void send(byte[] msg, InetAddress destinationAddress) {
try { try {
TcpConnection connection = getPublicConnection(destinationAddress, main.getIdentifier(), main.getIdentifier()); TcpConnection connection = getPublicConnection(destinationAddress, main.getIdentifier(), main.getIdentifier());
@ -80,10 +92,44 @@ public class SocketTCP extends Thread {
} }
} }
/**
* Estabelece uma conexão TCP privada com um plugin específico do peer de destino.
*
* <p>A conexão é criada como privada e passa a ser gerenciada pelo
* {@link ConnectionManager}. O método também inicia a tarefa responsável
* por receber os dados dessa conexão.</p>
*
* @param destinationAddress endereço IP do peer de destino.
* @param senderId identificador do remetente da conexão.
* @param receiverId identificador do plugin que receberá os dados.
* @return a conexão privada estabelecida ou {@code null} quando a criação
* da conexão falhar.
* @throws IllegalArgumentException se o endereço de destino for o endereço
* local desta instância.
*/
public TcpConnection createPrivateConnection(InetAddress destinationAddress, String senderId, String receiverId) { public TcpConnection createPrivateConnection(InetAddress destinationAddress, String senderId, String receiverId) {
return establishOutgoingConnection(destinationAddress, senderId, receiverId, true); return establishOutgoingConnection(destinationAddress, senderId, receiverId, true);
} }
/**
* Obtém uma conexão TCP pública existente com o peer de destino ou estabelece
* uma nova caso não exista uma conexão válida.
*
* <p>As conexões públicas são gerenciadas pelo {@link ConnectionManager}.
* O método primeiro verifica se existe uma conexão pública registrada
* para o endereço de destino e, caso ela exista e não esteja fechada,
* reutiliza-a. Caso contrário, uma nova conexão pública é estabelecida,
* registrada no {@link ConnectionManager} e disponibilizada para uso.</p>
*
* <p>O acesso ao método é sincronizado para evitar que múltiplas threads
* estabeleçam simultaneamente conexões públicas para o mesmo peer.</p>
*
* @param destinationAddress endereço IP do peer de destino.
* @param senderId identificador usado como remetente no estabelecimento da conexão.
* @param receiverId identificador usado como destinatário no estabelecimento da conexão.
* @return uma conexão pública válida ou {@code null} caso não seja possível
* estabelecê-la.
*/
private synchronized TcpConnection getPublicConnection(InetAddress destinationAddress, String senderId, String receiverId) { private synchronized TcpConnection getPublicConnection(InetAddress destinationAddress, String senderId, String receiverId) {
TcpConnection connection; TcpConnection connection;
@ -98,9 +144,9 @@ public class SocketTCP extends Thread {
/** /**
* Aguarda e aceita novas conexões TCP. * Aguarda e aceita novas conexões TCP.
* *
* <p>Cada conexão aceita é associada ao endereço do peer e registrada para * <p>Cada socket aceito é encaminhado para processamento assíncrono,
* que possa ser utilizada tanto para recepção quanto para envio de * interpretado como uma {@link TcpConnection}, registrado pelo
* mensagens.</p> * {@link ConnectionManager} e associado ao receptor adequado.</p>
*/ */
private void receive() { private void receive() {
while (running) { while (running) {
@ -120,6 +166,22 @@ public class SocketTCP extends Thread {
} }
} }
/**
* Estabelece uma conexão TCP de saída com outro peer.
*
* <p>O método impede conexões com o próprio endereço local, cria a conexão
* por meio da {@link ConnectionFactory}, registra-a no gerenciador de
* conexões e inicia uma tarefa para receber dados continuamente.</p>
*
* @param destinationAddress endereço IP do peer de destino.
* @param sender identificador do remetente.
* @param receiver identificador do destinatário.
* @param isPrivate {@code true} para uma conexão destinada a um plugin
* específico; {@code false} para uma conexão pública.
* @return a conexão estabelecida ou {@code null} se a
* {@link ConnectionFactory} não conseguir criá-la.
* @throws IllegalArgumentException se o destino for o endereço desta máquina.
*/
private TcpConnection establishOutgoingConnection(InetAddress destinationAddress, String sender, String receiver, boolean isPrivate) { private TcpConnection establishOutgoingConnection(InetAddress destinationAddress, String sender, String receiver, boolean isPrivate) {
if (address.equals(destinationAddress)) { if (address.equals(destinationAddress)) {
System.out.println("Failed to get socket."); System.out.println("Failed to get socket.");
@ -138,6 +200,15 @@ public class SocketTCP extends Thread {
return connection; return connection;
} }
/**
* Agenda o processamento de uma conexão TCP recebida.
*
* <p>A conexão é interpretada pela {@link ConnectionFactory}, registrada
* no gerenciador e associada a uma tarefa de recepção. O processamento é
* executado de forma assíncrona pelo executor compartilhado.</p>
*
* @param socket socket aceito pelo {@link ServerSocket}.
*/
private void acceptIncomingConnection(Socket socket) { private void acceptIncomingConnection(Socket socket) {
connectionExecutor.submit(() -> { connectionExecutor.submit(() -> {
TcpConnection connection = ConnectionFactory.receiveConnection(socket); TcpConnection connection = ConnectionFactory.receiveConnection(socket);
@ -148,40 +219,67 @@ public class SocketTCP extends Thread {
}); });
} }
/**
* Registra uma conexão no gerenciador de acordo com seu tipo.
*
* <p>Conexões privadas são armazenadas na coleção de conexões privadas;
* as demais são armazenadas na coleção de conexões públicas.</p>
*
* @param connection conexão que será registrada.
*/
private void saveConnection(TcpConnection connection) { private void saveConnection(TcpConnection connection) {
if (connection.isPrivate()) { if (connection.isPrivate()) {
connectionManager.addPrivateConnection(connection.getAddress(), connection); connectionManager.addPrivateConnection(connection);
} }
else { else {
connectionManager.addPublicConnection(connection.getAddress(), connection); connectionManager.addPublicConnection(connection);
} }
} }
/**
* Seleciona e inicia o receptor adequado para uma conexão.
*
* <p>Conexões públicas são tratadas pelo receptor de mensagens públicas.
* Conexões privadas destinadas ao próprio sistema são tratadas
* separadamente; as demais são encaminhadas ao plugin identificado como
* destinatário. Quando solicitado, o plugin também é notificado sobre o
* estabelecimento da conexão.</p>
*
* @param connection conexão cujo fluxo de entrada será processado.
* @param notify indica se o plugin destinatário deve ser notificado sobre
* o recebimento de uma nova conexão privada.
*/
private void startReceiver(TcpConnection connection, boolean notify) { private void startReceiver(TcpConnection connection, boolean notify) {
try { if (!connection.isPrivate()) {
if (!connection.isPrivate()) { publicConnectionReceiver(connection);
publicConnectionReceiver(connection); return;
return;
}
if (connection.getReceiver().equals(main.getIdentifier())) {
systemConnectionReceiver(connection);
return;
}
Plugin receiver = findConnectionReceiver(connection);
if (receiver instanceof PrivateConnectionPlugin plugin) {
if (notify) {
plugin.receivePrivateConnection(connection);
}
privateConnectionReceiver(connection, plugin);
}
} }
catch (IOException ex) {
logger.log(Level.SEVERE, null, ex); if (connection.getReceiver().equals(main.getIdentifier())) {
systemConnectionReceiver(connection);
return;
}
Plugin receiver = findConnectionReceiver(connection);
if (receiver instanceof PrivateConnectionPlugin plugin) {
if (notify) {
plugin.receivePrivateConnection(connection);
}
privateConnectionReceiver(connection, plugin);
} }
} }
/**
* Localiza o plugin responsável por receber uma conexão privada.
*
* <p>A busca é feita pelo identificador do destinatário informado na
* própria conexão. Caso nenhum plugin correspondente seja encontrado,
* uma mensagem de erro é enviada ao peer remoto.</p>
*
* @param connection conexão privada cujo destinatário será localizado.
* @return o plugin com o identificador correspondente ao destinatário,
* ou {@code null} se nenhum plugin for encontrado.
*/
private Plugin findConnectionReceiver(TcpConnection connection) { private Plugin findConnectionReceiver(TcpConnection connection) {
Plugin receiver = null; Plugin receiver = null;
for(Plugin plugin : main.getPlugins()) { for(Plugin plugin : main.getPlugins()) {
@ -201,6 +299,19 @@ public class SocketTCP extends Thread {
return receiver; return receiver;
} }
/**
* Processa continuamente as mensagens recebidas em uma conexão pública.
*
* <p>As mensagens são desserializadas e, quando são instâncias de
* {@link Message}, encaminhadas a todos os plugins da aplicação. Mensagens
* de controle do sistema podem encerrar a conexão ou registrar uma
* condição de erro.</p>
*
* <p>O método também trata timeouts e erros de I/O, removendo a conexão
* do {@link ConnectionManager} quando ela deixa de ser utilizável.</p>
*
* @param connection conexão pública da qual as mensagens serão recebidas.
*/
private void publicConnectionReceiver(TcpConnection connection) { private void publicConnectionReceiver(TcpConnection connection) {
while (running) { while (running) {
try { try {
@ -260,6 +371,18 @@ public class SocketTCP extends Thread {
} }
} }
/**
* Processa os dados recebidos em uma conexão privada e os encaminha
* ao plugin destinatário.
*
* <p>Dados que não representam mensagens de controle são entregues
* diretamente ao {@link PrivateConnectionPlugin}. Mensagens de controle
* relacionadas ao encerramento ou a erros internos provocam a notificação
* do plugin e o fechamento da conexão.</p>
*
* @param connection conexão privada da qual os dados serão recebidos.
* @param plugin plugin responsável pelo processamento dos dados recebidos.
*/
private void privateConnectionReceiver(TcpConnection connection, Plugin plugin) { private void privateConnectionReceiver(TcpConnection connection, Plugin plugin) {
if (plugin instanceof PrivateConnectionPlugin privatePlugin) { if (plugin instanceof PrivateConnectionPlugin privatePlugin) {
while (running) { while (running) {
@ -295,7 +418,16 @@ public class SocketTCP extends Thread {
} }
} }
private void systemConnectionReceiver(TcpConnection connection) throws IOException { /**
* Processa mensagens recebidas em uma conexão destinada ao próprio sistema.
*
* <p>Atualmente, mensagens destinadas ao sistema são apenas verificadas
* quanto ao seu formato de controle. Não processamento adicional de
* mensagens de sistema neste método.</p>
*
* @param connection conexão destinada ao identificador da própria aplicação.
*/
private void systemConnectionReceiver(TcpConnection connection) {
while (running) { while (running) {
try { try {
byte[] message = connection.receive(); byte[] message = connection.receive();
@ -308,12 +440,25 @@ public class SocketTCP extends Thread {
//TODO: Ainda não existem mensagens trocadas entre o sistema. //TODO: Ainda não existem mensagens trocadas entre o sistema.
} }
} catch (SocketTimeoutException ex) { }
catch (SocketTimeoutException ex) {
if (connection.hasTimedOut()) return; if (connection.hasTimedOut()) return;
} }
catch (IOException ex) {
connectionManager.closeConnection(connection);
}
} }
} }
/**
* Encerra uma conexão TCP e registra o motivo informado.
*
* <p>Quando nenhuma descrição é fornecida, é utilizado um motivo padrão.
* O fechamento efetivo da conexão é delegado ao {@link ConnectionManager}.</p>
*
* @param connection conexão que será encerrada.
* @param description motivo do encerramento; pode ser {@code null}.
*/
public void disconnect(TcpConnection connection, String description) { public void disconnect(TcpConnection connection, String description) {
connectionManager.closeConnection( connectionManager.closeConnection(
connection, connection,
@ -321,11 +466,22 @@ public class SocketTCP extends Thread {
); );
} }
@Override @Override
public void run() { public void run() {
receive(); receive();
} }
/**
* Encerra o servidor TCP e libera os recursos associados.
*
* <p>Interrompe o laço de aceitação de conexões, fecha o
* {@link ServerSocket}, encerra todas as conexões gerenciadas e interrompe
* as tarefas do executor.</p>
*
* <p>Após a chamada, esta instância não deve ser reutilizada para aceitar
* novas conexões.</p>
*/
public void close() { public void close() {
running = false; running = false;

View File

@ -8,16 +8,15 @@ import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
/** /**
* Representa uma conexão TCP persistente com um peer. * Representa uma conexão TCP ativa com um peer remoto.
* *
* <p>Encapsula o socket e os streams utilizados para enviar e receber * <p>A conexão permite o envio e recebimento de mensagens, além de manter
* mensagens. Também é responsável pelo framing das mensagens, permitindo * informações sobre o endereço remoto e metadados da conexão, como remetente,
* que várias mensagens sejam transmitidas pela mesma conexão.</p> * destinatário e se a conexão é privada.</p>
* *
* @author Gustavo * @author Gustavo
*/ */
public class TcpConnection { public class TcpConnection {
private static final long TIMEOUT = 300_000; // 5 minutos
private final Socket socket; private final Socket socket;
private final DataInputStream input; private final DataInputStream input;
@ -25,19 +24,35 @@ public class TcpConnection {
private final InetAddress address; private final InetAddress address;
private String sender; private String sender;
private String receiver; private String receiver;
private boolean isPrivate; private boolean isPrivate;
private volatile long lastActivity; private volatile long lastActivity;
private long timeout;
/**
* Cria uma conexão TCP a partir de um socket existente.
*
* @param socket socket utilizado para a comunicação
* @throws IOException se não for possível obter os fluxos de entrada ou saída
* do socket
*/
TcpConnection(Socket socket) throws IOException { TcpConnection(Socket socket) throws IOException {
this.socket = socket; this.socket = socket;
this.input = new DataInputStream(socket.getInputStream()); this.input = new DataInputStream(socket.getInputStream());
this.output = new DataOutputStream(socket.getOutputStream()); this.output = new DataOutputStream(socket.getOutputStream());
this.address = socket.getInetAddress(); this.address = socket.getInetAddress();
this.lastActivity = System.currentTimeMillis(); this.lastActivity = System.currentTimeMillis();
this.timeout = 300_000; // 5 minutos
} }
/**
* Envia uma mensagem pela conexão.
*
* <p>A mensagem é enviada precedida pelo seu tamanho, permitindo que o
* receptor determine quantos bytes devem ser lidos.</p>
*
* @param message mensagem a ser enviada
* @throws IOException se ocorrer um erro durante o envio
*/
public synchronized void send(byte[] message) throws IOException { public synchronized void send(byte[] message) throws IOException {
output.writeInt(message.length); output.writeInt(message.length);
output.write(message); output.write(message);
@ -45,6 +60,16 @@ public class TcpConnection {
updateLastActivity(); updateLastActivity();
} }
/**
* Recebe uma mensagem da conexão.
*
* <p>Primeiro o tamanho da mensagem e, em seguida, aquantidade
* correspondente de bytes.</p>
*
* @return mensagem recebida
* @throws IOException se o tamanho da mensagem for inválido ou se a conexão
* for encerrada antes que a mensagem seja completamente recebida
*/
byte[] receive() throws IOException { byte[] receive() throws IOException {
int length = input.readInt(); int length = input.readInt();
@ -61,28 +86,63 @@ public class TcpConnection {
return message; return message;
} }
/**
* Verifica se o socket da conexão está fechado.
*
* @return {@code true} se o socket estiver fechado;
* {@code false} caso contrário
*/
public boolean isClosed() { public boolean isClosed() {
return socket.isClosed(); return socket.isClosed();
} }
/**
* Fecha o socket da conexão.
*
* @throws IOException se ocorrer um erro ao fechar o socket
*/
void close() throws IOException { void close() throws IOException {
socket.close(); socket.close();
} }
/**
* Retorna o endereço do peer remoto conectado.
*
* @return endereço IP do peer remoto
*/
public InetAddress getAddress() { public InetAddress getAddress() {
return address; return address;
} }
/**
* Atualiza o instante da última atividade da conexão.
*/
private void updateLastActivity() { private void updateLastActivity() {
lastActivity = System.currentTimeMillis(); lastActivity = System.currentTimeMillis();
} }
/**
* Define o tempo limite de leitura do socket.
*
* @param timeout tempo limite, em milissegundos
* @throws SocketException se ocorrer um erro ao definir o tempo limite
*/
void setTimeout(int timeout) throws SocketException { void setTimeout(int timeout) throws SocketException {
socket.setSoTimeout(timeout); socket.setSoTimeout(timeout);
this.timeout = timeout;
} }
/**
* Verifica se a conexão ficou inativa pelo tempo limite configurado.
*
* <p>A verificação é baseada no instante da última atividade registrada
* durante o envio ou recebimento de uma mensagem.</p>
*
* @return {@code true} se a conexão estiver inativa pelo menos 5 minutos;
* {@code false} caso contrário
*/
boolean hasTimedOut() { boolean hasTimedOut() {
return System.currentTimeMillis() - lastActivity >= TIMEOUT; return System.currentTimeMillis() - lastActivity >= timeout;
} }
public String getReceiver() { public String getReceiver() {