Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23559dcc06 | ||
|
|
fa4ee046b1 | ||
|
|
0c71a85cba | ||
|
|
e7c8983775 | ||
|
|
0ec3129a22 | ||
|
|
ba2f41daf7 | ||
|
|
57059c3ef0 | ||
|
|
b5bbd1df2d | ||
|
|
93ac292f72 | ||
|
|
b610ec20e1 | ||
|
|
4461772ebf | ||
|
|
0d5a4dbe75 | ||
|
|
bbb1ceea23 | ||
|
|
7d9ca4ca71 | ||
|
|
1fcc03d9be | ||
|
|
3efc59444f | ||
|
|
947aaf00b7 | ||
|
|
e9ce17abb3 | ||
|
|
45e3bf6876 | ||
|
|
81c37225fa | ||
|
|
5c69189293 | ||
|
|
543073e84e | ||
|
|
7f21e89a0a | ||
|
|
239a10e79f | ||
|
|
a7ba307daf | ||
|
|
668b2d417b | ||
|
|
5e835f4d75 | ||
|
|
cf43f62793 | ||
|
|
5d650df8eb | ||
|
|
3beadca0c1 | ||
|
|
d2c155a2fa | ||
|
|
eff5fd2f12 | ||
|
|
076b4fcb43 | ||
|
|
d22b95e74f | ||
|
|
c89061db2e |
No files matched your search
@@ -0,0 +1,9 @@
|
|||||||
|
/pitiupi/build/
|
||||||
|
/pitiupi/dist/
|
||||||
|
/pitiupi.chat/build/
|
||||||
|
/pitiupi.chat/dist/
|
||||||
|
/pitiupi.chat/nbproject/private/
|
||||||
|
/pitiupi.screenCast/build/
|
||||||
|
/pitiupi.screenCast/dist/
|
||||||
|
|
||||||
|
.idea/
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="pitiupi.chat">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/dist/pitiupi.chat.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/pitiupi.chat.iml" filepath="$PROJECT_DIR$/pitiupi.chat.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#Thu, 16 Jul 2026 15:21:36 -0300
|
#Mon, 17 Aug 2026 14:26:26 -0300
|
||||||
|
|
||||||
|
|
||||||
/home/flavio/NetBeansProjects/pitiupi.chat=
|
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi.chat=
|
||||||
|
|
||||||
/home/flavio/NetBeansProjects/pitiupi=
|
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi=
|
||||||
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../pitiupi/dist/pitiupi.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
@@ -91,17 +91,17 @@ public class ChatWindow extends javax.swing.JDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage() {
|
private void sendMessage() {
|
||||||
if (jTextArea1.getText().trim().length() == 0) {
|
if (jTextArea1.getText().trim().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ChatMessage message = new ChatMessage(mainWindow.getName(), jTextArea1.getText());
|
ChatMessage message = new ChatMessage(mainWindow.getUserName(), jTextArea1.getText());
|
||||||
this.sendChatMessage(message);
|
this.sendChatMessage(message);
|
||||||
jTextArea1.setText("");
|
jTextArea1.setText("");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendChatMessage(Message message) {
|
public void sendChatMessage(Message message) {
|
||||||
try {
|
try {
|
||||||
mainWindow.getSocket().send(message.toByteArray());
|
mainWindow.getSocketUDP().sendMulticast(message.toByteArray());
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
Logger.getLogger(MainWindow.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(MainWindow.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AntConfiguration">
|
||||||
|
<buildFile url="file://$PROJECT_DIR$/build.xml" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+3
@@ -0,0 +1,3 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK" />
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+123
@@ -0,0 +1,123 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="083e72ce-fa9b-4ddf-a0ef-24105aa3eabb" name="Changes" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/../.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/misc.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../.idea/vcs.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi.chat/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.chat/.idea/misc.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi.chat/build/built-jar.properties" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.chat/build/built-jar.properties" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi.chat/build/classes/pitiupi/chat/GUI/ChatWindow.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.chat/build/classes/pitiupi/chat/GUI/ChatWindow.class" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi.chat/dist/pitiupi.chat.jar" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi.chat/pitiupi.chat.iml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.chat/pitiupi.chat.iml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi.chat/src/pitiupi/chat/GUI/ChatWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.chat/src/pitiupi/chat/GUI/ChatWindow.java" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/build/built-jar.properties" beforeDir="false" afterPath="$PROJECT_DIR$/build/built-jar.properties" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/build/classes/pitiupi/screenCast/GUI/ScreenCastWindow.class" beforeDir="false" afterPath="$PROJECT_DIR$/build/classes/pitiupi/screenCast/GUI/ScreenCastWindow.class" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/dist/pitiupi.screenCast.jar" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pitiupi/screenCast/GUI/ScreenCastWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/pitiupi/screenCast/GUI/ScreenCastWindow.java" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi/plugins/pitiupi.chat.jar" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/plugins/pitiupi.chat.jar" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi/plugins/pitiupi.screenCast.jar" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/plugins/pitiupi.screenCast.jar" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/GUI/MainWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/GUI/MainWindow.java" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/control/HeartbeatManager.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/control/HeartbeatManager.java" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 7
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="3Gs0wFaRD4HyhP9VXLUWBeq0qTL" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
|
"git-widget-placeholder": "feature/unicast",
|
||||||
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
|
"kotlin-language-version-configured": "true",
|
||||||
|
"last_opened_file_path": "C:/Users/extre/OneDrive/Documentos/Faculdade/2026_02/Monitoria/Pitiupi/pitiupi.screenCast",
|
||||||
|
"project.structure.last.edited": "Modules",
|
||||||
|
"project.structure.proportion": "0.15",
|
||||||
|
"project.structure.side.proportion": "0.2",
|
||||||
|
"settings.editor.selected.configurable": "preferences.keymap"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<configuration default="true" type="JetRunConfigurationType">
|
||||||
|
<module name="pitiupi.screenCast" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
|
||||||
|
<module name="pitiupi.screenCast" />
|
||||||
|
<option name="filePath" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="PythonConfigurationType" factoryName="Python">
|
||||||
|
<module name="pitiupi.screenCast" />
|
||||||
|
<option name="ENV_FILES" value="" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<option name="SCRIPT_NAME" value="" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="Tox" factoryName="Tox">
|
||||||
|
<module name="pitiupi.screenCast" />
|
||||||
|
<option name="ENV_FILES" value="" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.28539.54" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="083e72ce-fa9b-4ddf-a0ef-24105aa3eabb" name="Changes" comment="" />
|
||||||
|
<created>1784745206049</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1784745206049</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#Thu, 16 Jul 2026 15:21:39 -0300
|
#Mon, 17 Aug 2026 14:35:40 -0300
|
||||||
|
|
||||||
|
|
||||||
/home/flavio/NetBeansProjects/pitiupi.screenCast=
|
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi.screenCast=
|
||||||
|
|
||||||
/home/flavio/NetBeansProjects/pitiupi=
|
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi=
|
||||||
Binary file not shown.
BIN
Binary file not shown.
@@ -98,7 +98,7 @@ public class ScreenCastWindow extends javax.swing.JFrame {
|
|||||||
}
|
}
|
||||||
if (msg != null) {
|
if (msg != null) {
|
||||||
try {
|
try {
|
||||||
this.mainWindow.getSocket().send(msg);
|
this.mainWindow.getSocketUDP().sendMulticast(msg);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
System.out.println(ex.toString());
|
System.out.println(ex.toString());
|
||||||
this.compressRatio -= 0.1f;
|
this.compressRatio -= 0.1f;
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
plugin.TestChat
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../pitiupi/dist/pitiupi.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package GUI;
|
||||||
|
|
||||||
|
import plugin.TestChat;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ChatMenu extends JMenu {
|
||||||
|
private final TestChat testChat;
|
||||||
|
|
||||||
|
public ChatMenu(TestChat testChat) {
|
||||||
|
this.testChat = testChat;
|
||||||
|
this.setText("MP-Chat");
|
||||||
|
|
||||||
|
JMenuItem startPrivateChat = new JMenuItem("start Private Chat");
|
||||||
|
startPrivateChat.addActionListener(_ -> showPrivateChatDialog());
|
||||||
|
this.add(startPrivateChat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void newMenuItem(JMenuItem newMenuItem, ChatWindow chatWindow) {
|
||||||
|
newMenuItem.addActionListener(_ -> chatWindow.setVisible(true));
|
||||||
|
|
||||||
|
this.add(newMenuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeMenuItem(JMenuItem removeMenuItem) {
|
||||||
|
this.remove(removeMenuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPrivateChatDialog() {
|
||||||
|
JDialog dialog = new JDialog(
|
||||||
|
SwingUtilities.getWindowAncestor(this),
|
||||||
|
"Novo chat privado",
|
||||||
|
Dialog.ModalityType.APPLICATION_MODAL
|
||||||
|
);
|
||||||
|
|
||||||
|
JTextField ipField = new JTextField();
|
||||||
|
|
||||||
|
JButton startButton = new JButton("Iniciar");
|
||||||
|
JButton cancelButton = new JButton("Cancelar");
|
||||||
|
|
||||||
|
JPanel contentPanel = new JPanel(new BorderLayout(10, 10));
|
||||||
|
contentPanel.setBorder(
|
||||||
|
BorderFactory.createEmptyBorder(10, 10, 10, 10)
|
||||||
|
);
|
||||||
|
|
||||||
|
JPanel ipPanel = new JPanel(new BorderLayout(5, 5));
|
||||||
|
ipPanel.add(new JLabel("IP:"), BorderLayout.WEST);
|
||||||
|
ipPanel.add(ipField, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
JPanel buttonsPanel = new JPanel(
|
||||||
|
new FlowLayout(FlowLayout.RIGHT)
|
||||||
|
);
|
||||||
|
|
||||||
|
buttonsPanel.add(cancelButton);
|
||||||
|
buttonsPanel.add(startButton);
|
||||||
|
|
||||||
|
JLabel errorLabel = new JLabel("Não foi possível estabelecer a conexão.");
|
||||||
|
errorLabel.setForeground(Color.RED);
|
||||||
|
errorLabel.setVisible(false);
|
||||||
|
|
||||||
|
contentPanel.add(errorLabel, BorderLayout.NORTH);
|
||||||
|
contentPanel.add(ipPanel, BorderLayout.CENTER);
|
||||||
|
contentPanel.add(buttonsPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
cancelButton.addActionListener(e -> dialog.dispose());
|
||||||
|
|
||||||
|
startButton.addActionListener(e -> {
|
||||||
|
String ip = ipField.getText();
|
||||||
|
|
||||||
|
if (testChat.openConnection(ip)) dialog.dispose();
|
||||||
|
else errorLabel.setVisible(true);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.setContentPane(contentPanel);
|
||||||
|
dialog.pack();
|
||||||
|
dialog.setLocationRelativeTo(SwingUtilities.getWindowAncestor(this));
|
||||||
|
dialog.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package GUI;
|
||||||
|
|
||||||
|
import net.TestChatMessage;
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
public abstract class ChatWindow extends JFrame {
|
||||||
|
|
||||||
|
protected final MainWindow mainWindow;
|
||||||
|
|
||||||
|
private JTextArea receivedMessagesArea;
|
||||||
|
protected JTextField messageField;
|
||||||
|
|
||||||
|
public ChatWindow(MainWindow mainWindow) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
|
initComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
setTitle("Socket Test");
|
||||||
|
setSize(700, 500);
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
||||||
|
|
||||||
|
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
||||||
|
|
||||||
|
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
initReceivedMessagesPanel(mainPanel);
|
||||||
|
initBottomPanel(mainPanel);
|
||||||
|
|
||||||
|
setContentPane(mainPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initReceivedMessagesPanel(JPanel mainPanel) {
|
||||||
|
receivedMessagesArea = new JTextArea();
|
||||||
|
receivedMessagesArea.setEditable(false);
|
||||||
|
receivedMessagesArea.setLineWrap(true);
|
||||||
|
receivedMessagesArea.setWrapStyleWord(true);
|
||||||
|
|
||||||
|
JScrollPane messagesScrollPane = new JScrollPane(receivedMessagesArea);
|
||||||
|
|
||||||
|
messagesScrollPane.setBorder(BorderFactory.createTitledBorder("Mensagens recebidas"));
|
||||||
|
|
||||||
|
mainPanel.add(messagesScrollPane, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initBottomPanel(JPanel mainPanel) {
|
||||||
|
messageField = new JTextField();
|
||||||
|
|
||||||
|
JPanel bottomPanel = new JPanel(new BorderLayout(10, 10));
|
||||||
|
|
||||||
|
bottomPanel.add(messageField, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
initButtons(bottomPanel);
|
||||||
|
|
||||||
|
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void initButtons(JPanel bottomPanel);
|
||||||
|
|
||||||
|
protected TestChatMessage createMessage(String tipoMensagem) {
|
||||||
|
try {
|
||||||
|
String nome = mainWindow.getUserName();
|
||||||
|
String ip = InetAddress.getLocalHost().getHostAddress();
|
||||||
|
String message = messageField.getText();
|
||||||
|
|
||||||
|
return new TestChatMessage(nome, ip, tipoMensagem, message);
|
||||||
|
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receiveMessage(TestChatMessage msg) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
String mensagem = String.format(
|
||||||
|
"[%s] %s (%s): %s%n",
|
||||||
|
msg.getTipoMensagem(),
|
||||||
|
msg.getNome(),
|
||||||
|
msg.getIp(),
|
||||||
|
msg.getMessage()
|
||||||
|
);
|
||||||
|
|
||||||
|
receivedMessagesArea.append(mensagem);
|
||||||
|
|
||||||
|
receivedMessagesArea.setCaretPosition(receivedMessagesArea.getDocument().getLength());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package GUI;
|
||||||
|
|
||||||
|
import net.TestChatMessage;
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class PrivateChatWindow extends ChatWindow {
|
||||||
|
|
||||||
|
private final TcpConnection connection;
|
||||||
|
|
||||||
|
public PrivateChatWindow(MainWindow mainWindow, TcpConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
|
||||||
|
super(mainWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initButtons(JPanel bottomPanel) {
|
||||||
|
JButton tcpButton = new JButton("TCP");
|
||||||
|
|
||||||
|
tcpButton.addActionListener(e -> sendTCP());
|
||||||
|
|
||||||
|
JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
|
||||||
|
|
||||||
|
buttonsPanel.add(tcpButton);
|
||||||
|
|
||||||
|
bottomPanel.add(buttonsPanel, BorderLayout.EAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendTCP() {
|
||||||
|
try {
|
||||||
|
TestChatMessage msg = createMessage("TCP Private");
|
||||||
|
connection.send(msg.toByteArray());
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.getLogger(
|
||||||
|
PrivateChatWindow.class.getName()
|
||||||
|
).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package GUI;
|
||||||
|
|
||||||
|
import net.TestChatMessage;
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class PublicChatWindow extends ChatWindow {
|
||||||
|
|
||||||
|
private JTextField ipField;
|
||||||
|
|
||||||
|
public PublicChatWindow(MainWindow mainWindow) {
|
||||||
|
super(mainWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initButtons(JPanel bottomPanel) {
|
||||||
|
initIpField(bottomPanel);
|
||||||
|
initCommunicationButtons(bottomPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initIpField(JPanel bottomPanel) {
|
||||||
|
ipField = new JTextField();
|
||||||
|
ipField.setPreferredSize(new Dimension(130, 30));
|
||||||
|
|
||||||
|
JPanel ipPanel = new JPanel(new BorderLayout(5, 5));
|
||||||
|
|
||||||
|
ipPanel.add(new JLabel("IP destino:"), BorderLayout.WEST);
|
||||||
|
|
||||||
|
ipPanel.add(ipField, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
bottomPanel.add(ipPanel, BorderLayout.WEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initCommunicationButtons(JPanel bottomPanel) {
|
||||||
|
JButton tcpButton = new JButton("TCP");
|
||||||
|
JButton udpButton = new JButton("UDP");
|
||||||
|
JButton multicastButton = new JButton("Multicast");
|
||||||
|
|
||||||
|
tcpButton.addActionListener(e -> sendTCP());
|
||||||
|
udpButton.addActionListener(e -> sendUDP());
|
||||||
|
multicastButton.addActionListener(e -> sendMulticast());
|
||||||
|
|
||||||
|
JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
|
||||||
|
|
||||||
|
buttonsPanel.add(tcpButton);
|
||||||
|
buttonsPanel.add(udpButton);
|
||||||
|
buttonsPanel.add(multicastButton);
|
||||||
|
|
||||||
|
bottomPanel.add(buttonsPanel, BorderLayout.EAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendTCP() {
|
||||||
|
try {
|
||||||
|
TestChatMessage msg = createMessage("TCP Unicast");
|
||||||
|
|
||||||
|
InetAddress ip = InetAddress.getByName(ipField.getText());
|
||||||
|
|
||||||
|
mainWindow.getSocketTCP().send(msg, ip);
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendUDP() {
|
||||||
|
try {
|
||||||
|
TestChatMessage msg = createMessage("UDP Unicast");
|
||||||
|
|
||||||
|
InetAddress ip = InetAddress.getByName(ipField.getText());
|
||||||
|
|
||||||
|
mainWindow.getSocketUDP().sendUnicast(msg, ip);
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMulticast() {
|
||||||
|
try {
|
||||||
|
TestChatMessage msg = createMessage("UDP Multicast");
|
||||||
|
|
||||||
|
mainWindow.getSocketUDP().sendMulticast(msg);
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
plugin.TestChat
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package control;
|
||||||
|
|
||||||
|
import GUI.ChatMenu;
|
||||||
|
import GUI.ChatWindow;
|
||||||
|
import GUI.PrivateChatWindow;
|
||||||
|
import GUI.PublicChatWindow;
|
||||||
|
import net.TestChatMessage;
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class ChatController {
|
||||||
|
MainWindow mainWindow;
|
||||||
|
ChatMenu chatMenu;
|
||||||
|
|
||||||
|
PublicChatWindow publicChatWindow;
|
||||||
|
|
||||||
|
private final HashMap<TcpConnection, JMenuItem> chatMenus;
|
||||||
|
private final HashMap<TcpConnection, ChatWindow> chatWindows;
|
||||||
|
|
||||||
|
public ChatController(MainWindow mainWindow, ChatMenu chatMenu) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
|
publicChatWindow = new PublicChatWindow(mainWindow);
|
||||||
|
|
||||||
|
chatMenus = new HashMap<>();
|
||||||
|
chatWindows = new HashMap<>();
|
||||||
|
|
||||||
|
this.chatMenu = chatMenu;
|
||||||
|
|
||||||
|
this.chatMenu.newMenuItem(new JMenuItem("Show global chat"), publicChatWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChatWindow(TcpConnection tcpConnection) {
|
||||||
|
if (chatWindows.containsKey(tcpConnection)) return;
|
||||||
|
|
||||||
|
PrivateChatWindow newChat = new PrivateChatWindow(mainWindow, tcpConnection);
|
||||||
|
chatWindows.put(tcpConnection, newChat);
|
||||||
|
JMenuItem newMenuItem = new JMenuItem();
|
||||||
|
chatMenus.put(tcpConnection, newMenuItem);
|
||||||
|
newMenuItem.setText(tcpConnection.getAddress().toString());
|
||||||
|
chatMenu.newMenuItem(newMenuItem, newChat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeChatWindow(TcpConnection tcpConnection) {
|
||||||
|
ChatWindow removedWindow = chatWindows.remove(tcpConnection);
|
||||||
|
JMenuItem removedMenu = chatMenus.remove(tcpConnection);
|
||||||
|
|
||||||
|
chatMenu.removeMenuItem(removedMenu);
|
||||||
|
removedWindow.setVisible(false);
|
||||||
|
removedWindow.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receivePublic(TestChatMessage message) {
|
||||||
|
publicChatWindow.receiveMessage(message);
|
||||||
|
}
|
||||||
|
public void receivePrivate(TcpConnection tcpConnection, TestChatMessage message) {
|
||||||
|
chatWindows.get(tcpConnection).receiveMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package net;
|
||||||
|
|
||||||
|
import pitiupi.net.Message;
|
||||||
|
|
||||||
|
public class TestChatMessage extends Message {
|
||||||
|
private String nome;
|
||||||
|
private String ip;
|
||||||
|
private String tipoMensagem;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public TestChatMessage(String nome, String ip, String tipoMensagem, String message) {
|
||||||
|
this.nome = nome;
|
||||||
|
this.ip = ip;
|
||||||
|
this.tipoMensagem = tipoMensagem;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestChatMessage(){}
|
||||||
|
|
||||||
|
public String getNome() {
|
||||||
|
return nome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIp() {
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTipoMensagem() {
|
||||||
|
return tipoMensagem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package plugin;
|
||||||
|
|
||||||
|
import GUI.ChatMenu;
|
||||||
|
import control.ChatController;
|
||||||
|
import net.TestChatMessage;
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import pitiupi.net.Message;
|
||||||
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
|
import pitiupi.plugin.PrivateConnectionPlugin;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
|
||||||
|
public class TestChat implements PrivateConnectionPlugin{
|
||||||
|
ChatController chatController;
|
||||||
|
MainWindow mainWindow;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "MultiProtocol chat";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthor() {
|
||||||
|
return "Gustavo Henrique";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVersion() {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIdentifier() {
|
||||||
|
return "Multi.Protocol.Chat.Pitiupi";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createPlugin(MainWindow mainWindow) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
|
|
||||||
|
ChatMenu chatMenu = new ChatMenu(this);
|
||||||
|
chatController = new ChatController(mainWindow, chatMenu);
|
||||||
|
mainWindow.addMenu(chatMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message getMessage() {
|
||||||
|
return new TestChatMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receiveMessage(byte[] message) {
|
||||||
|
TestChatMessage testChatMessage = parseMessage(message);
|
||||||
|
chatController.receivePublic(testChatMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receivePrivateConnection(TcpConnection tcpConnection) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
chatController.addChatWindow(tcpConnection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receiveBytes(TcpConnection connection, byte[] bytes) {
|
||||||
|
TestChatMessage testChatMessage = parseMessage(bytes);
|
||||||
|
chatController.receivePrivate(connection, testChatMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSocketClosed(TcpConnection tcpConnection) {
|
||||||
|
chatController.removeChatWindow(tcpConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean openConnection(String ip) {
|
||||||
|
try {
|
||||||
|
mainWindow.getSocketTCP().createPrivateConnection(InetAddress.getByName(ip), getIdentifier(), getIdentifier());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
System.out.println("Error opening connection: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestChatMessage parseMessage(byte[] bytes) {
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||||
|
ObjectInputStream ois;
|
||||||
|
try {
|
||||||
|
ois = new ObjectInputStream(bis);
|
||||||
|
if (ois.readObject() instanceof TestChatMessage msg) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
} catch (IOException | ClassNotFoundException ex) {
|
||||||
|
System.out.println(ex.toString());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# NetBeans
|
||||||
|
/build/
|
||||||
|
/dist/
|
||||||
|
/nbproject/private/
|
||||||
|
|
||||||
|
# Java compilado
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# JARs gerados
|
||||||
|
*.jar
|
||||||
|
|
||||||
|
.idea/
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
Generated
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="pitiupi">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/dist/pitiupi.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
Generated
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="plugins">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/plugins/pitiupi.chat.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/plugins/pitiupi.screenCast.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/pitiupi.iml" filepath="$PROJECT_DIR$/pitiupi.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Whitespace-only changes.
Whitespace-only changes.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
-32
@@ -1,32 +0,0 @@
|
|||||||
========================
|
|
||||||
BUILD OUTPUT DESCRIPTION
|
|
||||||
========================
|
|
||||||
|
|
||||||
When you build an Java application project that has a main class, the IDE
|
|
||||||
automatically copies all of the JAR
|
|
||||||
files on the projects classpath to your projects dist/lib folder. The IDE
|
|
||||||
also adds each of the JAR files to the Class-Path element in the application
|
|
||||||
JAR files manifest file (MANIFEST.MF).
|
|
||||||
|
|
||||||
To run the project from the command line, go to the dist folder and
|
|
||||||
type the following:
|
|
||||||
|
|
||||||
java -jar "pitiupi.jar"
|
|
||||||
|
|
||||||
To distribute this project, zip up the dist folder (including the lib folder)
|
|
||||||
and distribute the ZIP file.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
|
|
||||||
* If two JAR files on the project classpath have the same name, only the first
|
|
||||||
JAR file is copied to the lib folder.
|
|
||||||
* Only JAR files are copied to the lib folder.
|
|
||||||
If the classpath contains other types of files or folders, these files (folders)
|
|
||||||
are not copied.
|
|
||||||
* If a library on the projects classpath also has a Class-Path element
|
|
||||||
specified in the manifest,the content of the Class-Path element has to be on
|
|
||||||
the projects runtime path.
|
|
||||||
* To set a main class in a standard Java project, right-click the project node
|
|
||||||
in the Projects window and choose Properties. Then click Run and enter the
|
|
||||||
class name in the Main Class field. Alternatively, you can manually type the
|
|
||||||
class name in the manifest Main-Class element.
|
|
||||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,547 @@
|
|||||||
|
# Visão geral
|
||||||
|
|
||||||
|
O `Pitiupi` é uma plataforma P2P baseada em uma arquitetura de plugins,
|
||||||
|
desenvolvida para permitir a criação de aplicações distribuídas sobre uma
|
||||||
|
infraestrutura comum de comunicação em rede.
|
||||||
|
|
||||||
|
A aplicação fornece mecanismos de descoberta de usuários, comunicação entre
|
||||||
|
peers, troca de mensagens serializadas e integração de extensões por meio de
|
||||||
|
plugins. A comunicação de rede utiliza dois protocolos de transporte: UDP,
|
||||||
|
empregado principalmente na descoberta e comunicação multicast entre as
|
||||||
|
instâncias, e TCP, utilizado para comunicação direta e persistente entre peers.
|
||||||
|
|
||||||
|
Dessa forma, novos comportamentos podem ser adicionados por meio de plugins
|
||||||
|
sem modificar o núcleo da aplicação.
|
||||||
|
|
||||||
|
O projeto foi desenvolvido como suporte às práticas da disciplina de Redes de
|
||||||
|
Computadores, servindo também como base para experimentação de aplicações
|
||||||
|
distribuídas e desenvolvimento de projetos acadêmicos.
|
||||||
|
|
||||||
|
# Arquitetura geral
|
||||||
|
|
||||||
|
A aplicação é dividida em três grupos principais:
|
||||||
|
|
||||||
|
- **Núcleo da aplicação**: A `MainWindow` atua como ponto central da aplicação,
|
||||||
|
sendo responsável por inicializar e integrar esses componentes durante a
|
||||||
|
execução.
|
||||||
|
- **Sistema de plugins**: responsável por adicionar funcionalidades sem
|
||||||
|
alterar o núcleo da aplicação.
|
||||||
|
- **Comunicação de rede**: responsável pela troca de mensagens entre
|
||||||
|
instâncias da aplicação.
|
||||||
|
|
||||||
|
## Inicialização da aplicação
|
||||||
|
|
||||||
|
1. A MainWindow é criada.
|
||||||
|
2. O SocketUDP é inicializado, associa-se à porta configurada e ingressa no
|
||||||
|
grupo multicast.
|
||||||
|
3. O SocketTCP é inicializado e cria um ServerSocket na porta da aplicação
|
||||||
|
para aceitar conexões TCP.
|
||||||
|
4. Os mecanismos de comunicação iniciam seus respectivos processos de recepção.
|
||||||
|
5. O HeartbeatManager é criado e inicia o envio periódico de heartbeats.
|
||||||
|
6. O painel de usuários online é registrado como listener do HeartbeatManager.
|
||||||
|
7. O PluginLoader procura e carrega os plugins disponíveis.
|
||||||
|
|
||||||
|
# Sistema de comunicação
|
||||||
|
|
||||||
|
A comunicação entre as instâncias da aplicação é realizada por meio de dois
|
||||||
|
mecanismos complementares:
|
||||||
|
|
||||||
|
- **UDP multicast/unicast**, implementado por `SocketUDP`
|
||||||
|
- **TCP**, implementado por `SocketTCP` e `TcpConnection`
|
||||||
|
|
||||||
|
Ambos os mecanismos utilizam mensagens serializadas em `byte[]`, permitindo que
|
||||||
|
os plugins permaneçam independentes dos detalhes do protocolo de transporte.
|
||||||
|
|
||||||
|
## Comunicação UDP
|
||||||
|
|
||||||
|
A classe `SocketUDP` é responsável pela comunicação UDP da aplicação.
|
||||||
|
|
||||||
|
Ela utiliza um `MulticastSocket` associado à porta configurada pela aplicação e
|
||||||
|
ingressa no grupo multicast `224.0.0.3`. Por ser baseada em UDP, a comunicação não
|
||||||
|
estabelece uma conexão permanente entre os peers.
|
||||||
|
|
||||||
|
A classe estende `Thread`, mantendo um processo contínuo de recepção de datagramas
|
||||||
|
enquanto a aplicação está em execução.
|
||||||
|
|
||||||
|
O SocketUDP suporta dois modos de envio:
|
||||||
|
|
||||||
|
- **multicast**, destinado ao grupo de peers
|
||||||
|
- **unicast**, destinado diretamente ao endereço IP de um peer específico
|
||||||
|
|
||||||
|
### Inicialização
|
||||||
|
|
||||||
|
Durante sua inicialização, o `SocketUDP`:
|
||||||
|
|
||||||
|
1. obtém o endereço do grupo multicast;
|
||||||
|
2. cria um `MulticastSocket` utilizando a porta configurada na `MainWindow`;
|
||||||
|
3. configura os buffers de envio e recepção;
|
||||||
|
4. habilita o reuso do endereço;
|
||||||
|
5. ingressa no grupo multicast.
|
||||||
|
|
||||||
|
O tamanho dos buffers de comunicação é configurado para `256000` bytes.
|
||||||
|
|
||||||
|
### Envio de mensagens UDP
|
||||||
|
|
||||||
|
O `SocketUDP` suporta dois modos de envio de mensagens: multicast e unicast, ambos
|
||||||
|
baseados no mesmo mecanismo interno de criação e envio de DatagramPacket.
|
||||||
|
|
||||||
|
O método `sendMulticast()` envia uma mensagem para o grupo multicast da aplicação.
|
||||||
|
Nesse caso, o `DatagramPacket` é direcionado ao endereço multicast configurado,
|
||||||
|
permitindo que todas as instâncias participantes do grupo recebam a mensagem
|
||||||
|
simultaneamente.
|
||||||
|
|
||||||
|
Já o método `sendUnicast()` permite enviar uma mensagem diretamente para um peer
|
||||||
|
específico. Nesse caso, o `DatagramPacket` é direcionado ao endereço IP informado pelo
|
||||||
|
chamador, mantendo a mesma porta utilizada pela aplicação.
|
||||||
|
|
||||||
|
Ambos os métodos são, na prática, abstrações de um único mecanismo interno de envio,
|
||||||
|
que utiliza o mesmo processo de criação e envio de `DatagramPacket` por baixo dos
|
||||||
|
panos. A diferença entre eles está apenas no endereço de destino: enquanto o multicast
|
||||||
|
utiliza o grupo compartilhado, o unicast direciona o pacote a um IP específico.
|
||||||
|
|
||||||
|
### Recepção de mensagens
|
||||||
|
|
||||||
|
O método `receive()` mantém um loop aguardando novos datagramas.
|
||||||
|
|
||||||
|
Ao receber uma mensagem, o `SocketUDP` inicialmente tenta identificar se os dados
|
||||||
|
correspondem a uma `HeartbeatMessage`.
|
||||||
|
|
||||||
|
O processamento segue o seguinte fluxo:
|
||||||
|
|
||||||
|
1. O datagrama é recebido pelo `MulticastSocket`.
|
||||||
|
2. Os dados recebidos são analisados pelo método `tryParseHeartbeat()`.
|
||||||
|
3. Caso seja identificada uma `HeartbeatMessage`, ela é encaminhada ao
|
||||||
|
`HeartbeatManager`, juntamente com o endereço IP de origem.
|
||||||
|
4. Caso não seja um heartbeat, a mensagem é encaminhada para todos os plugins
|
||||||
|
carregados.
|
||||||
|
5. Cada plugin decide se a mensagem recebida pertence à sua funcionalidade.
|
||||||
|
|
||||||
|
Essa separação mantém as mensagens de infraestrutura, como heartbeat, sob
|
||||||
|
responsabilidade do núcleo da aplicação, enquanto as mensagens específicas das
|
||||||
|
funcionalidades são processadas pelos plugins.
|
||||||
|
|
||||||
|
### Identificação de mensagens de heartbeat
|
||||||
|
|
||||||
|
O método `tryParseHeartbeat()` tenta desserializar os dados recebidos e verificar se o
|
||||||
|
objeto resultante é uma instância de `HeartbeatMessage`.
|
||||||
|
|
||||||
|
Caso a mensagem não possa ser desserializada ou não corresponda ao tipo esperado, o
|
||||||
|
método retorna `null`. Nesse caso, a mensagem é tratada como uma mensagem destinada aos
|
||||||
|
plugins.
|
||||||
|
|
||||||
|
### Encerramento
|
||||||
|
|
||||||
|
O método `close()` interrompe a execução do processo de recepção, remove a aplicação
|
||||||
|
do grupo multicast e fecha o `MulticastSocket`.
|
||||||
|
|
||||||
|
A variável `running` é utilizada para sinalizar que o processo de recepção deve ser
|
||||||
|
encerrado.
|
||||||
|
|
||||||
|
## Comunicação TCP
|
||||||
|
|
||||||
|
A comunicação TCP é implementada pela classe `SocketTCP`.
|
||||||
|
|
||||||
|
Diferentemente do UDP, o TCP estabelece conexões entre dois peers. Essas conexões
|
||||||
|
são mantidas enquanto forem necessárias e podem ser reutilizadas para o envio de
|
||||||
|
múltiplas mensagens.
|
||||||
|
|
||||||
|
A arquitetura utiliza uma conexão TCP persistente por endereço de peer.
|
||||||
|
|
||||||
|
O SocketTCP possui um mapa de conexões:
|
||||||
|
|
||||||
|
`InetAddress` -> `TcpConnection`
|
||||||
|
|
||||||
|
Esse mapa permite localizar rapidamente uma conexão existente para determinado peer e
|
||||||
|
reutilizá-la durante novos envios.
|
||||||
|
|
||||||
|
### SocketTCP
|
||||||
|
|
||||||
|
A classe `SocketTCP` estende `Thread` e implementa `PeerListener`.
|
||||||
|
|
||||||
|
Ao ser criada, ela inicializa um `ServerSocket` utilizando a porta configurada na
|
||||||
|
aplicação.
|
||||||
|
|
||||||
|
O servidor TCP permanece aguardando novas conexões enquanto a aplicação estiver
|
||||||
|
em execução.
|
||||||
|
|
||||||
|
### Envio de mensagens TCP
|
||||||
|
|
||||||
|
O método `send()` recebe uma mensagem serializada e o endereço IP do peer destinatário.
|
||||||
|
|
||||||
|
Antes de enviar a mensagem, o `SocketTCP` verifica se já existe uma conexão válida com
|
||||||
|
o peer.
|
||||||
|
|
||||||
|
O comportamento é:
|
||||||
|
|
||||||
|
1. Procurar uma conexão existente no mapa de conexões.
|
||||||
|
2. Verificar se a conexão está fechada.
|
||||||
|
3. Caso não exista uma conexão válida, criar um novo Socket TCP para o endereço do peer.
|
||||||
|
4. Encapsular o socket em uma `TcpConnection`.
|
||||||
|
5. Registrar a conexão no mapa.
|
||||||
|
6. Iniciar uma tarefa de recepção para essa conexão.
|
||||||
|
7. Enviar a mensagem pela conexão.
|
||||||
|
|
||||||
|
Assim, uma conexão TCP existente pode ser reutilizada para várias mensagens, evitando
|
||||||
|
a criação de um novo socket para cada envio.
|
||||||
|
|
||||||
|
### Recepção de conexões
|
||||||
|
|
||||||
|
O método `receive()` permanece aguardando novas conexões através de
|
||||||
|
`ServerSocket.accept()`.
|
||||||
|
|
||||||
|
Quando uma conexão é aceita:
|
||||||
|
|
||||||
|
O endereço IP do peer é obtido.
|
||||||
|
Um objeto `TcpConnection` é criado para encapsular o socket.
|
||||||
|
A conexão é registrada no mapa de conexões.
|
||||||
|
Caso já exista uma conexão associada ao mesmo endereço, a conexão anterior é encerrada.
|
||||||
|
Uma tarefa independente de recepção é criada para a nova conexão.
|
||||||
|
|
||||||
|
Esse modelo permite que o servidor aceite múltiplas conexões sem bloquear o
|
||||||
|
processamento das conexões já existentes.
|
||||||
|
|
||||||
|
### Recepção concorrente
|
||||||
|
|
||||||
|
A recepção das mensagens TCP é realizada de maneira concorrente por meio de um
|
||||||
|
`ExecutorService`.
|
||||||
|
|
||||||
|
Cada conexão possui uma tarefa própria responsável por receber continuamente as
|
||||||
|
mensagens.
|
||||||
|
|
||||||
|
O fluxo de recepção é:
|
||||||
|
|
||||||
|
```
|
||||||
|
SocketTCP
|
||||||
|
|
|
||||||
|
+-- Peer A → TcpConnection → tarefa de recepção
|
||||||
|
|
|
||||||
|
+-- Peer B → TcpConnection → tarefa de recepção
|
||||||
|
|
|
||||||
|
+-- Peer C → TcpConnection → tarefa de recepção
|
||||||
|
```
|
||||||
|
|
||||||
|
Dessa forma, uma conexão bloqueada ou aguardando dados não impede que outras conexões
|
||||||
|
continuem sendo processadas.
|
||||||
|
|
||||||
|
Quando uma mensagem é recebida por uma `TcpConnection`, ela é encaminhada para todos os
|
||||||
|
plugins registrados na `MainWindow`.
|
||||||
|
|
||||||
|
O plugin responsável pela funcionalidade deve identificar o tipo da mensagem e
|
||||||
|
realizar o processamento correspondente.
|
||||||
|
|
||||||
|
### TcpConnection
|
||||||
|
|
||||||
|
A classe TcpConnection encapsula uma conexão TCP individual entre dois peers.
|
||||||
|
|
||||||
|
Ela mantém:
|
||||||
|
|
||||||
|
o Socket utilizado na conexão;
|
||||||
|
um DataInputStream para recepção;
|
||||||
|
um DataOutputStream para envio.
|
||||||
|
|
||||||
|
Além de encapsular esses recursos, a classe é responsável pelo framing das mensagens.
|
||||||
|
|
||||||
|
### Framing das mensagens TCP
|
||||||
|
|
||||||
|
A classe `TcpConnection` encapsula uma conexão TCP individual entre dois peers.
|
||||||
|
|
||||||
|
Ela mantém:
|
||||||
|
|
||||||
|
1. o `Socket` utilizado na conexão;
|
||||||
|
2. um `DataInputStream` para recepção;
|
||||||
|
3. um `DataOutputStream` para envio.
|
||||||
|
|
||||||
|
Além de encapsular esses recursos, a classe é responsável pelo framing das mensagens.
|
||||||
|
|
||||||
|
Framing das mensagens TCP
|
||||||
|
|
||||||
|
O protocolo TCP fornece um fluxo contínuo de bytes e não preserva os limites das
|
||||||
|
mensagens enviadas pela aplicação.
|
||||||
|
|
||||||
|
Por esse motivo, é adicionado o tamanho da mensagem antes do conteúdo.
|
||||||
|
|
||||||
|
O formato utilizado é:
|
||||||
|
|
||||||
|
```
|
||||||
|
+----------------------+----------------------+
|
||||||
|
| tamanho da mensagem | dados da mensagem |
|
||||||
|
+----------------------+----------------------+
|
||||||
|
4 bytes N bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
O tamanho é enviado como um `int`, seguido pelos bytes da mensagem.
|
||||||
|
|
||||||
|
No envio, o método `TcpConnection.send()`:
|
||||||
|
|
||||||
|
1. obtém o tamanho do vetor de bytes;
|
||||||
|
2. escreve o tamanho no `DataOutputStream`;
|
||||||
|
3. escreve os dados da mensagem;
|
||||||
|
4. realiza `flush()` no stream.
|
||||||
|
|
||||||
|
O acesso ao stream de saída é sincronizado para evitar que envios concorrentes
|
||||||
|
misturem seus dados.
|
||||||
|
|
||||||
|
Na recepção, o método `receive()`:
|
||||||
|
|
||||||
|
1. lê o tamanho da mensagem;
|
||||||
|
2. verifica se o tamanho é válido;
|
||||||
|
3. lê a quantidade correspondente de bytes;
|
||||||
|
4. retorna o vetor de bytes completo.
|
||||||
|
|
||||||
|
Esse mecanismo permite que várias mensagens sejam transmitidas pela mesma conexão
|
||||||
|
TCP sem que o receptor perca a delimitação entre elas.
|
||||||
|
|
||||||
|
### Integração entre TCP e gerenciamento de peers
|
||||||
|
|
||||||
|
O `SocketTCP` implementa a interface PeerListener para acompanhar as alterações na
|
||||||
|
lista de peers ativos.
|
||||||
|
|
||||||
|
O `HeartbeatManager` mantém a lista de peers atualmente detectados na rede. Quando
|
||||||
|
essa lista é alterada, o SocketTCP recebe a notificação por meio do método
|
||||||
|
`onPeersChanged()`.
|
||||||
|
|
||||||
|
O método obtém os endereços IP dos peers atualmente ativos e compara esses endereços
|
||||||
|
com as conexões TCP existentes.
|
||||||
|
|
||||||
|
Quando uma conexão pertence a um peer que não está mais ativo:
|
||||||
|
|
||||||
|
a conexão é removida do mapa;
|
||||||
|
a conexão TCP é encerrada;
|
||||||
|
os recursos associados são liberados.
|
||||||
|
|
||||||
|
Esse mecanismo impede que conexões TCP permaneçam abertas indefinidamente para peers
|
||||||
|
que já deixaram de participar da rede.
|
||||||
|
|
||||||
|
O fluxo de integração pode ser representado da seguinte forma:
|
||||||
|
```
|
||||||
|
HeartbeatManager
|
||||||
|
|
|
||||||
|
| peers ativos alterados
|
||||||
|
|
|
||||||
|
v
|
||||||
|
SocketTCP.onPeersChanged()
|
||||||
|
|
|
||||||
|
| compara peers ativos
|
||||||
|
|
|
||||||
|
v
|
||||||
|
+---- peer ativo ------> mantém conexão
|
||||||
|
|
|
||||||
|
+---- peer inativo ----> fecha conexão
|
||||||
|
```
|
||||||
|
|
||||||
|
### Message
|
||||||
|
|
||||||
|
A classe `Message` representa a estrutura base das mensagens trocadas pela aplicação.
|
||||||
|
|
||||||
|
Todas as mensagens utilizadas pelo sistema devem herdar dessa classe. Ela implementa
|
||||||
|
`Serializable`, permitindo que objetos de mensagem sejam convertidos em vetores de
|
||||||
|
bytes para transmissão pela rede.
|
||||||
|
|
||||||
|
A serialização é realizada pelo método `toByteArray()`, que transforma uma instância
|
||||||
|
da mensagem em uma representação binária enviada pelos mecanismos de comunicação.
|
||||||
|
|
||||||
|
As mensagens específicas devem estender essa classe, adicionando os atributos e
|
||||||
|
comportamentos necessários para cada funcionalidade.
|
||||||
|
|
||||||
|
A camada de transporte não depende do conteúdo específico das mensagens. Tanto o
|
||||||
|
UDP quanto o TCP recebem mensagens serializadas como `byte[]`.
|
||||||
|
|
||||||
|
### Heartbeat
|
||||||
|
|
||||||
|
O mecanismo de heartbeat é utilizado para identificar quais usuários estão ativos
|
||||||
|
na rede.
|
||||||
|
|
||||||
|
A aplicação envia periodicamente uma `HeartbeatMessage` contendo informações do usuário,
|
||||||
|
um identificador único da instância da aplicação e um timestamp.
|
||||||
|
|
||||||
|
Essas mensagens são transmitidas utilizando a comunicação UDP multicast.
|
||||||
|
|
||||||
|
Quando outra instância recebe uma mensagem de heartbeat, o `SocketUDP` identifica
|
||||||
|
o tipo da mensagem e encaminha o objeto ao `HeartbeatManager`, juntamente com o endereço
|
||||||
|
IP de origem.
|
||||||
|
|
||||||
|
O `HeartbeatManager` registra ou atualiza o peer correspondente, utilizando o
|
||||||
|
identificador da instância para diferenciá-lo das demais.
|
||||||
|
|
||||||
|
Um peer é considerado inativo quando permanece mais de 15 segundos sem receber um novo
|
||||||
|
heartbeat. O `HeartbeatManager` então remove o peer da lista e notifica todos os
|
||||||
|
componentes registrados como listeners de mudanças de peers.
|
||||||
|
|
||||||
|
Esses listeners podem incluir diferentes partes da aplicação, como componentes
|
||||||
|
da interface gráfica, mecanismos de comunicação (como o `SocketTCP`) e também plugins
|
||||||
|
que desejem reagir à presença ou ausência de usuários na rede.
|
||||||
|
|
||||||
|
Dessa forma, o sistema permite que tanto o núcleo da aplicação quanto extensões
|
||||||
|
externas sejam notificados sobre alterações no estado da rede, mantendo a arquitetura
|
||||||
|
flexível e extensível.
|
||||||
|
|
||||||
|
O fluxo completo é:
|
||||||
|
|
||||||
|
```
|
||||||
|
HeartbeatManager
|
||||||
|
|
|
||||||
|
| cria HeartbeatMessage
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Message.toByteArray()
|
||||||
|
|
|
||||||
|
v
|
||||||
|
SocketUDP.sendMulticast()
|
||||||
|
|
|
||||||
|
v
|
||||||
|
UDP Multicast
|
||||||
|
|
|
||||||
|
v
|
||||||
|
SocketUDP.receive()
|
||||||
|
|
|
||||||
|
v
|
||||||
|
HeartbeatManager.receiveHeartbeat()
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Lista de peers ativos
|
||||||
|
|
|
||||||
|
+----> Interface de usuários online
|
||||||
|
|
|
||||||
|
+----> SocketTCP (PeerListener)
|
||||||
|
|
|
||||||
|
+----> Plugins (PeerListener)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sistema de plugins
|
||||||
|
|
||||||
|
O sistema de plugins é responsável por permitir a extensão das funcionalidades da
|
||||||
|
aplicação de forma modular, sem a necessidade de alterações no núcleo do sistema.
|
||||||
|
Ele define um contrato comum que deve ser seguido por todas as extensões, garantindo
|
||||||
|
que possam ser carregadas dinamicamente e integradas à comunicação e à interface da
|
||||||
|
aplicação.
|
||||||
|
|
||||||
|
### Interface Plugin
|
||||||
|
|
||||||
|
A interface `Plugin` define o contrato que deve ser implementado por qualquer
|
||||||
|
extensão da aplicação.
|
||||||
|
|
||||||
|
Um plugin deve fornecer informações básicas de identificação (`name`,
|
||||||
|
`author` e `version`), inicializar seu estado através de `createPlugin()` e
|
||||||
|
participar do sistema de comunicação utilizando mensagens derivadas de
|
||||||
|
`Message`.
|
||||||
|
|
||||||
|
Os principais métodos são:
|
||||||
|
|
||||||
|
#### Identificação
|
||||||
|
|
||||||
|
Os métodos:
|
||||||
|
- `getName()`
|
||||||
|
- `getAuthor()`
|
||||||
|
- `getVersion()`
|
||||||
|
|
||||||
|
fornecem informações utilizadas pela aplicação para identificar o plugin.
|
||||||
|
|
||||||
|
#### Inicialização
|
||||||
|
|
||||||
|
`createPlugin(MainWindow window)`
|
||||||
|
|
||||||
|
É chamado após o carregamento do plugin e permite que ele registre seus
|
||||||
|
componentes na aplicação, como menus ou elementos de interface.
|
||||||
|
|
||||||
|
#### Comunicação
|
||||||
|
|
||||||
|
`getMessage()`
|
||||||
|
|
||||||
|
Deve retornar uma mensagem utilizada pelo plugin para comunicação com outras
|
||||||
|
instâncias da aplicação.
|
||||||
|
|
||||||
|
`receiveMessage(byte[] message)`
|
||||||
|
|
||||||
|
É chamado pela aplicação sempre que uma mensagem é recebida. Cada plugin deve
|
||||||
|
verificar se a mensagem pertence ao seu tipo e realizar o processamento
|
||||||
|
correspondente.
|
||||||
|
|
||||||
|
### Descoberta de plugins
|
||||||
|
|
||||||
|
Para serem reconhecidos, os plugins devem possuir os arquivos de configuração
|
||||||
|
do Java Service Provider Interface (SPI):
|
||||||
|
|
||||||
|
```
|
||||||
|
MeusPlugins
|
||||||
|
|
|
||||||
|
+---META-INF
|
||||||
|
|
|
||||||
|
+---services
|
||||||
|
|
|
||||||
|
+---pitiupi.plugin.Plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
O arquivo `pitiupi.plugin.Plugin` deve conter o nome completo das classes que
|
||||||
|
implementam a interface Plugin:
|
||||||
|
|
||||||
|
```
|
||||||
|
MeusPlugins.plugins.NomeDaClasse
|
||||||
|
```
|
||||||
|
|
||||||
|
Para serem carregados, os plugins devem estar empacotados como arquivos JAR
|
||||||
|
e colocados no diretório plugins da aplicação.
|
||||||
|
|
||||||
|
### Carregamento de plugins
|
||||||
|
|
||||||
|
Ao iniciar a aplicação, o `PluginLoader` executa os seguintes passos:
|
||||||
|
|
||||||
|
1. Procura arquivos `.jar` dentro do diretório `plugins`.
|
||||||
|
2. Cria um `URLClassLoader` contendo os JARs encontrados.
|
||||||
|
3. Utiliza `ServiceLoader` para localizar classes que implementam
|
||||||
|
`Plugin`.
|
||||||
|
4. Instancia cada plugin encontrado.
|
||||||
|
5. Chama o método `createPlugin(MainWindow)`.
|
||||||
|
6. Registra o plugin na lista de plugins ativos da aplicação.
|
||||||
|
|
||||||
|
## Interface gráfica
|
||||||
|
|
||||||
|
A interface gráfica da aplicação é baseada em Swing e é centralizada na
|
||||||
|
classe `MainWindow`.
|
||||||
|
|
||||||
|
Durante a inicialização, cada plugin recebe uma referência para a
|
||||||
|
`MainWindow` através do método `createPlugin(MainWindow)`.
|
||||||
|
|
||||||
|
Essa referência permite integrar componentes gráficos à aplicação, sendo a
|
||||||
|
principal forma de extensão a adição de menus à barra de menus:
|
||||||
|
|
||||||
|
```
|
||||||
|
JMenu menu = new JMenu("Meu Plugin");
|
||||||
|
window.addMenu(menu);
|
||||||
|
```
|
||||||
|
|
||||||
|
Visão geral da comunicação
|
||||||
|
|
||||||
|
A arquitetura de comunicação pode ser resumida da seguinte maneira:
|
||||||
|
```
|
||||||
|
+------------------+
|
||||||
|
| MainWindow |
|
||||||
|
+--------+---------+
|
||||||
|
|
|
||||||
|
+--------------------+--------------------+
|
||||||
|
| | |
|
||||||
|
v v v
|
||||||
|
HeartbeatManager SocketUDP SocketTCP
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
v v v
|
||||||
|
Heartbeat UDP Multicast TCP Connections
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
+---------+----------+
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Plugins
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Message
|
||||||
|
```
|
||||||
|
|
||||||
|
O UDP multicast fornece a infraestrutura de descoberta e manutenção da presença dos
|
||||||
|
peers, além de permitir comunicação multicast e unicast.
|
||||||
|
|
||||||
|
O TCP fornece canais diretos e persistentes entre peers, com uma conexão reutilizável
|
||||||
|
por endereço.
|
||||||
|
|
||||||
|
Os dois mecanismos encaminham as mensagens destinadas às funcionalidades da aplicação
|
||||||
|
para os plugins, mantendo separadas as responsabilidades de transporte, infraestrutura
|
||||||
|
e lógica específica de cada extensão.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Como criar plugins para o Share my Sheet
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../pitiupi.chat/dist/pitiupi.chat.jar
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../pitiupi.screenCast/dist/pitiupi.screenCast.jar
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package pitiupi.GUI;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Janela que exibe informações sobre a aplicação, incluindo versão,
|
||||||
|
* descrição e autores.
|
||||||
|
*
|
||||||
|
* @author gustavo
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class About extends javax.swing.JFrame {
|
||||||
|
public About () {
|
||||||
|
this.setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
this.setTitle("About");
|
||||||
|
this.setBounds(new java.awt.Rectangle(0, 0, 800, 600));
|
||||||
|
this.getContentPane().setLayout(new java.awt.BorderLayout(5, 5));
|
||||||
|
this.setPreferredSize(new java.awt.Dimension(800, 600));
|
||||||
|
|
||||||
|
JScrollPane scroll = getJScrollPane();
|
||||||
|
|
||||||
|
this.add(scroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JScrollPane getJScrollPane() {
|
||||||
|
JEditorPane texto = new JEditorPane();
|
||||||
|
texto.setContentType("text/html");
|
||||||
|
texto.setEditable(false);
|
||||||
|
|
||||||
|
texto.setText("""
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Share My Sheet</h1>
|
||||||
|
\s
|
||||||
|
<p>
|
||||||
|
<b>Versão:</b> 1.0
|
||||||
|
</p>
|
||||||
|
\s
|
||||||
|
<p>
|
||||||
|
Plataforma P2P baseada em plugins para o desenvolvimento de aplicações
|
||||||
|
distribuídas. Criada para apoiar as práticas da disciplina de Redes de
|
||||||
|
Computadores e servir como base para projetos acadêmicos e TCCs.
|
||||||
|
\s
|
||||||
|
</p>
|
||||||
|
\s
|
||||||
|
<p>
|
||||||
|
<b>Autores:</b><br>
|
||||||
|
Flávio<br>
|
||||||
|
Tony<br>
|
||||||
|
Gustavo Henrique
|
||||||
|
</p>
|
||||||
|
\s
|
||||||
|
<p>
|
||||||
|
Universidade Federal de São João del-Rei (UFSJ)
|
||||||
|
</p>
|
||||||
|
\s
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
\s""");
|
||||||
|
|
||||||
|
return new JScrollPane(texto);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,3 @@
|
|||||||
/*
|
|
||||||
* To change this license header, choose License Headers in Project Properties.
|
|
||||||
* To change this template file, choose Tools | Templates
|
|
||||||
* and open the template in the editor.
|
|
||||||
*/
|
|
||||||
package pitiupi.GUI;
|
package pitiupi.GUI;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
@@ -11,6 +6,7 @@ import java.awt.GridBagConstraints;
|
|||||||
import java.awt.GridBagLayout;
|
import java.awt.GridBagLayout;
|
||||||
import java.awt.Insets;
|
import java.awt.Insets;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JDialog;
|
import javax.swing.JDialog;
|
||||||
@@ -20,11 +16,20 @@ import javax.swing.JSpinner;
|
|||||||
import javax.swing.JTextField;
|
import javax.swing.JTextField;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Janela de preferencias da aplicação.
|
||||||
|
*
|
||||||
|
* <p>Permite ao usuário informar o nome utilizado na rede e a porta
|
||||||
|
* multicast que será utilizada pela comunicação.</p>
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
|
* @author tony
|
||||||
*/
|
*/
|
||||||
public final class Login extends JDialog {
|
public final class Login extends JDialog {
|
||||||
|
|
||||||
|
private static final Preferences prefs = Preferences.userNodeForPackage(Login.class);
|
||||||
|
private static final String PREF_NOME = "lastUserName";
|
||||||
|
private static final String PREF_PORTA = "lastPort";
|
||||||
|
|
||||||
JLabel nameLabel = new JLabel("Name: ");
|
JLabel nameLabel = new JLabel("Name: ");
|
||||||
JLabel portLabel = new JLabel("Port: ");
|
JLabel portLabel = new JLabel("Port: ");
|
||||||
|
|
||||||
@@ -43,8 +48,11 @@ public final class Login extends JDialog {
|
|||||||
public void initComponents() {
|
public void initComponents() {
|
||||||
this.setTitle("Login");
|
this.setTitle("Login");
|
||||||
|
|
||||||
portField.setModel(new javax.swing.SpinnerNumberModel(8000, 8000, 9000, 1));
|
String nomeSalvo = prefs.get(PREF_NOME, System.getProperty("user.name"));
|
||||||
nameField.setText(System.getProperty("user.name"));
|
int portaSalva = prefs.getInt(PREF_PORTA, 8000);
|
||||||
|
|
||||||
|
portField.setModel(new javax.swing.SpinnerNumberModel(portaSalva, 8000, 9000, 1));
|
||||||
|
nameField.setText(nomeSalvo);
|
||||||
|
|
||||||
JPanel topPanel = new JPanel(new GridBagLayout());
|
JPanel topPanel = new JPanel(new GridBagLayout());
|
||||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||||
@@ -83,6 +91,15 @@ public final class Login extends JDialog {
|
|||||||
this.add(buttonPanel, BorderLayout.SOUTH);
|
this.add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
okButton.addActionListener((ActionEvent e) -> {
|
okButton.addActionListener((ActionEvent e) -> {
|
||||||
|
try {
|
||||||
|
portField.commitEdit();
|
||||||
|
} catch (java.text.ParseException ex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prefs.put(PREF_NOME, nameField.getText());
|
||||||
|
prefs.putInt(PREF_PORTA, (Integer) portField.getValue());
|
||||||
|
|
||||||
Login.this.setVisible(false);
|
Login.this.setVisible(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,28 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.GUI;
|
package pitiupi.GUI;
|
||||||
|
|
||||||
import pitiupi.net.Socket;
|
import pitiupi.net.tcp.SocketTCP;
|
||||||
|
import pitiupi.net.udp.SocketUDP;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.swing.JMenu;
|
import javax.swing.JMenu;
|
||||||
import pitiupi.control.PluginLoader;
|
import pitiupi.control.PluginLoader;
|
||||||
import pitiupi.plugin.Plugin;
|
import pitiupi.plugin.Plugin;
|
||||||
|
import pitiupi.control.HeartbeatManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Janela principal da aplicação.
|
||||||
|
*
|
||||||
|
* <p>Responsável por inicializar a interface gráfica e coordenar os
|
||||||
|
* principais componentes da aplicação, incluindo comunicação de rede,
|
||||||
|
* gerenciamento de heartbeat e carregamento de plugins.</p>
|
||||||
|
*
|
||||||
|
* <p>A instância desta classe é compartilhada com os plugins para permitir
|
||||||
|
* integração com a aplicação principal.</p>
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
|
* @author tony
|
||||||
|
* @author Gustavo
|
||||||
*/
|
*/
|
||||||
public class MainWindow extends javax.swing.JFrame {
|
public class MainWindow extends javax.swing.JFrame {
|
||||||
|
|
||||||
@@ -24,24 +36,45 @@ public class MainWindow extends javax.swing.JFrame {
|
|||||||
private javax.swing.JPanel statusPanel;
|
private javax.swing.JPanel statusPanel;
|
||||||
private javax.swing.JTabbedPane tabbedPane;
|
private javax.swing.JTabbedPane tabbedPane;
|
||||||
|
|
||||||
private Socket socket;
|
private HeartbeatManager heartbeatManager;
|
||||||
|
private OnlineUsersPanel onlineUsersPanel;
|
||||||
|
|
||||||
private final String userName;
|
|
||||||
private final int port;
|
private SocketUDP socketUDP;
|
||||||
|
private SocketTCP socketTCP;
|
||||||
|
|
||||||
|
private String userName;
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
private String pitiupiIdentifier;
|
||||||
|
|
||||||
private PluginLoader pluginManager;
|
private PluginLoader pluginManager;
|
||||||
private final List<Plugin> plugins;
|
private final List<Plugin> plugins;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria a janela principal da aplicação.
|
||||||
|
*
|
||||||
|
* @param userName nome utilizado para identificação na rede.
|
||||||
|
* @param port porta utilizada na comunicação multicast.
|
||||||
|
*/
|
||||||
public MainWindow(String userName, int port) {
|
public MainWindow(String userName, int port) {
|
||||||
this.userName = userName;
|
this.userName = userName;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
|
this.pitiupiIdentifier = "Pitiupi.Alice.2026";
|
||||||
this.menuBar = new MenuBar(this);
|
this.menuBar = new MenuBar(this);
|
||||||
this.plugins = new ArrayList<>();
|
this.plugins = new ArrayList<>();
|
||||||
this.initComponents();
|
this.initComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
|
initGUI();
|
||||||
|
initSocket();
|
||||||
|
initHeartbeat();
|
||||||
|
initPlugins();
|
||||||
|
this.setSize(new java.awt.Dimension(1024, 768));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initGUI() {
|
||||||
this.setJMenuBar(menuBar);
|
this.setJMenuBar(menuBar);
|
||||||
this.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
|
this.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
|
||||||
this.setTitle("Share My Sheet");
|
this.setTitle("Share My Sheet");
|
||||||
@@ -60,25 +93,46 @@ public class MainWindow extends javax.swing.JFrame {
|
|||||||
this.getContentPane().add(statusPanel, java.awt.BorderLayout.PAGE_END);
|
this.getContentPane().add(statusPanel, java.awt.BorderLayout.PAGE_END);
|
||||||
|
|
||||||
this.pack();
|
this.pack();
|
||||||
|
}
|
||||||
|
|
||||||
this.socket = new Socket(this);
|
private void initSocket() {
|
||||||
this.socket.start();
|
this.socketUDP = new SocketUDP(this);
|
||||||
|
this.socketTCP = new SocketTCP(this);
|
||||||
|
|
||||||
this.setSize(new java.awt.Dimension(1024, 768));
|
this.socketUDP.start();
|
||||||
|
this.socketTCP.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initHeartbeat() {
|
||||||
|
this.heartbeatManager = new HeartbeatManager(this);
|
||||||
|
this.onlineUsersPanel = new OnlineUsersPanel();
|
||||||
|
this.heartbeatManager.addPeerListener(this.onlineUsersPanel);
|
||||||
|
this.getContentPane().add(this.onlineUsersPanel, java.awt.BorderLayout.LINE_END);
|
||||||
|
this.heartbeatManager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initPlugins() {
|
||||||
this.pluginManager = new PluginLoader(this);
|
this.pluginManager = new PluginLoader(this);
|
||||||
this.pluginManager.loadPlugins();
|
this.pluginManager.loadPlugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void exit() {
|
public void exit() {
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
this.dispose();
|
this.dispose();
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HeartbeatManager getHeartbeatManager() {
|
||||||
|
return heartbeatManager;
|
||||||
|
}
|
||||||
|
|
||||||
public String getUserName(){
|
public String getUserName(){
|
||||||
return this.userName;
|
return this.userName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getIdentifier(){
|
||||||
|
return this.pitiupiIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
public void addMenu(JMenu menu) {
|
public void addMenu(JMenu menu) {
|
||||||
this.menuBar.addPlugin(menu);
|
this.menuBar.addPlugin(menu);
|
||||||
@@ -88,11 +142,45 @@ public class MainWindow extends javax.swing.JFrame {
|
|||||||
return this.port;
|
return this.port;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Socket getSocket() {
|
public SocketUDP getSocketUDP() {
|
||||||
return this.socket;
|
return this.socketUDP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketTCP getSocketTCP() {
|
||||||
|
return this.socketTCP;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Plugin> getPlugins() {
|
public List<Plugin> getPlugins() {
|
||||||
return plugins;
|
return plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUserName(String userName) {
|
||||||
|
this.userName = userName;
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPort(int port) {
|
||||||
|
this.port = port;
|
||||||
|
reconnect();
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStatus() {
|
||||||
|
this.status.setText("Connected on port " + this.port + " name " + this.userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reconnect() {
|
||||||
|
if (this.socketUDP != null) {
|
||||||
|
this.socketUDP.close();
|
||||||
|
}
|
||||||
|
if (this.socketTCP != null) {
|
||||||
|
this.socketTCP.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socketUDP = new SocketUDP(this);
|
||||||
|
this.socketTCP = new SocketTCP(this);
|
||||||
|
|
||||||
|
this.socketUDP.start();
|
||||||
|
this.socketTCP.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,18 +9,31 @@ import javax.swing.JMenu;
|
|||||||
import javax.swing.JMenuBar;
|
import javax.swing.JMenuBar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Barra de menus principal da aplicação.
|
||||||
|
*
|
||||||
|
* <p>Gerencia os menus padrões da interface e disponibiliza um espaço
|
||||||
|
* para que plugins adicionem seus próprios menus.</p>
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
|
* @author Gustavo
|
||||||
|
* @author tony
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public class MenuBar extends JMenuBar {
|
public class MenuBar extends JMenuBar {
|
||||||
|
|
||||||
private javax.swing.JMenuItem setupMenu;
|
private javax.swing.JMenuItem setupMenu;
|
||||||
|
private javax.swing.JMenuItem aboutMenu;
|
||||||
private javax.swing.JMenu fileMenu;
|
private javax.swing.JMenu fileMenu;
|
||||||
private javax.swing.JMenu helpMenu;
|
private javax.swing.JMenu helpMenu;
|
||||||
private javax.swing.JMenu pluginMenu;
|
private javax.swing.JMenu pluginMenu;
|
||||||
private javax.swing.JMenuItem exitMenu;
|
private javax.swing.JMenuItem exitMenu;
|
||||||
private final MainWindow mainWindow;
|
private final MainWindow mainWindow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria a barra de menus principal da aplicação.
|
||||||
|
*
|
||||||
|
* @param mainWindow mainWindow janela principal da aplicação utilizada para executar
|
||||||
|
*/
|
||||||
public MenuBar(MainWindow mainWindow) {
|
public MenuBar(MainWindow mainWindow) {
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.initComponents();
|
this.initComponents();
|
||||||
@@ -32,6 +45,7 @@ public class MenuBar extends JMenuBar {
|
|||||||
helpMenu = new javax.swing.JMenu();
|
helpMenu = new javax.swing.JMenu();
|
||||||
pluginMenu = new javax.swing.JMenu();
|
pluginMenu = new javax.swing.JMenu();
|
||||||
setupMenu = new javax.swing.JMenuItem();
|
setupMenu = new javax.swing.JMenuItem();
|
||||||
|
aboutMenu = new javax.swing.JMenuItem();
|
||||||
|
|
||||||
fileMenu.setText("File");
|
fileMenu.setText("File");
|
||||||
exitMenu.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F4, java.awt.event.InputEvent.ALT_MASK));
|
exitMenu.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F4, java.awt.event.InputEvent.ALT_MASK));
|
||||||
@@ -44,24 +58,44 @@ public class MenuBar extends JMenuBar {
|
|||||||
this.add(pluginMenu);
|
this.add(pluginMenu);
|
||||||
|
|
||||||
helpMenu.setText("Help");
|
helpMenu.setText("Help");
|
||||||
|
|
||||||
setupMenu.setText("Preferences");
|
setupMenu.setText("Preferences");
|
||||||
setupMenu.addActionListener(this::setupMenuActionPerformed);
|
setupMenu.addActionListener(this::setupMenuActionPerformed);
|
||||||
helpMenu.add(setupMenu);
|
helpMenu.add(setupMenu);
|
||||||
|
|
||||||
|
aboutMenu.setText("About");
|
||||||
|
aboutMenu.addActionListener(this::aboutMenuActionPerformed);
|
||||||
|
helpMenu.add(aboutMenu);
|
||||||
|
|
||||||
this.add(helpMenu);
|
this.add(helpMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void exitMenuActionPerformed(java.awt.event.ActionEvent evt) {
|
private void exitMenuActionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
mainWindow.exit();
|
mainWindow.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupMenuActionPerformed(java.awt.event.ActionEvent evt) {
|
|
||||||
|
private void setupMenuActionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
Login login = new Login();
|
Login login = new Login();
|
||||||
login.setVisible(true);
|
login.setVisible(true);
|
||||||
|
|
||||||
|
mainWindow.setUserName(login.getName());
|
||||||
|
mainWindow.setPort(login.getPort());
|
||||||
|
|
||||||
login.dispose();
|
login.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void aboutMenuActionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
About about = new About();
|
||||||
|
about.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adiciona um menu fornecido por um plugin à seção de plugins da barra
|
||||||
|
* de menus.
|
||||||
|
*
|
||||||
|
* @param menu menu criado pelo plugin.
|
||||||
|
*/
|
||||||
public void addPlugin(JMenu menu){
|
public void addPlugin(JMenu menu){
|
||||||
this.pluginMenu.add(menu);
|
this.pluginMenu.add(menu);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package pitiupi.GUI;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.DefaultListModel;
|
||||||
|
import javax.swing.JList;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import pitiupi.control.PeerInfo;
|
||||||
|
import pitiupi.control.PeerListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Painel da interface gráfica responsável por exibir os usuários ativos
|
||||||
|
* na rede.
|
||||||
|
*
|
||||||
|
* @author tony
|
||||||
|
*/
|
||||||
|
public class OnlineUsersPanel extends JPanel implements PeerListener {
|
||||||
|
|
||||||
|
private final DefaultListModel<String> listModel;
|
||||||
|
private final JList<String> userList;
|
||||||
|
|
||||||
|
public OnlineUsersPanel() {
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(BorderFactory.createTitledBorder("Online"));
|
||||||
|
setPreferredSize(new Dimension(160, 0));
|
||||||
|
|
||||||
|
listModel = new DefaultListModel<>();
|
||||||
|
userList = new JList<>(listModel);
|
||||||
|
add(new JScrollPane(userList), BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atualiza a lista de usuários exibida no painel.
|
||||||
|
*
|
||||||
|
* @param peers lista atualizada de usuários ativos na rede.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onPeersChanged(List<PeerInfo> peers) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
listModel.clear();
|
||||||
|
for (PeerInfo peer : peers) {
|
||||||
|
listModel.addElement(peer.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,9 +12,9 @@ import java.util.logging.Logger;
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
|
* @author tony
|
||||||
*/
|
*/
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param args the command line arguments
|
* @param args the command line arguments
|
||||||
*/
|
*/
|
||||||
@@ -40,16 +40,17 @@ public class Main {
|
|||||||
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
|
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
|
||||||
java.util.logging.Logger.getLogger(pitiupi.GUI.MainWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
|
java.util.logging.Logger.getLogger(pitiupi.GUI.MainWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
|
||||||
}
|
}
|
||||||
//</editor-fold>
|
|
||||||
|
|
||||||
/* Create and display the form */
|
|
||||||
java.awt.EventQueue.invokeLater(new Runnable() {
|
java.awt.EventQueue.invokeLater(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
pitiupi.GUI.Login login = new pitiupi.GUI.Login();
|
||||||
|
login.setVisible(true);
|
||||||
|
|
||||||
pitiupi.GUI.MainWindow main;
|
pitiupi.GUI.MainWindow main;
|
||||||
main = new pitiupi.GUI.MainWindow(System.getProperty("user.name"), 8000);
|
main = new pitiupi.GUI.MainWindow(login.getName(), login.getPort());
|
||||||
main.setVisible(true);
|
main.setVisible(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package pitiupi.control;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import pitiupi.net.HeartbeatMessage;
|
||||||
|
import java.util.UUID;
|
||||||
|
/**
|
||||||
|
* Gerencia o mecanismo de heartbeat da aplicação.
|
||||||
|
*
|
||||||
|
* <p>Responsável por anunciar a presença da aplicação na rede, receber
|
||||||
|
* heartbeats de outros usuários, controlar o tempo desde a última atividade
|
||||||
|
* dos peers e notificar listeners quando a lista de usuários ativos muda.</p>
|
||||||
|
*
|
||||||
|
* @author tony
|
||||||
|
*/
|
||||||
|
public class HeartbeatManager {
|
||||||
|
|
||||||
|
private static final long HEARTBEAT_INTERVAL_MS = 5000;
|
||||||
|
private static final long PEER_TIMEOUT_MS = 15000; // ~3 batimentos perdidos
|
||||||
|
|
||||||
|
private final MainWindow mainWindow;
|
||||||
|
private final Map<String, PeerInfo> peers = new ConcurrentHashMap<>();
|
||||||
|
private final List<PeerListener> listeners = new CopyOnWriteArrayList<>();
|
||||||
|
private ScheduledExecutorService scheduler;
|
||||||
|
private final String instanceId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria um gerenciador de heartbeat associado à janela principal da aplicação.
|
||||||
|
*
|
||||||
|
* @param mainWindow janela principal utilizada para obter o nome do usuário
|
||||||
|
* e o socket de comunicação.
|
||||||
|
*/
|
||||||
|
public HeartbeatManager(MainWindow mainWindow) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inicia o mecanismo de heartbeat.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
scheduler = Executors.newScheduledThreadPool(1);
|
||||||
|
scheduler.scheduleAtFixedRate(this::announce, 0, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS);
|
||||||
|
scheduler.scheduleAtFixedRate(this::checkTimeouts, HEARTBEAT_INTERVAL_MS, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Para o mecanismo de heartbeat e encerra as tarefas agendadas.
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
if (scheduler != null) {
|
||||||
|
scheduler.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void announce() {
|
||||||
|
try {
|
||||||
|
HeartbeatMessage msg = new HeartbeatMessage(mainWindow.getUserName(), instanceId);
|
||||||
|
mainWindow.getSocketUDP().sendMulticast(msg);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.getLogger(HeartbeatManager.class.getName()).log(Level.WARNING, "Falha ao enviar heartbeat", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chamado pelo Socket quando um pacote é identificado como heartbeat
|
||||||
|
/**
|
||||||
|
* Processa uma mensagem de heartbeat recebida de outro usuário.
|
||||||
|
*
|
||||||
|
* <p>Atualiza o momento da última atividade do peer correspondente e notifica os listeners
|
||||||
|
* caso outra instância da aplicação seja identificada.</p>
|
||||||
|
*
|
||||||
|
* <p>Mensagens de heartbeat enviadas pela própria aplicação são ignoradas.</p>
|
||||||
|
*
|
||||||
|
* @param msg mensagem de heartbeat recebida.
|
||||||
|
* @param from endereço de rede do usuário que enviou a mensagem.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void receiveHeartbeat(HeartbeatMessage msg, InetAddress from) {
|
||||||
|
// ignora o próprio heartbeat
|
||||||
|
if (msg.getInstanceId().equals(instanceId) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = msg.getInstanceId();
|
||||||
|
boolean isNew = !peers.containsKey(key);
|
||||||
|
|
||||||
|
PeerInfo peer = peers.computeIfAbsent(key, k -> new PeerInfo(msg.getUserName(), from, msg.getTimestamp()));
|
||||||
|
peer.touch(System.currentTimeMillis());
|
||||||
|
|
||||||
|
if (isNew) {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkTimeouts() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
boolean changed = peers.values().removeIf(p -> now - p.getLastSeen() > PEER_TIMEOUT_MS);
|
||||||
|
if (changed) {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyListeners() {
|
||||||
|
List<PeerInfo> snapshot = new ArrayList<>(peers.values());
|
||||||
|
for (PeerListener l : listeners) {
|
||||||
|
l.onPeersChanged(snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adiciona um listener para receber atualizações sobre os peers conhecidos.
|
||||||
|
*
|
||||||
|
* <p>O listener recebe imediatamente o estado atual dos peers ao ser
|
||||||
|
* registrado.</p>
|
||||||
|
*
|
||||||
|
* @param l listener que será notificado sobre alterações.
|
||||||
|
*/
|
||||||
|
public void addPeerListener(PeerListener l) {
|
||||||
|
listeners.add(l);
|
||||||
|
l.onPeersChanged(new ArrayList<>(peers.values()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove um listener previamente registrado.
|
||||||
|
*
|
||||||
|
* @param l listener que deixará de receber atualizações.
|
||||||
|
*/
|
||||||
|
public void removePeerListener(PeerListener l) {
|
||||||
|
listeners.remove(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna uma cópia da lista atual de peers conhecidos.
|
||||||
|
*
|
||||||
|
* @return lista contendo os peers atualmente conhecidos.
|
||||||
|
*/
|
||||||
|
public List<PeerInfo> getPeers() {
|
||||||
|
return new ArrayList<>(peers.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package pitiupi.control;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
/**
|
||||||
|
* Representa um usuário identificado na rede e as informações necessárias
|
||||||
|
* para acompanhar sua presença através de mensagens de heartbeat.
|
||||||
|
*
|
||||||
|
* <p>Armazena o nome do usuário, seu endereço de rede e o instante da
|
||||||
|
* última mensagem recebida.</p>
|
||||||
|
*
|
||||||
|
* @author tony
|
||||||
|
*/
|
||||||
|
public class PeerInfo {
|
||||||
|
|
||||||
|
private final String userName;
|
||||||
|
private final InetAddress address;
|
||||||
|
private volatile long lastSeen;
|
||||||
|
|
||||||
|
public PeerInfo(String userName, InetAddress address, long lastSeen) {
|
||||||
|
this.userName = userName;
|
||||||
|
this.address = address;
|
||||||
|
this.lastSeen = lastSeen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName() {
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetAddress getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastSeen() {
|
||||||
|
return lastSeen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atualiza o instante da última atividade do usuário.
|
||||||
|
*
|
||||||
|
* @param timestamp novo instante de atividade.
|
||||||
|
*/
|
||||||
|
public void touch(long timestamp) {
|
||||||
|
this.lastSeen = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return userName + " (" + address.getHostAddress() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package pitiupi.control;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface para receber notificações sobre alterações nos usuários
|
||||||
|
* conhecidos na rede.
|
||||||
|
*
|
||||||
|
* <p>Implementações desta interface são notificadas sempre que a lista
|
||||||
|
* de usuários ativos é modificada.</p>
|
||||||
|
*
|
||||||
|
* @author tony
|
||||||
|
*/
|
||||||
|
public interface PeerListener {
|
||||||
|
/**
|
||||||
|
* Chamado quando a lista de usuários conhecidos na rede é alterada.
|
||||||
|
*
|
||||||
|
* @param peers lista atualizada de usuários ativos.
|
||||||
|
*/
|
||||||
|
void onPeersChanged(List<PeerInfo> peers);
|
||||||
|
}
|
||||||
@@ -17,7 +17,12 @@ import pitiupi.Main;
|
|||||||
import pitiupi.plugin.Plugin;
|
import pitiupi.plugin.Plugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Responsável por localizar, carregar e registrar os plugins
|
||||||
|
* presentes no diretório {@code plugins}.
|
||||||
*
|
*
|
||||||
|
* <p>Os plugins são descobertos utilizando {@link java.util.ServiceLoader}
|
||||||
|
* e devem estar empacotados como arquivos JAR contendo a configuração
|
||||||
|
* {@code META-INF/services} correspondente.</p>
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public class PluginLoader {
|
public class PluginLoader {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package pitiupi.net;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mensagem utilizada para sinalizar a presença de um usuário na rede.
|
||||||
|
*
|
||||||
|
* <p>É enviada periodicamente para permitir que a aplicação identifique
|
||||||
|
* usuários ativos e mantenha o controle dos participantes conectados.</p>
|
||||||
|
*
|
||||||
|
* @author tony
|
||||||
|
*/
|
||||||
|
public class HeartbeatMessage extends Message {
|
||||||
|
|
||||||
|
private final String userName;
|
||||||
|
private final long timestamp;
|
||||||
|
private final String instanceId;
|
||||||
|
|
||||||
|
public HeartbeatMessage(String userName, String instanceId) {
|
||||||
|
this.userName = userName;
|
||||||
|
this.instanceId = instanceId;
|
||||||
|
this.timestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName() {
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInstanceId() {
|
||||||
|
return instanceId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,11 +12,25 @@ import java.io.ObjectOutputStream;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Classe base para as mensagens trocadas entre a aplicação e os plugins.
|
||||||
|
*
|
||||||
|
* <p>As mensagens devem estender esta classe para utilizar o sistema de
|
||||||
|
* comunicação da aplicação. Subclasses podem adicionar os dados e a lógica
|
||||||
|
* necessários para representar diferentes tipos de mensagens.</p>
|
||||||
|
*
|
||||||
|
* <p>Por implementar {@link Serializable}, objetos derivados desta classe
|
||||||
|
* podem ser convertidos em bytes para transmissão pela rede.</p>
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public abstract class Message implements Serializable{
|
public abstract class Message implements Serializable{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializa a mensagem para um vetor de bytes.
|
||||||
|
*
|
||||||
|
* @return representação serializada da mensagem.
|
||||||
|
* @throws IOException caso ocorra um erro durante a serialização.
|
||||||
|
*/
|
||||||
public byte[] toByteArray() throws IOException {
|
public byte[] toByteArray() throws IOException {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
ObjectOutput out = new ObjectOutputStream(baos);
|
ObjectOutput out = new ObjectOutputStream(baos);
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* To change this license header, choose License Headers in Project Properties.
|
|
||||||
* To change this template file, choose Tools | Templates
|
|
||||||
* and open the template in the editor.
|
|
||||||
*/
|
|
||||||
package pitiupi.net;
|
|
||||||
|
|
||||||
import pitiupi.GUI.MainWindow;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInput;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.net.DatagramPacket;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.MulticastSocket;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import pitiupi.plugin.Plugin;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author flavio
|
|
||||||
*/
|
|
||||||
public class Socket extends Thread {
|
|
||||||
|
|
||||||
private MulticastSocket multicastSocket;
|
|
||||||
private InetAddress address;
|
|
||||||
private MainWindow main;
|
|
||||||
public final static String INET_ADDR = "224.0.0.3";
|
|
||||||
|
|
||||||
public Socket(MainWindow main) {
|
|
||||||
this.main = main;
|
|
||||||
try {
|
|
||||||
this.address = InetAddress.getByName(Socket.INET_ADDR);
|
|
||||||
} catch (UnknownHostException ex) {
|
|
||||||
System.getLogger(Socket.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
multicastSocket = new MulticastSocket(this.main.getPort());
|
|
||||||
multicastSocket.setSendBufferSize(256000);
|
|
||||||
multicastSocket.setReceiveBufferSize(256000);
|
|
||||||
multicastSocket.setReuseAddress(true);
|
|
||||||
multicastSocket.joinGroup(address);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
System.out.println("There is no socket connection. Sorry.");
|
|
||||||
System.out.println(ex.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(byte[] msg) throws IOException {
|
|
||||||
DatagramPacket msgPacket;
|
|
||||||
msgPacket = new DatagramPacket(msg, msg.length, this.address, this.main.getPort());
|
|
||||||
multicastSocket.send(msgPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void receive() throws UnknownHostException, IOException, ClassNotFoundException {
|
|
||||||
byte[] buf = new byte[256000];
|
|
||||||
while (true) {
|
|
||||||
// Receive the information and print it.
|
|
||||||
DatagramPacket msgPacket = new DatagramPacket(buf, buf.length);
|
|
||||||
multicastSocket.receive(msgPacket);
|
|
||||||
for (Plugin plugin : main.getPlugins()) {
|
|
||||||
System.out.println(msgPacket.getAddress().toString());
|
|
||||||
plugin.receiveMessage(msgPacket.getData());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
this.receive();
|
|
||||||
} catch (IOException | ClassNotFoundException ex) {
|
|
||||||
Logger.getLogger(Socket.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
private static final int HANDSHAKE_TIMEOUT = 5000;
|
||||||
|
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) {
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* <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 já 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) {
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* <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) {
|
||||||
|
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) {
|
||||||
|
SysInfoMessage sysInfo = SysInfoMessage.parseSystemInfo(bytes);
|
||||||
|
if (sysInfo != null) 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
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,262 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Map<InetAddress, TcpConnection> publicConnections;
|
||||||
|
private final Map<InetAddress, List<TcpConnection>> privateConnections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria um gerenciador de conexões TCP vazio.
|
||||||
|
*/
|
||||||
|
public ConnectionManager() {
|
||||||
|
publicConnections = new ConcurrentHashMap<>();
|
||||||
|
privateConnections = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return publicConnections.get(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 =
|
||||||
|
privateConnections.computeIfAbsent(connection.getAddress(), _ -> new ArrayList<>());
|
||||||
|
|
||||||
|
synchronized (connections) {
|
||||||
|
connections.add(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registra uma conexão pública associada a um peer.
|
||||||
|
*
|
||||||
|
* <p>Cada 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.</p>
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*
|
||||||
|
* <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) {
|
||||||
|
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
|
||||||
|
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 já 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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
List<TcpConnection> connections = privateConnections.get(address);
|
||||||
|
for (TcpConnection connection : connections) {
|
||||||
|
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
for (List<TcpConnection> connections : privateConnections.values()) {
|
||||||
|
for (TcpConnection connection : connections) {
|
||||||
|
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
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>Os 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.</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,476 @@
|
|||||||
|
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 é 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.</p>
|
||||||
|
*
|
||||||
|
* @param main janela principal da aplicação, usada para obter a porta,
|
||||||
|
* o identificador local e os plugins disponíveis.
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 que será enviada.
|
||||||
|
* @param destinationAddress endereço IP do peer que receberá a mensagem.
|
||||||
|
*/
|
||||||
|
public void send(Message 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.toByteArray());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
System.out.println("Could not send TCP message.");
|
||||||
|
System.out.println(ex.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
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 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 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) {
|
||||||
|
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 socket aceito é encaminhado para processamento assíncrono,
|
||||||
|
* interpretado como uma {@link TcpConnection}, registrado pelo
|
||||||
|
* {@link ConnectionManager} e associado ao receptor adequado.</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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* <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) {
|
||||||
|
if (connection.isPrivate()) {
|
||||||
|
connectionManager.addPrivateConnection(connection);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
private void startReceiver(TcpConnection connection) {
|
||||||
|
|
||||||
|
if (!connection.isPrivate()) {
|
||||||
|
publicConnectionReceiver(connection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection.getReceiver().equals(main.getIdentifier())) {
|
||||||
|
systemConnectionReceiver(connection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Plugin receiver = findConnectionReceiver(connection);
|
||||||
|
if (receiver instanceof PrivateConnectionPlugin plugin) {
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
byte[] bytes = connection.receive();
|
||||||
|
|
||||||
|
SysInfoMessage sysMessage = SysInfoMessage.parseSystemInfo(bytes);
|
||||||
|
|
||||||
|
if (sysMessage == null) {
|
||||||
|
for (Plugin plugin : main.getPlugins()) {
|
||||||
|
plugin.receiveMessage(bytes);
|
||||||
|
}
|
||||||
|
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 -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketTimeoutException ex) {
|
||||||
|
if (connection.hasTimedOut()) {
|
||||||
|
connectionManager.closeConnection(connection, "Connection timeout");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
connectionManager.closeConnection(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
if (plugin instanceof PrivateConnectionPlugin privatePlugin) {
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
byte[] message = connection.receive();
|
||||||
|
SysInfoMessage sysMessage = SysInfoMessage.parseSystemInfo(message);
|
||||||
|
|
||||||
|
if (sysMessage == null) {
|
||||||
|
privatePlugin.receiveBytes(connection, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 há 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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
connectionManager.closeConnection(
|
||||||
|
connection,
|
||||||
|
Objects.requireNonNullElse(description, "No reason provided")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
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() {
|
||||||
|
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,51 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,171 @@
|
|||||||
|
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.
|
||||||
|
*
|
||||||
|
* <p>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.</p>
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*
|
||||||
|
* <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 {
|
||||||
|
output.writeInt(message.length);
|
||||||
|
output.write(message);
|
||||||
|
output.flush();
|
||||||
|
updateLastActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recebe uma mensagem da conexão.
|
||||||
|
*
|
||||||
|
* <p>Primeiro lê o tamanho da mensagem e, em seguida, lê 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 {
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* <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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package pitiupi.net.udp;
|
||||||
|
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInput;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gerencia a comunicação UDP multicast e unicast da aplicação.
|
||||||
|
*
|
||||||
|
* <p>Mantém a conexão com o grupo multicast da aplicação e permite o envio
|
||||||
|
* de mensagens tanto para o grupo quanto diretamente para um peer.</p>
|
||||||
|
*
|
||||||
|
* <p>As mensagens recebidas são verificadas para identificar mensagens de
|
||||||
|
* heartbeat. As demais mensagens são encaminhadas aos plugins, que são
|
||||||
|
* responsáveis por interpretá-las.</p>
|
||||||
|
*
|
||||||
|
* @author flavio
|
||||||
|
* @author tony
|
||||||
|
* @author Gustavo
|
||||||
|
*/
|
||||||
|
public class SocketUDP extends Thread {
|
||||||
|
|
||||||
|
private MulticastSocket multicastSocket;
|
||||||
|
|
||||||
|
private InetAddress address;
|
||||||
|
private MainWindow main;
|
||||||
|
private volatile boolean running = true;
|
||||||
|
public final static String INET_ADDR = "224.0.0.3";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria e inicializa o socket UDP utilizado pela aplicação.
|
||||||
|
*
|
||||||
|
* <p>O socket é associado à porta da aplicação e ingressa no grupo
|
||||||
|
* multicast configurado.</p>
|
||||||
|
*
|
||||||
|
* @param main janela principal da aplicação.
|
||||||
|
*/
|
||||||
|
public SocketUDP(MainWindow main) {
|
||||||
|
this.main = main;
|
||||||
|
try {
|
||||||
|
this.address = InetAddress.getByName(SocketUDP.INET_ADDR);
|
||||||
|
} catch (UnknownHostException ex) {
|
||||||
|
System.getLogger(SocketUDP.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
multicastSocket = new MulticastSocket(this.main.getPort());
|
||||||
|
multicastSocket.setSendBufferSize(256000);
|
||||||
|
multicastSocket.setReceiveBufferSize(256000);
|
||||||
|
multicastSocket.setReuseAddress(true);
|
||||||
|
multicastSocket.joinGroup(address);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.out.println("There is no socket connection. Sorry.");
|
||||||
|
System.out.println(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envia uma mensagem para o grupo multicast da aplicação.
|
||||||
|
*
|
||||||
|
* @param msg mensagem serializada a ser enviada.
|
||||||
|
* @throws IOException caso ocorra um erro durante o envio.
|
||||||
|
*/
|
||||||
|
public void sendMulticast(Message msg) throws IOException {
|
||||||
|
send(msg, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envia uma mensagem diretamente para um peer utilizando UDP.
|
||||||
|
*
|
||||||
|
* @param msg mensagem serializada a ser enviada.
|
||||||
|
* @param destinationAddress endereço IP do peer destinatário.
|
||||||
|
* @throws IOException caso ocorra um erro durante o envio.
|
||||||
|
*/
|
||||||
|
public void sendUnicast(Message msg, InetAddress destinationAddress) throws IOException {
|
||||||
|
send(msg, destinationAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envia uma mensagem UDP para o endereço especificado.
|
||||||
|
*
|
||||||
|
* @param msg mensagem serializada a ser enviada.
|
||||||
|
* @param destinationAddress endereço de destino.
|
||||||
|
* @throws IOException caso ocorra um erro durante o envio.
|
||||||
|
*/
|
||||||
|
private void send(Message msg, InetAddress destinationAddress) throws IOException {
|
||||||
|
DatagramPacket msgPacket;
|
||||||
|
byte[] buf = msg.toByteArray();
|
||||||
|
msgPacket = new DatagramPacket(buf, buf.length, destinationAddress, this.main.getPort());
|
||||||
|
multicastSocket.send(msgPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encerra a comunicação multicast e interrompe a recepção de mensagens.
|
||||||
|
*/
|
||||||
|
public void close() {
|
||||||
|
running = false;
|
||||||
|
if (multicastSocket != null) {
|
||||||
|
try {
|
||||||
|
multicastSocket.leaveGroup(address);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.out.println(ex);
|
||||||
|
}
|
||||||
|
multicastSocket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recebe e processa mensagens UDP.
|
||||||
|
*
|
||||||
|
* <p>Mensagens de heartbeat são encaminhadas ao gerenciador de heartbeat.
|
||||||
|
* As demais mensagens são encaminhadas aos plugins carregados pela
|
||||||
|
* aplicação.</p>
|
||||||
|
*
|
||||||
|
* @throws UnknownHostException caso não seja possível resolver o endereço
|
||||||
|
* utilizado pela comunicação.
|
||||||
|
* @throws IOException caso ocorra um erro durante a recepção.
|
||||||
|
* @throws ClassNotFoundException caso ocorra um erro ao verificar uma
|
||||||
|
* mensagem de heartbeat.
|
||||||
|
*/
|
||||||
|
public void receive() throws UnknownHostException, IOException, ClassNotFoundException {
|
||||||
|
byte[] buf = new byte[256000];
|
||||||
|
while (running) {
|
||||||
|
DatagramPacket msgPacket = new DatagramPacket(buf, buf.length);
|
||||||
|
try {
|
||||||
|
multicastSocket.receive(msgPacket);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
if (!running) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
HeartbeatMessage heartbeat = tryParseHeartbeat(msgPacket.getData());
|
||||||
|
if (heartbeat != null) {
|
||||||
|
main.getHeartbeatManager().receiveHeartbeat(heartbeat, msgPacket.getAddress());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Plugin plugin : main.getPlugins()) {
|
||||||
|
System.out.println(msgPacket.getAddress().toString());
|
||||||
|
plugin.receiveMessage(msgPacket.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tenta identificar os dados recebidos como uma mensagem de heartbeat.
|
||||||
|
*
|
||||||
|
* @param data dados serializados recebidos.
|
||||||
|
* @return a mensagem de heartbeat caso os dados correspondam a uma;
|
||||||
|
* {@code null} caso contrário.
|
||||||
|
*/
|
||||||
|
private HeartbeatMessage tryParseHeartbeat(byte[] data) {
|
||||||
|
try {
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||||
|
ObjectInput in = new ObjectInputStream(bais);
|
||||||
|
Object obj = in.readObject();
|
||||||
|
return (obj instanceof HeartbeatMessage) ? (HeartbeatMessage) obj : null;
|
||||||
|
} catch (IOException | ClassNotFoundException | ClassCastException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inicia a recepção de mensagens UDP.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
this.receive();
|
||||||
|
} catch (IOException | ClassNotFoundException ex) {
|
||||||
|
Logger.getLogger(SocketUDP.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,11 +4,15 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.plugin;
|
package pitiupi.plugin;
|
||||||
|
|
||||||
import javax.swing.JMenu;
|
|
||||||
import pitiupi.GUI.MainWindow;
|
import pitiupi.GUI.MainWindow;
|
||||||
import pitiupi.net.Message;
|
import pitiupi.net.Message;
|
||||||
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Interface base para plugins da aplicação.
|
||||||
|
*
|
||||||
|
* <p>Cada plugin deve implementar esta interface para ser carregado
|
||||||
|
* pela aplicação e participar do sistema de comunicação por mensagens.</p>
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
@@ -17,7 +21,32 @@ public interface Plugin {
|
|||||||
public String getName();
|
public String getName();
|
||||||
public String getAuthor();
|
public String getAuthor();
|
||||||
public String getVersion();
|
public String getVersion();
|
||||||
|
public String getIdentifier();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inicializa o plugin.
|
||||||
|
*
|
||||||
|
* <p>Este método é chamado pela aplicação após o carregamento do plugin.</p>
|
||||||
|
*
|
||||||
|
* @param window janela principal da aplicação.
|
||||||
|
*/
|
||||||
public void createPlugin(MainWindow window);
|
public void createPlugin(MainWindow window);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna uma mensagem utilizada pelo plugin.
|
||||||
|
*
|
||||||
|
* @return mensagem criada pelo plugin.
|
||||||
|
*/
|
||||||
public Message getMessage();
|
public Message getMessage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recebe uma mensagem enviada pela aplicação.
|
||||||
|
*
|
||||||
|
* <p>A mensagem é recebida em formato serializado. O plugin deve verificar
|
||||||
|
* se a mensagem pertence ao seu tipo e realizar o processamento necessário.</p>
|
||||||
|
*
|
||||||
|
* @param message mensagem serializada recebida.
|
||||||
|
*/
|
||||||
public void receiveMessage(byte[] message);
|
public void receiveMessage(byte[] 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(TcpConnection connection, byte[] bytes);
|
||||||
|
public void onSocketClosed(TcpConnection connection);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user