Compare commits
7 Commits
3efc59444f
...
93ac292f72
| Author | SHA1 | Date |
|---|---|---|
|
|
93ac292f72 | |
|
|
b610ec20e1 | |
|
|
4461772ebf | |
|
|
0d5a4dbe75 | |
|
|
bbb1ceea23 | |
|
|
7d9ca4ca71 | |
|
|
1fcc03d9be |
|
|
@ -5,8 +5,8 @@
|
|||
*/
|
||||
package pitiupi.GUI;
|
||||
|
||||
import pitiupi.net.SocketTCP;
|
||||
import pitiupi.net.SocketUDP;
|
||||
import pitiupi.net.tcp.SocketTCP;
|
||||
import pitiupi.net.udp.SocketUDP;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.swing.JMenu;
|
||||
|
|
@ -44,7 +44,9 @@ public class MainWindow extends javax.swing.JFrame {
|
|||
private SocketTCP socketTCP;
|
||||
|
||||
private String userName;
|
||||
private int port;
|
||||
private int port;
|
||||
|
||||
private String pitiupiIdentifier;
|
||||
|
||||
private PluginLoader pluginManager;
|
||||
private final List<Plugin> plugins;
|
||||
|
|
@ -58,6 +60,7 @@ public class MainWindow extends javax.swing.JFrame {
|
|||
public MainWindow(String userName, int port) {
|
||||
this.userName = userName;
|
||||
this.port = port;
|
||||
this.pitiupiIdentifier = "Pitiupi.Alice.2026";
|
||||
this.menuBar = new MenuBar(this);
|
||||
this.plugins = new ArrayList<>();
|
||||
this.initComponents();
|
||||
|
|
@ -127,6 +130,10 @@ public class MainWindow extends javax.swing.JFrame {
|
|||
return this.userName;
|
||||
}
|
||||
|
||||
public String getIdentifier(){
|
||||
return this.pitiupiIdentifier;
|
||||
}
|
||||
|
||||
public void addMenu(JMenu menu) {
|
||||
this.menuBar.addPlugin(menu);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
package pitiupi.net;
|
||||
|
||||
/**
|
||||
* Mensagem utilizada para sinalizar a presença de um usuário na rede.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,244 +0,0 @@
|
|||
package pitiupi.net;
|
||||
|
||||
import pitiupi.GUI.MainWindow;
|
||||
import pitiupi.control.PeerInfo;
|
||||
import pitiupi.control.PeerListener;
|
||||
import pitiupi.plugin.Plugin;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Gerencia a comunicação TCP da aplicação.
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>As conexões são associadas aos endereços dos peers e podem ser
|
||||
* encerradas quando um peer deixa de estar ativo.</p>
|
||||
*
|
||||
* @author Gustavo
|
||||
*/
|
||||
public class SocketTCP extends Thread implements PeerListener {
|
||||
private final ExecutorService connectionExecutor = Executors.newCachedThreadPool();
|
||||
private final Map<InetAddress, TcpConnection> connections;
|
||||
private ServerSocket serverSocket;
|
||||
private InetAddress address;
|
||||
|
||||
private final MainWindow main;
|
||||
|
||||
private volatile boolean running = true;
|
||||
|
||||
/**
|
||||
* Cria o servidor TCP da aplicação.
|
||||
*
|
||||
* <p>O servidor utiliza a porta definida pela aplicação e inicia sem
|
||||
* estabelecer conexões com os peers. As conexões são criadas conforme
|
||||
* necessário durante o envio ou recebidas de outros peers.</p>
|
||||
*
|
||||
* @param main janela principal da aplicação.
|
||||
*/
|
||||
public SocketTCP(MainWindow main) {
|
||||
this.main = main;
|
||||
connections = new ConcurrentHashMap<>();
|
||||
try {
|
||||
serverSocket = new ServerSocket(main.getPort());
|
||||
address = InetAddress.getByName(InetAddress.getLocalHost().getHostAddress());
|
||||
} catch (IOException ex) {
|
||||
System.out.println("There is no socket connection. Sorry.");
|
||||
System.out.println(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envia uma mensagem TCP para um peer.
|
||||
*
|
||||
* <p>Uma conexão existente com o peer é reutilizada. Caso não exista uma
|
||||
* conexão válida, uma nova conexão é criada e adicionada ao conjunto de
|
||||
* conexões ativas.</p>
|
||||
*
|
||||
* @param msg mensagem serializada a ser enviada.
|
||||
* @param destinationAddress endereço IP do peer destinatário.
|
||||
*/
|
||||
public void send(byte[] msg, InetAddress destinationAddress) {
|
||||
try {
|
||||
if (destinationAddress.equals(address)) {
|
||||
throw new IllegalArgumentException("Destination address cannot be the same as the original address.");
|
||||
}
|
||||
TcpConnection connec tion;
|
||||
|
||||
synchronized (connections) {
|
||||
connection = connections.get(destinationAddress);
|
||||
|
||||
if (connection == null || connection.isClosed()) {
|
||||
Socket socket = new Socket(destinationAddress, main.getPort());
|
||||
|
||||
connection = new TcpConnection(socket);
|
||||
|
||||
connections.put(destinationAddress, connection);
|
||||
|
||||
startReceiver(destinationAddress, connection);
|
||||
}
|
||||
}
|
||||
|
||||
connection.send(msg);
|
||||
|
||||
} catch (IOException ex) {
|
||||
System.out.println("Could not send TCP message.");
|
||||
System.out.println(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aguarda e aceita novas conexões TCP.
|
||||
*
|
||||
* <p>Cada conexão aceita é associada ao endereço do peer e registrada para
|
||||
* que possa ser utilizada tanto para recepção quanto para envio de
|
||||
* mensagens.</p>
|
||||
*/
|
||||
private void receive() {
|
||||
while (running) {
|
||||
|
||||
try {
|
||||
Socket socket = serverSocket.accept();
|
||||
|
||||
InetAddress address = socket.getInetAddress();
|
||||
|
||||
TcpConnection connection = new TcpConnection(socket);
|
||||
|
||||
TcpConnection oldConnection = connections.put(address, connection);
|
||||
|
||||
if (oldConnection != null) {
|
||||
oldConnection.close();
|
||||
}
|
||||
|
||||
startReceiver(address, connection);
|
||||
|
||||
} catch (IOException ex) {
|
||||
|
||||
if (!running) {
|
||||
break;
|
||||
}
|
||||
|
||||
System.out.println("Error accepting TCP connection.");
|
||||
System.out.println(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicia a recepção de mensagens de uma conexão TCP.
|
||||
*
|
||||
* <p>A recepção é executada de forma independente para que uma conexão
|
||||
* não impeça a aceitação ou o processamento de outras conexões.</p>
|
||||
*
|
||||
* <p>Cada mensagem recebida é encaminhada aos plugins da aplicação.</p>
|
||||
*
|
||||
* @param address endereço do peer associado à conexão.
|
||||
* @param connection conexão TCP utilizada para a comunicação.
|
||||
*/
|
||||
private void startReceiver(InetAddress address, TcpConnection connection) {
|
||||
connectionExecutor.submit(() -> {
|
||||
try {
|
||||
while (running) {
|
||||
byte[] message = connection.receive();
|
||||
|
||||
for (Plugin plugin : main.getPlugins()) {
|
||||
plugin.receiveMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException ex) {
|
||||
System.out.println(
|
||||
"Connection with " + address.getHostAddress() + " closed."
|
||||
);
|
||||
|
||||
} finally {
|
||||
connections.remove(address, connection);
|
||||
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicia o processo de recepção de conexões TCP.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
receive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encerra o servidor TCP e as conexões associadas.
|
||||
*
|
||||
* <p>As conexões existentes são encerradas e o servidor deixa
|
||||
* de aceitar novas conexões.</p>
|
||||
*/
|
||||
public void close() {
|
||||
running = false;
|
||||
|
||||
if (serverSocket != null) {
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (IOException _) {
|
||||
}
|
||||
}
|
||||
|
||||
for (TcpConnection connection : connections.values()) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException _) {
|
||||
}
|
||||
}
|
||||
|
||||
connections.clear();
|
||||
|
||||
connectionExecutor.shutdownNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* e removidas.</p>
|
||||
*
|
||||
* @param peers lista atualizada de peers ativos.
|
||||
*/
|
||||
@Override
|
||||
public void onPeersChanged(List<PeerInfo> peers) {
|
||||
Set<InetAddress> activePeers = peers.stream()
|
||||
.map(PeerInfo::getAddress)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (InetAddress address : connections.keySet()) {
|
||||
if (!activePeers.contains(address)) {
|
||||
|
||||
TcpConnection connection = connections.remove(address);
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package pitiupi.net;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
|
||||
/**
|
||||
* Representa uma conexão TCP persistente com um peer.
|
||||
*
|
||||
* <p>Encapsula o socket e os streams utilizados para enviar e receber
|
||||
* mensagens. Também é responsável pelo framing das mensagens, permitindo
|
||||
* que várias mensagens sejam transmitidas pela mesma conexão.</p>
|
||||
*
|
||||
* @author Gustavo
|
||||
*/
|
||||
class TcpConnection {
|
||||
|
||||
private final Socket socket;
|
||||
private final DataInputStream input;
|
||||
private final DataOutputStream output;
|
||||
|
||||
/**
|
||||
* Cria uma conexão a partir de um socket existente.
|
||||
*
|
||||
* @param socket socket TCP utilizado pela conexão.
|
||||
* @throws IOException caso não seja possível obter os streams do socket.
|
||||
*/
|
||||
public TcpConnection(Socket socket) throws IOException {
|
||||
this.socket = socket;
|
||||
this.input = new DataInputStream(socket.getInputStream());
|
||||
this.output = new DataOutputStream(socket.getOutputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Envia uma mensagem pela conexão.
|
||||
*
|
||||
* <p>A mensagem é precedida por seu tamanho para permitir que o receptor
|
||||
* identifique o limite entre mensagens transmitidas pela mesma conexão.</p>
|
||||
*
|
||||
* @param message mensagem serializada a ser enviada.
|
||||
* @throws IOException caso ocorra um erro durante o envio.
|
||||
*/
|
||||
public void send(byte[] message) throws IOException {
|
||||
synchronized (output) {
|
||||
output.writeInt(message.length);
|
||||
output.write(message);
|
||||
output.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recebe uma mensagem completa da conexão.
|
||||
*
|
||||
* <p>O tamanho da mensagem é lido primeiro e, em seguida, os dados
|
||||
* correspondentes são recebidos.</p>
|
||||
*
|
||||
* @return mensagem recebida.
|
||||
* @throws IOException caso ocorra um erro durante a recepção.
|
||||
*/
|
||||
public byte[] receive() throws IOException {
|
||||
int length = input.readInt();
|
||||
|
||||
if (length < 0) {
|
||||
throw new IOException("Invalid message length.");
|
||||
}
|
||||
|
||||
return input.readNBytes(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se o socket da conexão está fechado.
|
||||
*
|
||||
* @return {@code true} caso o socket esteja fechado.
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return socket.isClosed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encerra a conexão TCP.
|
||||
*
|
||||
* @throws IOException caso ocorra um erro ao fechar o socket.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.net.Message;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class ConnectionFactory {
|
||||
private static final int HANDSHAKE_TIMEOUT = 5000;
|
||||
private static final int STANDARD_TIMEOUT = 300_000;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static TcpConnection receiveConnection(Socket socket) {
|
||||
try {
|
||||
TcpConnection connection;
|
||||
|
||||
connection = new TcpConnection(socket);
|
||||
|
||||
HandshakeMessage handshake = validateConnection(connection);
|
||||
|
||||
if (handshake == null) return null;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (SysInfoMessage.parseSystemInfo(message) != null) {
|
||||
//TODO: Tratar erros
|
||||
return null;
|
||||
}
|
||||
|
||||
if (message instanceof HandshakeMessage handshake) {
|
||||
return (validateHandshake(handshake, connection)) ? handshake : null;
|
||||
}
|
||||
}
|
||||
|
||||
SysInfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
SysInfoType.CONNECTION_REJECTED,
|
||||
"The message was not from an allowed type."
|
||||
);
|
||||
}
|
||||
catch (SocketTimeoutException ex) {
|
||||
SysInfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
SysInfoType.CONNECTION_REJECTED,
|
||||
"No handshake was received within the allowed time."
|
||||
);
|
||||
}
|
||||
catch (ClassNotFoundException | IOException ex) {
|
||||
Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
|
||||
|
||||
SysInfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
SysInfoType.INTERNAL_ERROR,
|
||||
"The server could not process the handshake."
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean validateHandshake(HandshakeMessage handshake, TcpConnection connection) {
|
||||
String sender = handshake.getSender();
|
||||
String receiver = handshake.getReceiver();
|
||||
|
||||
if (sender == null || sender.isBlank()) {
|
||||
SysInfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
SysInfoType.CONNECTION_REJECTED,
|
||||
"Invalid Sender: " + sender + "."
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (receiver == null || receiver.isBlank()) {
|
||||
SysInfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
SysInfoType.CONNECTION_REJECTED,
|
||||
"Invalid Receiver: " + receiver + "."
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.control.PeerInfo;
|
||||
import pitiupi.control.PeerListener;
|
||||
|
||||
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;
|
||||
|
||||
public class ConnectionManager implements PeerListener {
|
||||
private final Logger logger;
|
||||
private final Map<InetAddress, TcpConnection> publicConnections;
|
||||
private final Map<InetAddress, List<TcpConnection>> privateConnections;
|
||||
|
||||
public ConnectionManager() {
|
||||
publicConnections = new ConcurrentHashMap<>();
|
||||
privateConnections = new ConcurrentHashMap<>();
|
||||
|
||||
logger = Logger.getLogger(getClass().getName());
|
||||
}
|
||||
|
||||
public TcpConnection getPublicConnection(InetAddress address) {
|
||||
return publicConnections.get(address);
|
||||
}
|
||||
|
||||
public void addPrivateConnection(InetAddress address, TcpConnection connection) {
|
||||
List<TcpConnection> connections =
|
||||
privateConnections.computeIfAbsent(connection.getAddress(), _ -> new ArrayList<>());
|
||||
|
||||
synchronized (connections) {
|
||||
connections.add(connection);
|
||||
}
|
||||
}
|
||||
public void addPublicConnection(InetAddress address, TcpConnection connection) {
|
||||
TcpConnection oldConnection = publicConnections.put(connection.getAddress(), connection);
|
||||
if (oldConnection != null && oldConnection != connection) {
|
||||
closeConnection(oldConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public void closeConnection(TcpConnection connection, String reason) {
|
||||
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
|
||||
closeConnection(connection);
|
||||
}
|
||||
public void closeConnection(TcpConnection connection) {
|
||||
if (connection == null) return;
|
||||
if (connection.isPrivate()) {
|
||||
List<TcpConnection> connections = privateConnections.get(connection.getAddress());
|
||||
|
||||
if (connections != null) {
|
||||
synchronized (connections) {
|
||||
connections.remove(connection);
|
||||
|
||||
if (connections.isEmpty()) privateConnections.remove(connection.getAddress(), connections);
|
||||
}
|
||||
}
|
||||
}
|
||||
else publicConnections.remove(connection.getAddress(), connection);
|
||||
|
||||
try {
|
||||
if (!connection.isClosed()) {
|
||||
connection.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, "Error closing TCP connection", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void closeByAdress(InetAddress address, String reason) {
|
||||
List<TcpConnection> connections = privateConnections.get(address);
|
||||
for (TcpConnection connection : connections) {
|
||||
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
|
||||
}
|
||||
closeByAdress(address);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
List<TcpConnection> connections = privateConnections.remove(address);
|
||||
|
||||
if (connections != null) {
|
||||
synchronized (connections) {
|
||||
for (TcpConnection connection : connections) {
|
||||
try {
|
||||
connection.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, "Error closing TCP connection", ex);
|
||||
}
|
||||
}
|
||||
connections.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void closeAllConnections(String reason) {
|
||||
for (List<TcpConnection> connections : privateConnections.values()) {
|
||||
for (TcpConnection connection : connections) {
|
||||
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
|
||||
}
|
||||
}
|
||||
closeAllConnections();
|
||||
}
|
||||
public void closeAllConnections() {
|
||||
for (TcpConnection connection : publicConnections.values()) {
|
||||
closeConnection(connection);
|
||||
}
|
||||
|
||||
synchronized (privateConnections) {
|
||||
for (List<TcpConnection> connectionsList : privateConnections.values()) {
|
||||
for (TcpConnection connection : connectionsList) {
|
||||
closeConnection(connection);
|
||||
}
|
||||
privateConnections.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* e removidas.</p>
|
||||
*
|
||||
* @param peers lista atualizada de peers ativos.
|
||||
*/
|
||||
@Override
|
||||
public void onPeersChanged(List<PeerInfo> peers) {
|
||||
Set<InetAddress> activePeers = peers.stream().map(PeerInfo::getAddress).collect(Collectors.toSet());
|
||||
|
||||
for (InetAddress address : publicConnections.keySet()) {
|
||||
if (!activePeers.contains(address)) {
|
||||
closeByAdress(address);
|
||||
}
|
||||
}
|
||||
for (InetAddress address : privateConnections.keySet()) {
|
||||
if (!activePeers.contains(address)) {
|
||||
closeByAdress(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.net.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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,344 @@
|
|||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.GUI.MainWindow;
|
||||
import pitiupi.net.Message;
|
||||
import pitiupi.plugin.Plugin;
|
||||
import pitiupi.plugin.PrivateConnectionPlugin;
|
||||
|
||||
import javax.swing.*;
|
||||
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.
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>As conexões são associadas aos endereços dos peers e podem ser
|
||||
* encerradas quando um peer deixa de estar ativo.</p>
|
||||
*
|
||||
* @author Gustavo
|
||||
*/
|
||||
public class SocketTCP extends Thread {
|
||||
private final Logger logger;
|
||||
|
||||
private final ExecutorService connectionExecutor = Executors.newCachedThreadPool();
|
||||
|
||||
private final ConnectionManager connectionManager;
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
private InetAddress address;
|
||||
|
||||
private final MainWindow main;
|
||||
|
||||
private volatile boolean running = true;
|
||||
|
||||
/**
|
||||
* Cria o servidor TCP da aplicação.
|
||||
*
|
||||
* <p>O servidor utiliza a porta definida pela aplicação e inicia sem
|
||||
* estabelecer conexões com os peers. As conexões são criadas conforme
|
||||
* necessário durante o envio ou recebidas de outros peers.</p>
|
||||
*
|
||||
* @param main janela principal da aplicação.
|
||||
*/
|
||||
public SocketTCP(MainWindow main) {
|
||||
this.main = main;
|
||||
logger = Logger.getLogger(getClass().getName());
|
||||
connectionManager = new ConnectionManager();
|
||||
try {
|
||||
serverSocket = new ServerSocket(main.getPort());
|
||||
address = InetAddress.getByName(InetAddress.getLocalHost().getHostAddress());
|
||||
} catch (IOException ex) {
|
||||
System.out.println("There is no socket connection. Sorry.");
|
||||
System.out.println(ex.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void send(byte[] msg, InetAddress destinationAddress) {
|
||||
try {
|
||||
TcpConnection connection = getPublicConnection(destinationAddress, main.getIdentifier(), main.getIdentifier());
|
||||
if (connection == null) {
|
||||
System.out.println("Could not establish TCP connection.");
|
||||
return;
|
||||
}
|
||||
connection.send(msg);
|
||||
} catch (Exception ex) {
|
||||
System.out.println("Could not send TCP message.");
|
||||
System.out.println(ex.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public TcpConnection createPrivateConnection(InetAddress destinationAddress, String senderId, String receiverId) {
|
||||
return establishOutgoingConnection(destinationAddress, senderId, receiverId, true);
|
||||
}
|
||||
|
||||
private synchronized TcpConnection getPublicConnection(InetAddress destinationAddress, String senderId, String receiverId) {
|
||||
TcpConnection connection;
|
||||
|
||||
connection = connectionManager.getPublicConnection(destinationAddress);
|
||||
if (connection != null && !connection.isClosed()) return connection;
|
||||
|
||||
connection = establishOutgoingConnection(destinationAddress, senderId, receiverId, false);
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aguarda e aceita novas conexões TCP.
|
||||
*
|
||||
* <p>Cada conexão aceita é associada ao endereço do peer e registrada para
|
||||
* que possa ser utilizada tanto para recepção quanto para envio de
|
||||
* mensagens.</p>
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, main.getPort(), sender, receiver, isPrivate);
|
||||
if (connection == null) return null;
|
||||
|
||||
saveConnection(connection);
|
||||
|
||||
connectionExecutor.submit(() -> {
|
||||
startReceiver(connection, false);
|
||||
});
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
private void acceptIncomingConnection(Socket socket) {
|
||||
connectionExecutor.submit(() -> {
|
||||
TcpConnection connection = ConnectionFactory.receiveConnection(socket);
|
||||
if (connection == null) return;
|
||||
|
||||
saveConnection(connection);
|
||||
startReceiver(connection, true);
|
||||
});
|
||||
}
|
||||
|
||||
private void saveConnection(TcpConnection connection) {
|
||||
if (connection.isPrivate()) {
|
||||
connectionManager.addPrivateConnection(connection.getAddress(), connection);
|
||||
}
|
||||
else {
|
||||
connectionManager.addPublicConnection(connection.getAddress(), connection);
|
||||
}
|
||||
}
|
||||
|
||||
private void startReceiver(TcpConnection connection, boolean notify) {
|
||||
try {
|
||||
if (!connection.isPrivate()) {
|
||||
publicConnectionReceiver(connection);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private Plugin findConnectionReceiver(TcpConnection connection) {
|
||||
Plugin receiver = null;
|
||||
for(Plugin plugin : main.getPlugins()) {
|
||||
if (plugin.getIdentifier().equals(connection.getReceiver())) {
|
||||
receiver = plugin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (receiver == null) {
|
||||
SysInfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
SysInfoType.RECEIVER_NOT_FOUND,
|
||||
"This peer does not have the plugin: " + connection.getReceiver()
|
||||
);
|
||||
}
|
||||
return receiver;
|
||||
}
|
||||
|
||||
private void publicConnectionReceiver(TcpConnection connection) {
|
||||
while (running) {
|
||||
try {
|
||||
byte[] bytes = connection.receive();
|
||||
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream ois = new ObjectInputStream(bis)) {
|
||||
|
||||
Object object = ois.readObject();
|
||||
if (object instanceof Message message) {
|
||||
SysInfoMessage sysMessage = SysInfoMessage.parseSystemInfo(message);
|
||||
|
||||
if (sysMessage == null) {
|
||||
for (Plugin plugin : main.getPlugins()) {
|
||||
plugin.receiveMessage(message);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (sysMessage.getType()) {
|
||||
case CONNECTION_CLOSED, INTERNAL_ERROR -> {
|
||||
connectionManager.closeConnection(connection);
|
||||
return;
|
||||
}
|
||||
case MESSAGE_REJECTED -> {
|
||||
logger.log(Level.SEVERE, "Somehow sent something that was not an Message");
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
else {
|
||||
SysInfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
SysInfoType.MESSAGE_REJECTED,
|
||||
"Public messages must be instances of Message"
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
SysInfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
SysInfoType.INTERNAL_ERROR,
|
||||
"class Message not found."
|
||||
);
|
||||
logger.log(Level.SEVERE, null, ex);
|
||||
connectionManager.closeConnection(connection);
|
||||
}
|
||||
}
|
||||
catch (SocketTimeoutException ex) {
|
||||
if (connection.hasTimedOut()) {
|
||||
connectionManager.closeConnection(connection, "Connection timeout");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
connectionManager.closeConnection(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void privateConnectionReceiver(TcpConnection connection, Plugin plugin) {
|
||||
if (plugin instanceof PrivateConnectionPlugin privatePlugin) {
|
||||
while (running) {
|
||||
try {
|
||||
byte[] message = connection.receive();
|
||||
SysInfoMessage sysMessage = SysInfoMessage.parseSystemInfo(message);
|
||||
|
||||
if (sysMessage == null) {
|
||||
privatePlugin.receiveBytes(message);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (sysMessage.getType()) {
|
||||
case CONNECTION_CLOSED, INTERNAL_ERROR -> {
|
||||
privatePlugin.onSocketClosed(connection);
|
||||
connectionManager.closeConnection(connection);
|
||||
}
|
||||
default -> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SocketTimeoutException ex) {
|
||||
if (connection.hasTimedOut()) {
|
||||
connectionManager.closeConnection(connection, "Connection timeout");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
connectionManager.closeConnection(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void systemConnectionReceiver(TcpConnection connection) throws IOException {
|
||||
while (running) {
|
||||
try {
|
||||
byte[] message = connection.receive();
|
||||
try (ByteArrayInputStream bis = new ByteArrayInputStream(message);
|
||||
ObjectInputStream ois = new ObjectInputStream(bis)) {
|
||||
|
||||
SysInfoMessage sysMessage = SysInfoMessage.parseSystemInfo(message);
|
||||
if (sysMessage != null) continue;
|
||||
|
||||
//TODO: Ainda não existem mensagens trocadas entre o sistema.
|
||||
}
|
||||
|
||||
} catch (SocketTimeoutException ex) {
|
||||
if (connection.hasTimedOut()) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect(TcpConnection connection, String description) {
|
||||
connectionManager.closeConnection(
|
||||
connection,
|
||||
Objects.requireNonNullElse(description, "No reason provided")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
receive();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
running = false;
|
||||
|
||||
if (serverSocket != null) {
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
connectionManager.closeAllConnections("The peer must have closed the application or changed ports");
|
||||
|
||||
connectionExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.net.Message;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class SysInfoMessage extends Message {
|
||||
|
||||
private final SysInfoType type;
|
||||
private final String message;
|
||||
|
||||
public SysInfoMessage(String message, SysInfoType type) {
|
||||
this.message = message;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public SysInfoType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public static void sendSystemInfo(TcpConnection connection, SysInfoType type, String description) {
|
||||
try {
|
||||
SysInfoMessage error = new SysInfoMessage(description, type);
|
||||
connection.send(error.toByteArray());
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(SysInfoMessage.class.getName()).log(Level.WARNING, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static SysInfoMessage parseSystemInfo(byte[] bytes) {
|
||||
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream ois = new ObjectInputStream(bis)) {
|
||||
|
||||
Object object = ois.readObject();
|
||||
if (object instanceof SysInfoMessage) {
|
||||
return (SysInfoMessage) object;
|
||||
}
|
||||
} catch (IOException | ClassNotFoundException ex) {
|
||||
Logger.getLogger(SysInfoMessage.class.getName()).log(Level.WARNING, null, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static SysInfoMessage parseSystemInfo(Message message) {
|
||||
if (message instanceof SysInfoMessage) {
|
||||
return (SysInfoMessage) message;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package pitiupi.net.tcp;
|
||||
|
||||
public enum SysInfoType {
|
||||
CONNECTION_REJECTED,
|
||||
RECEIVER_NOT_FOUND,
|
||||
MESSAGE_REJECTED,
|
||||
INTERNAL_ERROR,
|
||||
CONNECTION_CLOSED,
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
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 persistente com um peer.
|
||||
*
|
||||
* <p>Encapsula o socket e os streams utilizados para enviar e receber
|
||||
* mensagens. Também é responsável pelo framing das mensagens, permitindo
|
||||
* que várias mensagens sejam transmitidas pela mesma conexão.</p>
|
||||
*
|
||||
* @author Gustavo
|
||||
*/
|
||||
public class TcpConnection {
|
||||
private static final long TIMEOUT = 300_000; // 5 minutos
|
||||
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public synchronized void send(byte[] message) throws IOException {
|
||||
output.writeInt(message.length);
|
||||
output.write(message);
|
||||
output.flush();
|
||||
updateLastActivity();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return socket.isClosed();
|
||||
}
|
||||
|
||||
void close() throws IOException {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
public InetAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
private void updateLastActivity() {
|
||||
lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
void setTimeout(int timeout) throws SocketException {
|
||||
socket.setSoTimeout(timeout);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package pitiupi.net;
|
||||
package pitiupi.net.udp;
|
||||
|
||||
import pitiupi.GUI.MainWindow;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
|
@ -13,6 +13,9 @@ import java.io.ObjectInputStream;
|
|||
import java.net.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import pitiupi.net.HeartbeatMessage;
|
||||
import pitiupi.net.Message;
|
||||
import pitiupi.plugin.Plugin;
|
||||
|
||||
/**
|
||||
|
|
@ -145,9 +148,15 @@ public class SocketUDP extends Thread {
|
|||
main.getHeartbeatManager().receiveHeartbeat(heartbeat, msgPacket.getAddress());
|
||||
continue;
|
||||
}
|
||||
|
||||
Message message = tryParseMessage(msgPacket.getData());
|
||||
if (message == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Plugin plugin : main.getPlugins()) {
|
||||
System.out.println(msgPacket.getAddress().toString());
|
||||
plugin.receiveMessage(msgPacket.getData());
|
||||
plugin.receiveMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -170,6 +179,20 @@ public class SocketUDP extends Thread {
|
|||
}
|
||||
}
|
||||
|
||||
private Message tryParseMessage(byte[] data) {
|
||||
try {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(data);
|
||||
ObjectInputStream ois = new ObjectInputStream(bis);
|
||||
|
||||
Object object = ois.readObject();
|
||||
|
||||
if (object instanceof Message message) return message;
|
||||
return null;
|
||||
} catch (IOException | ClassNotFoundException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicia a recepção de mensagens UDP.
|
||||
*/
|
||||
|
|
@ -4,9 +4,9 @@
|
|||
*/
|
||||
package pitiupi.plugin;
|
||||
|
||||
import javax.swing.JMenu;
|
||||
import pitiupi.GUI.MainWindow;
|
||||
import pitiupi.net.Message;
|
||||
import pitiupi.net.tcp.TcpConnection;
|
||||
|
||||
/**
|
||||
* Interface base para plugins da aplicação.
|
||||
|
|
@ -21,6 +21,7 @@ public interface Plugin {
|
|||
public String getName();
|
||||
public String getAuthor();
|
||||
public String getVersion();
|
||||
public String getIdentifier();
|
||||
|
||||
/**
|
||||
* Inicializa o plugin.
|
||||
|
|
@ -46,5 +47,6 @@ public interface Plugin {
|
|||
*
|
||||
* @param message mensagem serializada recebida.
|
||||
*/
|
||||
public void receiveMessage(byte[] message);
|
||||
public void receiveMessage(Message message);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package pitiupi.plugin;
|
||||
|
||||
import pitiupi.net.tcp.TcpConnection;
|
||||
|
||||
public interface PrivateConnectionPlugin extends Plugin {
|
||||
public void receivePrivateConnection(TcpConnection connection);
|
||||
public void receiveBytes(byte[] bytes);
|
||||
public void onSocketClosed(TcpConnection connection);
|
||||
}
|
||||
Loading…
Reference in New Issue