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.
- * - *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}.
- */ -public class ConnectionFactory { - private static final int HANDSHAKE_TIMEOUT = 1000; - 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. - * - *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.
- * - *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.
- * - * @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) { - try { - TcpConnection connection; - - Socket socket = new Socket(address, port); - connection = new TcpConnection(socket); - connection.setSender(sender); - connection.setReceiver(receiver); - connection.setPrivate(isPrivate); - - HandshakeMessage handshake = new HandshakeMessage(isPrivate, sender, receiver); - - connection.send(handshake.toByteArray()); - HandshakeMessage handshakeResponse = validateConnection(connection); - - if (handshakeResponse == null) { - return null; - } - - connection.setTimeout(STANDARD_TIMEOUT); - - return connection; - } - catch (IOException ex) { - Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex); - } - return null; - } - - /** - * Configura uma conexão TCP recebida e valida o handshake enviado pelo - * peer remoto. - * - *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}.
- * - *O {@link Socket} recebido deve representar uma conexão TCP já aceita - * pelo servidor.
- * - * @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) { - try { - TcpConnection connection; - - connection = new TcpConnection(socket); - - HandshakeMessage handshake = validateConnection(connection); - - if (handshake == null) return null; - - connection.send(handshake.createResponse().toByteArray()); - - connection.setSender(handshake.getSender()); - connection.setReceiver(handshake.getReceiver()); - connection.setPrivate(handshake.isPrivate()); - - connection.setTimeout(STANDARD_TIMEOUT); - - return connection; - } - catch (IOException ex) { - Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex); - } - return null; - } - - /** - * Aguarda e valida a mensagem de handshake de uma conexão TCP. - * - *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.
- * - *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.
- * - *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.
- * - * @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) { - try { - connection.setTimeout(HANDSHAKE_TIMEOUT); - byte[] bytes = connection.receive(); - - ByteArrayInputStream bis = new ByteArrayInputStream(bytes); - ObjectInputStream ois = new ObjectInputStream(bis); - - Object object = ois.readObject(); - - if (object instanceof Message message) { - InfoMessage sysInfo = InfoMessage.parseSystemInfo(bytes); - if (sysInfo != null) return null; - - - if (message instanceof HandshakeMessage handshake) { - return (validateHandshake(handshake, connection)) ? handshake : null; - } - } - - InfoMessage.sendSystemInfo( - connection, - InfoType.CONNECTION_REJECTED, - "The message was not from an allowed type." - ); - } - catch (SocketTimeoutException ex) { - InfoMessage.sendSystemInfo( - connection, - InfoType.CONNECTION_REJECTED, - "No handshake was received within the allowed time." - ); - } - catch (ClassNotFoundException | IOException ex) { - Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex); - - InfoMessage.sendSystemInfo( - connection, - InfoType.INTERNAL_ERROR, - "The server could not process the handshake." - ); - } - - return null; - } - - /** - * Valida os identificadores presentes em uma mensagem de handshake. - * - *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.
- * - * @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) { - String sender = handshake.getSender(); - String receiver = handshake.getReceiver(); - - if (sender == null || sender.isBlank()) { - InfoMessage.sendSystemInfo( - connection, - InfoType.CONNECTION_REJECTED, - "Invalid Sender: " + sender + "." - ); - return false; - } - if (receiver == null || receiver.isBlank()) { - InfoMessage.sendSystemInfo( - connection, - InfoType.CONNECTION_REJECTED, - "Invalid Receiver: " + receiver + "." - ); - return false; - } - - return true; - } -} diff --git a/pitiupi/src/pitiupi/net/tcp/HandshakeMessage.java b/pitiupi/src/pitiupi/net/tcp/HandshakeMessage.java deleted file mode 100644 index 625deb4..0000000 --- a/pitiupi/src/pitiupi/net/tcp/HandshakeMessage.java +++ /dev/null @@ -1,30 +0,0 @@ -package pitiupi.net.tcp; - -import pitiupi.models.Messages.Message; - -class HandshakeMessage extends Message { - private final boolean isPrivate; - private final String sender; - private final String receiver; - - - public HandshakeMessage(boolean isPrivate, String sender, String receiver) { - this.isPrivate = isPrivate; - this.sender = sender; - this.receiver = receiver; - } - - public boolean isPrivate() { - return isPrivate; - } - public String getSender() { - return sender; - } - public String getReceiver() { - return receiver; - } - - public HandshakeMessage createResponse() { - return new HandshakeMessage(isPrivate, receiver, sender); - } -} diff --git a/pitiupi/src/pitiupi/net/tcp/SocketTCP.java b/pitiupi/src/pitiupi/net/tcp/SocketTCP.java deleted file mode 100644 index 688f638..0000000 --- a/pitiupi/src/pitiupi/net/tcp/SocketTCP.java +++ /dev/null @@ -1,456 +0,0 @@ -package pitiupi.net.tcp; - -import pitiupi.control.ConnectionManager; -import pitiupi.control.plugins.PluginManager; -import pitiupi.models.Messages.Message; -import pitiupi.models.Messages.InfoMessage; -import pitiupi.models.Messages.InfoType; - -import java.io.*; -import java.net.*; -import java.util.Objects; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Gerencia a comunicação TCP da aplicação. - * - *Mantém conexões persistentes com os peers e reutiliza essas conexões - * para o envio de mensagens. As mensagens recebidas são encaminhadas aos - * plugins, que são responsáveis por interpretá-las.
- * - *Novas conexões são aceitas pelo servidor e cada conexão possui uma - * tarefa própria para receber mensagens, permitindo que múltiplas conexões - * sejam mantidas simultaneamente.
- * - *As conexões são associadas aos endereços dos peers e podem ser - * encerradas quando um peer deixa de estar ativo.
- * - * @author Gustavo - */ -public class SocketTCP extends Thread { - private final Logger logger; - - private final ExecutorService connectionExecutor = Executors.newCachedThreadPool(); - - private final ConnectionManager connectionManager; - - private final PluginManager pluginManager; - - private final TcpConnectionManager tcpConnectionManager; - - private ServerSocket serverSocket; - private InetAddress address; - - private volatile boolean running = true; - - /** - * Cria o servidor TCP da aplicação. - * - *O servidor é criado na porta definida pela aplicação. As conexões - * com os peers são estabelecidas sob demanda durante o envio ou aceitas - * quando iniciadas por outros peers.
- * - * @param connectionManager interface de conexão da aplicação, usada para obter a porta, - * o identificador local e os plugins disponíveis. - */ - public SocketTCP(ConnectionManager connectionManager, PluginManager pluginManager) { - this.connectionManager = connectionManager; - this.pluginManager = pluginManager; - - logger = Logger.getLogger(getClass().getName()); - tcpConnectionManager = new TcpConnectionManager(); - - try { - serverSocket = new ServerSocket(connectionManager.getPort()); - address = InetAddress.getByName(InetAddress.getLocalHost().getHostAddress()); - } catch (IOException ex) { - System.out.println("There is no socket connection. Sorry."); - System.out.println(ex.toString()); - } - } - - /** - * Envia uma mensagem por meio de uma conexão TCP pública com o peer de destino. - * - *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.
- * - * @param msg mensagem que será enviada. - * @param destinationAddress endereço IP do peer que receberá a mensagem. - */ - public boolean send(Message msg, InetAddress destinationAddress) { - try { - TcpConnection connection = getPublicConnection( - destinationAddress, - connectionManager.getIdentifier(), - connectionManager.getIdentifier() - ); - - if (connection == null) { - System.out.println("Could not establish TCP connection."); - return false; - } - connection.send(msg.toByteArray()); - } catch (Exception ex) { - System.out.println("Could not send TCP message."); - System.out.println(ex.toString()); - return false; - } - return true; - } - - /** - * Estabelece uma conexão TCP privada com um plugin específico do peer de destino. - * - *A conexão é criada como privada e passa a ser gerenciada pelo - * {@link TcpConnectionManager}. O método também inicia a tarefa responsável - * por receber os dados dessa conexão.
- * - * @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) { - 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. - * - *As conexões públicas são gerenciadas pelo {@link TcpConnectionManager}. - * O método primeiro verifica se já 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 TcpConnectionManager} e disponibilizada para uso.
- * - *O acesso ao método é sincronizado para evitar que múltiplas threads - * estabeleçam simultaneamente conexões públicas para o mesmo peer.
- * - * @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) { - TcpConnection connection; - - connection = tcpConnectionManager.getPublicConnection(destinationAddress); - if (connection != null && !connection.isClosed()) return connection; - - connection = establishOutgoingConnection(destinationAddress, senderId, receiverId, false); - - return connection; - } - - /** - * Aguarda e aceita novas conexões TCP. - * - *Cada socket aceito é encaminhado para processamento assíncrono, - * interpretado como uma {@link TcpConnection}, registrado pelo - * {@link TcpConnectionManager} e associado ao receptor adequado.
- */ - private void receive() { - while (running) { - - try { - Socket socket = serverSocket.accept(); - acceptIncomingConnection(socket); - - } catch (IOException ex) { - if (!running) { - break; - } - - System.out.println("Error accepting TCP connection."); - System.out.println(ex.toString()); - } - } - } - - /** - * Estabelece uma conexão TCP de saída com outro peer. - * - *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.
- * - * @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) { - if (address.equals(destinationAddress)) { - System.out.println("Failed to get socket."); - throw new IllegalArgumentException("Connections to the local host are forbidden."); - } - - TcpConnection connection = ConnectionFactory.createConnection(destinationAddress, connectionManager.getPort(), sender, receiver, isPrivate); - if (connection == null) return null; - - saveConnection(connection); - - connectionExecutor.submit(() -> { - startReceiver(connection); - }); - - return connection; - } - - /** - * Agenda o processamento de uma conexão TCP recebida. - * - *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.
- * - * @param socket socket aceito pelo {@link ServerSocket}. - */ - private void acceptIncomingConnection(Socket socket) { - connectionExecutor.submit(() -> { - TcpConnection connection = ConnectionFactory.receiveConnection(socket); - if (connection == null) return; - - saveConnection(connection); - startReceiver(connection); - }); - } - - /** - * Registra uma conexão no gerenciador de acordo com seu tipo. - * - *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.
- * - * @param connection conexão que será registrada. - */ - private void saveConnection(TcpConnection connection) { - if (connection.isPrivate()) { - tcpConnectionManager.addPrivateConnection(connection); - } - else { - tcpConnectionManager.addPublicConnection(connection); - } - } - - /** - * Seleciona e inicia o receptor adequado para uma conexão. - * - *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.
- * - * @param connection conexão cujo fluxo de entrada será processado. - */ - private void startReceiver(TcpConnection connection) { - - if (!connection.isPrivate()) { - publicConnectionReceiver(connection); - return; - } - - if (connection.getReceiver().equals(connectionManager.getIdentifier())) { - systemConnectionReceiver(connection); - return; - } - - if (pluginManager.handleConnection(connection, connection.getReceiver())) - privateConnectionReceiver(connection); - else { - InfoMessage.sendSystemInfo( - connection, - InfoType.RECEIVER_NOT_FOUND, - "This peer does not have the plugin: " + connection.getReceiver() - ); - } - - } - - /** - * Processa continuamente as mensagens recebidas em uma conexão pública. - * - *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.
- * - *O método também trata timeouts e erros de I/O, removendo a conexão - * do {@link TcpConnectionManager} quando ela deixa de ser utilizável.
- * - * @param connection conexão pública da qual as mensagens serão recebidas. - */ - private void publicConnectionReceiver(TcpConnection connection) { - while (running) { - try { - byte[] bytes = connection.receive(); - - InfoMessage sysMessage = InfoMessage.parseSystemInfo(bytes); - - if (sysMessage == null) { - pluginManager.handleMessage(bytes); - continue; - } - - switch (sysMessage.getType()) { - case CONNECTION_CLOSED, INTERNAL_ERROR -> { - tcpConnectionManager.closeConnection(connection); - return; - } - case MESSAGE_REJECTED -> { - logger.log(Level.SEVERE, "Somehow sent something that was not an Message"); - } - default -> {} - } - } - catch (SocketTimeoutException ex) { - if (connection.hasTimedOut()) { - tcpConnectionManager.closeConnection(connection, "Connection timeout"); - return; - } - } - catch (IOException ex) { - tcpConnectionManager.closeConnection(connection); - } - } - } - - /** - * Processa os dados recebidos em uma conexão privada e os encaminha - * ao plugin destinatário. - * - *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.
- * - * @param connection conexão privada da qual os dados serão recebidos. - */ - private void privateConnectionReceiver(TcpConnection connection) { - while (running) { - try { - byte[] message = connection.receive(); - InfoMessage sysMessage = InfoMessage.parseSystemInfo(message); - - if (sysMessage == null) { - pluginManager.handleMessageFromConnection(connection, message); - continue; - } - - switch (sysMessage.getType()) { - case CONNECTION_CLOSED, INTERNAL_ERROR -> { - pluginManager.handleDisconnection(connection, connection.getReceiver()); - tcpConnectionManager.closeConnection(connection); - } - default -> { - continue; - } - } - } - catch (SocketTimeoutException ex) { - if (connection.hasTimedOut()) { - tcpConnectionManager.closeConnection(connection, "Connection timeout"); - return; - } - } - catch (IOException ex) { - tcpConnectionManager.closeConnection(connection); - } - } - } - - /** - * Processa mensagens recebidas em uma conexão destinada ao próprio sistema. - * - *Atualmente, mensagens destinadas ao sistema são apenas verificadas - * quanto ao seu formato de controle. Não há processamento adicional de - * mensagens de sistema neste método.
- * - * @param connection conexão destinada ao identificador da própria aplicação. - */ - private void systemConnectionReceiver(TcpConnection connection) { - while (running) { - try { - byte[] message = connection.receive(); - try (ByteArrayInputStream bis = new ByteArrayInputStream(message); - ObjectInputStream ois = new ObjectInputStream(bis)) { - - InfoMessage sysMessage = InfoMessage.parseSystemInfo(message); - if (sysMessage != null) continue; - - //TODO: Ainda não existem mensagens trocadas entre o sistema. - } - - } - catch (SocketTimeoutException ex) { - if (connection.hasTimedOut()) return; - } - catch (IOException ex) { - tcpConnectionManager.closeConnection(connection); - } - } - } - - /** - * Encerra uma conexão TCP e registra o motivo informado. - * - *Quando nenhuma descrição é fornecida, é utilizado um motivo padrão. - * O fechamento efetivo da conexão é delegado ao {@link TcpConnectionManager}.
- * - * @param connection conexão que será encerrada. - * @param description motivo do encerramento; pode ser {@code null}. - */ - public void disconnect(TcpConnection connection, String description) { - tcpConnectionManager.closeConnection( - connection, - Objects.requireNonNullElse(description, "No reason provided") - ); - } - - @Override - public void run() { - receive(); - } - - /** - * Encerra o servidor TCP e libera os recursos associados. - * - *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.
- * - *Após a chamada, esta instância não deve ser reutilizada para aceitar - * novas conexões.
- */ - public void close() { - running = false; - - if (serverSocket != null) { - try { - serverSocket.close(); - } catch (IOException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - - tcpConnectionManager.closeAllConnections("The peer must have closed the application or changed ports"); - - connectionExecutor.shutdownNow(); - } -} \ No newline at end of file diff --git a/pitiupi/src/pitiupi/net/tcp/TcpClient.java b/pitiupi/src/pitiupi/net/tcp/TcpClient.java new file mode 100644 index 0000000..5fb070e --- /dev/null +++ b/pitiupi/src/pitiupi/net/tcp/TcpClient.java @@ -0,0 +1,34 @@ +package pitiupi.net.tcp; + +import pitiupi.models.ConnectionData; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.net.Socket; + +public final class TcpClient extends TcpPitiupi { + private final ConnectionData clientData; + private final ConnectionData serverData; + + TcpClient( + ConnectionData clientData, + ConnectionData serverData, + Socket socket, + DataInputStream input, + DataOutputStream output) + { + + super(socket, input, output); + + this.clientData = clientData; + this.serverData = serverData; + } + + public ConnectionData getServerData() { + return serverData; + } + + public ConnectionData getClientData() { + return clientData; + } +} diff --git a/pitiupi/src/pitiupi/net/tcp/TcpConnection.java b/pitiupi/src/pitiupi/net/tcp/TcpConnection.java deleted file mode 100644 index be7c0e4..0000000 --- a/pitiupi/src/pitiupi/net/tcp/TcpConnection.java +++ /dev/null @@ -1,171 +0,0 @@ -package pitiupi.net.tcp; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.net.SocketException; - -/** - * Representa uma conexão TCP ativa com um peer remoto. - * - *A conexão permite o envio e recebimento de mensagens, além de manter - * informações sobre o endereço remoto e metadados da conexão, como remetente, - * destinatário e se a conexão é privada.
- * - * @author Gustavo - */ -public class TcpConnection { - - private final Socket socket; - private final DataInputStream input; - private final DataOutputStream output; - private final InetAddress address; - private String sender; - private String receiver; - private boolean isPrivate; - 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 { - this.socket = socket; - this.input = new DataInputStream(socket.getInputStream()); - this.output = new DataOutputStream(socket.getOutputStream()); - this.address = socket.getInetAddress(); - this.lastActivity = System.currentTimeMillis(); - this.timeout = 300_000; // 5 minutos - } - - /** - * Envia uma mensagem pela conexão. - * - *A mensagem é enviada precedida pelo seu tamanho, permitindo que o - * receptor determine quantos bytes devem ser lidos.
- * - * @param message mensagem a ser enviada - * @throws IOException se ocorrer um erro durante o envio - */ - public synchronized void send(byte[] message) throws IOException { - output.writeInt(message.length); - output.write(message); - output.flush(); - updateLastActivity(); - } - - /** - * Recebe uma mensagem da conexão. - * - *Primeiro lê o tamanho da mensagem e, em seguida, lê aquantidade - * correspondente de bytes.
- * - * @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 { - int length = input.readInt(); - - if (length < 0) throw new IOException("Invalid message length."); - - byte[] message = input.readNBytes(length); - - if (message.length != length) { - throw new IOException("Connection closed before receiving the complete message."); - } - - updateLastActivity(); - - 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() { - return socket.isClosed(); - } - - /** - * Fecha o socket da conexão. - * - * @throws IOException se ocorrer um erro ao fechar o socket - */ - void close() throws IOException { - socket.close(); - } - - /** - * Retorna o endereço do peer remoto conectado. - * - * @return endereço IP do peer remoto - */ - public InetAddress getAddress() { - return address; - } - - /** - * Atualiza o instante da última atividade da conexão. - */ - private void updateLastActivity() { - 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 { - socket.setSoTimeout(timeout); - this.timeout = timeout; - } - - /** - * Verifica se a conexão ficou inativa pelo tempo limite configurado. - * - *A verificação é baseada no instante da última atividade registrada - * durante o envio ou recebimento de uma mensagem.
- * - * @return {@code true} se a conexão estiver inativa há pelo menos 5 minutos; - * {@code false} caso contrário - */ - boolean hasTimedOut() { - return System.currentTimeMillis() - lastActivity >= timeout; - } - - public String getReceiver() { - return receiver; - } - - public String getSender() { - return sender; - } - - public boolean isPrivate() { - return isPrivate; - } - - void setSender(String sender) { - this.sender = sender; - } - - void setReceiver(String receiver) { - this.receiver = receiver; - } - - void setPrivate(boolean aPrivate) { - isPrivate = aPrivate; - } -} diff --git a/pitiupi/src/pitiupi/net/tcp/TcpConnectionManager.java b/pitiupi/src/pitiupi/net/tcp/TcpConnectionManager.java deleted file mode 100644 index 81b29ad..0000000 --- a/pitiupi/src/pitiupi/net/tcp/TcpConnectionManager.java +++ /dev/null @@ -1,264 +0,0 @@ -package pitiupi.net.tcp; - -import pitiupi.models.Messages.InfoType; -import pitiupi.models.User; -import pitiupi.control.PeerListener; -import pitiupi.models.Messages.InfoMessage; - -import java.io.IOException; -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -/** - * Gerencia as conexões TCP estabelecidas com os peers da aplicação. - * - *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.
- * - *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.
- */ -class TcpConnectionManager implements PeerListener { - private final Logger logger; - private final MapUm 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.
- * - * @param connection conexão privada a ser registrada. - */ - public void addPrivateConnection(TcpConnection connection) { - ListCada peer pode possuir apenas uma conexão pública registrada. - * Quando já existe uma conexão pública para o endereço da nova conexão, - * a conexão anterior é encerrada e substituída pela nova.
- * - * @param connection nova conexão pública a ser registrada. - */ - - public void addPublicConnection(TcpConnection connection) { - TcpConnection oldConnection = publicConnections.put(connection.getAddress(), connection); - if (oldConnection != null && oldConnection != connection) { - closeConnection(oldConnection); - } - } - - /** - * Envia uma mensagem de encerramento ao peer e fecha a conexão. - * - *A mensagem enviada informa o motivo do encerramento antes que a - * conexão seja removida do gerenciamento e fechada.
- * - * @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) { - InfoMessage.sendSystemInfo(connection, InfoType.CONNECTION_CLOSED, reason); - closeConnection(connection); - } - - /** - * Remove uma conexão do gerenciamento e encerra seu socket. - * - *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.
- * - *Para conexões públicas, a conexão é removida do mapa somente se - * ainda for a conexão registrada para seu endereço.
- * - *Se a conexão já estiver fechada ou for {@code null}, nenhuma ação - * adicional de fechamento é realizada.
- * - * @param connection conexão TCP a ser removida e encerrada. - */ - - public void closeConnection(TcpConnection connection) { - if (connection == null) return; - if (connection.isPrivate()) { - ListTanto a conexão pública quanto todas as conexões privadas associadas - * ao endereço são encerradas e removidas do gerenciamento.
- * - * @param address endereço IP do peer cujas conexões serão encerradas. - */ - public void closeByAdress(InetAddress address) { - TcpConnection publicConnection = publicConnections.remove(address); - - if (publicConnection != null) { - try { - publicConnection.close(); - } - catch (IOException ex) { - logger.log(Level.SEVERE, "Error closing TCP connection", ex); - } - - } - - ListApós a execução, não devem permanecer conexões registradas nos - * mapas de conexões deste gerenciador.
- */ - - public void closeAllConnections() { - for (TcpConnection connection : publicConnections.values()) { - closeConnection(connection); - } - - synchronized (privateConnections) { - for (ListOs endereços presentes na lista de peers são utilizados como - * 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.
- * - * @param peers lista atualizada de peers ativos. - */ - - @Override - public void onPeersChanged(ListA mensagem é enviada precedida pelo seu tamanho, permitindo que o + * receptor determine quantos bytes devem ser lidos.
+ * + * @param message mensagem a ser enviada + * @throws IOException se ocorrer um erro durante o envio + */ + public void send(byte[] message) throws IOException { + output.writeInt(message.length); + output.write(message); + output.flush(); + } + + /** + * Recebe uma mensagem da conexão. + * + *Primeiro lê o tamanho da mensagem e, em seguida, lê aquantidade + * correspondente de bytes.
+ * + * @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 + */ + public byte[] receive() throws IOException { + int length = input.readInt(); + + if (length < 0) throw new IOException("Invalid message length."); + + byte[] message = input.readNBytes(length); + + if (message.length != length) { + throw new IOException("Connection closed before receiving the complete message."); + } + + return message; + } + + public boolean isClosed() { + return socket.isClosed(); + } + + Socket getSocket() { + return socket; + } + + public void close() throws IOException { + socket.close(); + } +} diff --git a/pitiupi/src/pitiupi/net/tcp/TcpServer.java b/pitiupi/src/pitiupi/net/tcp/TcpServer.java new file mode 100644 index 0000000..5b21623 --- /dev/null +++ b/pitiupi/src/pitiupi/net/tcp/TcpServer.java @@ -0,0 +1,61 @@ +package pitiupi.net.tcp; + +import pitiupi.models.ConnectionData; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +public final class TcpServer { + private final ConnectionData connectionData; + + private final BlockingQueue