Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d551324e8b |
No files matched your search
Generated
+1
@@ -1,3 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="openjdk-27" project-jdk-type="JavaSDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="openjdk-27" project-jdk-type="JavaSDK" />
|
||||||
</project>
|
</project>
|
||||||
Generated
-1
@@ -2,6 +2,5 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/udpTest" vcs="Git" />
|
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
Generated
-85
@@ -1,85 +0,0 @@
|
|||||||
<?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="e2cdacc6-69cd-4190-bef1-27719e32140a" 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" afterPath="$PROJECT_DIR$/../.idea/vcs.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/GUI/PrivateChatWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/GUI/PrivateChatWindow.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/GUI/PublicChatWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/GUI/PublicChatWindow.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/plugin/MultiProtocolChat.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/plugin/MultiProtocolChat.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/.idea/misc.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/.idea/workspace.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/built-jar.properties" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/built-jar.properties" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/GUI/ScreenCastMenu.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/GUI/ScreenCastMenu.class" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/GUI/ScreenCastWindow.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/GUI/ScreenCastWindow.class" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/net/ScreenCastMessage.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/net/ScreenCastMessage.class" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/plugin/ScreenCastPlugin.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/plugin/ScreenCastPlugin.class" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi.screenCast.iml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi.screenCast.iml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/GUI/ScreenCastMenu.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/GUI/ScreenCastMenu.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/GUI/ScreenCastWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/GUI/ScreenCastWindow.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/net/ScreenCastMessage.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/net/ScreenCastMessage.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/plugin/ScreenCastPlugin.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/plugin/ScreenCastPlugin.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/control/plugins/PluginManager.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/control/plugins/PluginManager.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/SocketTCP.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/SocketTCP.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/TcpConnection.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/TcpConnection.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="FileTemplateManagerImpl">
|
|
||||||
<option name="RECENT_TEMPLATES">
|
|
||||||
<list>
|
|
||||||
<option value="Class" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="Git.Settings">
|
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectColorInfo">{
|
|
||||||
"associatedIndex": 6
|
|
||||||
}</component>
|
|
||||||
<component name="ProjectId" id="3K7MYfvmfmI7sWODbOakShpPyuR" />
|
|
||||||
<component name="ProjectViewState">
|
|
||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
|
||||||
<option name="showLibraryContents" value="true" />
|
|
||||||
</component>
|
|
||||||
<component name="PropertiesComponent">{
|
|
||||||
"keyToString": {
|
|
||||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
|
||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
|
||||||
"git-widget-placeholder": "update/mpChat",
|
|
||||||
"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.mpChat",
|
|
||||||
"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="SharedIndexes">
|
|
||||||
<attachedChunks>
|
|
||||||
<set>
|
|
||||||
<option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.28539.97" />
|
|
||||||
</set>
|
|
||||||
</attachedChunks>
|
|
||||||
</component>
|
|
||||||
<component name="TaskManager">
|
|
||||||
<task active="true" id="Default" summary="Default task">
|
|
||||||
<changelist id="e2cdacc6-69cd-4190-bef1-27719e32140a" name="Changes" comment="" />
|
|
||||||
<created>1790904201925</created>
|
|
||||||
<option name="number" value="Default" />
|
|
||||||
<option name="presentableId" value="Default" />
|
|
||||||
<updated>1790904201925</updated>
|
|
||||||
</task>
|
|
||||||
<servers />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-8
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/pitiupi.screenCast.iml" filepath="$PROJECT_DIR$/.idea/pitiupi.screenCast.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<?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$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
Generated
+18
-16
@@ -5,9 +5,10 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="083e72ce-fa9b-4ddf-a0ef-24105aa3eabb" name="Changes" comment="">
|
<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 afterPath="$PROJECT_DIR$/../pitiupi.testSocket/Makefile" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/vcs.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../pitiupi/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/.idea/misc.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/ConnectionFactory.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/ConnectionFactory.java" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -33,7 +34,7 @@
|
|||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
"codeWithMe.voiceChat.enabledByDefault": "false",
|
||||||
"git-widget-placeholder": "update/screenCast",
|
"git-widget-placeholder": "feature/separacao-gerenciadores",
|
||||||
"ignore.virus.scanning.warn.message": "true",
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
"kotlin-language-version-configured": "true",
|
"kotlin-language-version-configured": "true",
|
||||||
"last_opened_file_path": "C:/Users/Gustavo/OneDrive/Documentos/Faculdade/2026_2/Monitoria/pitiupi/pitiupi.screenCast",
|
"last_opened_file_path": "C:/Users/Gustavo/OneDrive/Documentos/Faculdade/2026_2/Monitoria/pitiupi/pitiupi.screenCast",
|
||||||
@@ -44,17 +45,6 @@
|
|||||||
}
|
}
|
||||||
}]]></component>
|
}]]></component>
|
||||||
<component name="RunManager">
|
<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">
|
<configuration default="true" type="PythonConfigurationType" factoryName="Python">
|
||||||
<module name="pitiupi.screenCast" />
|
<module name="pitiupi.screenCast" />
|
||||||
<option name="ENV_FILES" value="" />
|
<option name="ENV_FILES" value="" />
|
||||||
@@ -89,11 +79,23 @@
|
|||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
<method v="2" />
|
<method v="2" />
|
||||||
</configuration>
|
</configuration>
|
||||||
|
<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>
|
||||||
</component>
|
</component>
|
||||||
<component name="SharedIndexes">
|
<component name="SharedIndexes">
|
||||||
<attachedChunks>
|
<attachedChunks>
|
||||||
<set>
|
<set>
|
||||||
<option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.28539.97" />
|
<option value="bundled-jdk-30f59d01ecdd-37e91769500f-intellij.indexing.shared.core-IU-261.26222.65" />
|
||||||
|
<option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-IU-261.26222.65" />
|
||||||
</set>
|
</set>
|
||||||
</attachedChunks>
|
</attachedChunks>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#Wed, 07 Oct 2026 15:36:37 -0300
|
#Tue, 29 Sep 2026 11:04:58 -0300
|
||||||
|
|
||||||
|
|
||||||
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi.screenCast=
|
C\:\\Users\\Gustavo\\OneDrive\\Documentos\\Faculdade\\2026_2\\Monitoria\\pitiupi\\pitiupi.screenCast=
|
||||||
|
|
||||||
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi=
|
C\:\\Users\\Gustavo\\OneDrive\\Documentos\\Faculdade\\2026_2\\Monitoria\\pitiupi\\pitiupi=
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Generated
-5
@@ -1,5 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<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
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/pitiupi.screenCast.iml" filepath="$PROJECT_DIR$/pitiupi.screenCast.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
File renamed without changes.
@@ -6,7 +6,6 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="module-library">
|
<orderEntry type="module-library">
|
||||||
<library>
|
<library>
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
@@ -16,5 +15,6 @@
|
|||||||
<SOURCES />
|
<SOURCES />
|
||||||
</library>
|
</library>
|
||||||
</orderEntry>
|
</orderEntry>
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -4,29 +4,28 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.screenCast.GUI;
|
package pitiupi.screenCast.GUI;
|
||||||
|
|
||||||
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;
|
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
import javax.swing.JMenu;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public class ScreenCastMenu extends MulticastActionMenu {
|
public class ScreenCastMenu extends JMenu {
|
||||||
|
|
||||||
private final ScreenCastWindow screenCastWindow;
|
private final ScreenCastWindow screenCastWindow;
|
||||||
|
private final javax.swing.JMenuItem menuItem;
|
||||||
|
|
||||||
public ScreenCastMenu(ScreenCastWindow screenCastWindow) {
|
public ScreenCastMenu(ScreenCastWindow screenCastWindow) {
|
||||||
this.screenCastWindow = screenCastWindow;
|
this.screenCastWindow = screenCastWindow;
|
||||||
|
this.setText("Screen Cast");
|
||||||
|
menuItem = new javax.swing.JMenuItem();
|
||||||
|
menuItem.setText("ScreenCast Window");
|
||||||
|
menuItem.addActionListener(this::chatMenuActionPerformed);
|
||||||
|
this.add(menuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void chatMenuActionPerformed(ActionEvent ae) {
|
||||||
public void onClick() {
|
|
||||||
this.screenCastWindow.setVisible(true);
|
this.screenCastWindow.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getText() {
|
|
||||||
return "Screen Cast";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,6 @@ package pitiupi.screenCast.GUI;
|
|||||||
|
|
||||||
import pitiupi.control.ConnectionManager;
|
import pitiupi.control.ConnectionManager;
|
||||||
import pitiupi.control.GuiManager;
|
import pitiupi.control.GuiManager;
|
||||||
import pitiupi.plugin.Pitiupi;
|
|
||||||
import pitiupi.screenCast.control.AlarmClock;
|
import pitiupi.screenCast.control.AlarmClock;
|
||||||
import pitiupi.screenCast.control.ImageTransformation;
|
import pitiupi.screenCast.control.ImageTransformation;
|
||||||
import pitiupi.screenCast.control.ScreenShot;
|
import pitiupi.screenCast.control.ScreenShot;
|
||||||
@@ -23,6 +22,8 @@ import java.util.logging.Logger;
|
|||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import pitiupi.net.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -39,11 +40,13 @@ public class ScreenCastWindow extends javax.swing.JFrame {
|
|||||||
public final static int TIME = 1000;
|
public final static int TIME = 1000;
|
||||||
|
|
||||||
private float compressRatio;
|
private float compressRatio;
|
||||||
|
private final ConnectionManager connectionManager;
|
||||||
|
private final GuiManager guiManager;
|
||||||
|
|
||||||
private final Pitiupi pitiupi;
|
public ScreenCastWindow(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
|
this.guiManager = guiManager;
|
||||||
|
|
||||||
public ScreenCastWindow(Pitiupi pitiupi) {
|
|
||||||
this.pitiupi = pitiupi;
|
|
||||||
this.compressRatio = 0.5f;
|
this.compressRatio = 0.5f;
|
||||||
this.initComponents();
|
this.initComponents();
|
||||||
}
|
}
|
||||||
@@ -93,9 +96,9 @@ public class ScreenCastWindow extends javax.swing.JFrame {
|
|||||||
BufferedImage image = ScreenShot.takeAShot();
|
BufferedImage image = ScreenShot.takeAShot();
|
||||||
BufferedImage rescaled = ImageTransformation.resize(image, 1024, 768);
|
BufferedImage rescaled = ImageTransformation.resize(image, 1024, 768);
|
||||||
byte[] byteImage = ImageTransformation.compress(rescaled, compressRatio);
|
byte[] byteImage = ImageTransformation.compress(rescaled, compressRatio);
|
||||||
ScreenCastMessage message = new ScreenCastMessage(pitiupi.getUserName(), byteImage);
|
ScreenCastMessage message = new ScreenCastMessage(this.connectionManager.getUserName(), byteImage);
|
||||||
|
|
||||||
if (!pitiupi.sendUdpMulticast(message)) this.compressRatio -= 0.1f;
|
if (!connectionManager.sendUdpMulticast(message)) this.compressRatio -= 0.1f;
|
||||||
} catch (AWTException | IOException ex) {
|
} catch (AWTException | IOException ex) {
|
||||||
Logger.getLogger(AlarmClock.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(AlarmClock.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.screenCast.net;
|
package pitiupi.screenCast.net;
|
||||||
|
|
||||||
|
import pitiupi.net.Message;
|
||||||
import pitiupi.models.Messages.Message;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,12 +4,14 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.screenCast.plugin;
|
package pitiupi.screenCast.plugin;
|
||||||
|
|
||||||
import java.util.List;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import pitiupi.models.Menus.MenuCategory;
|
import java.io.ObjectInputStream;
|
||||||
import pitiupi.models.Messages.Message;
|
import pitiupi.GUI.MainWindow;
|
||||||
import pitiupi.models.Plugin;
|
import pitiupi.control.ConnectionManager;
|
||||||
import pitiupi.plugin.Pitiupi;
|
import pitiupi.control.GuiManager;
|
||||||
|
import pitiupi.net.Message;
|
||||||
|
import pitiupi.plugin.Plugin;
|
||||||
import pitiupi.screenCast.GUI.ScreenCastMenu;
|
import pitiupi.screenCast.GUI.ScreenCastMenu;
|
||||||
import pitiupi.screenCast.GUI.ScreenCastWindow;
|
import pitiupi.screenCast.GUI.ScreenCastWindow;
|
||||||
import pitiupi.screenCast.net.ScreenCastMessage;
|
import pitiupi.screenCast.net.ScreenCastMessage;
|
||||||
@@ -18,7 +20,7 @@ import pitiupi.screenCast.net.ScreenCastMessage;
|
|||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public class ScreenCastPlugin extends Plugin {
|
public class ScreenCastPlugin implements Plugin {
|
||||||
|
|
||||||
private ScreenCastWindow screenCastWindow;
|
private ScreenCastWindow screenCastWindow;
|
||||||
|
|
||||||
@@ -43,20 +45,28 @@ public class ScreenCastPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createPlugin(Pitiupi pitiupi) {
|
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||||
this.screenCastWindow = new ScreenCastWindow(pitiupi);
|
this.screenCastWindow = new ScreenCastWindow(connectionManager, guiManager);
|
||||||
pitiupi.createMenu(MenuCategory.Streaming, new ScreenCastMenu(screenCastWindow));
|
guiManager.addMenu(new ScreenCastMenu(screenCastWindow));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Class<? extends Message>> getMessages() {
|
public void receiveMessage(byte[] message) {
|
||||||
return List.of(ScreenCastMessage.class);
|
ByteArrayInputStream bis = new ByteArrayInputStream(message);
|
||||||
}
|
ObjectInputStream ois;
|
||||||
|
try {
|
||||||
@Override
|
ois = new ObjectInputStream(bis);
|
||||||
public void receiveMessage(Message message) {
|
if (ois.readObject() instanceof ScreenCastMessage msg) {
|
||||||
if (message instanceof ScreenCastMessage msg) {
|
|
||||||
this.screenCastWindow.receiveScreenCastMessage(msg);
|
this.screenCastWindow.receiveScreenCastMessage(msg);
|
||||||
}
|
}
|
||||||
|
} catch (IOException | ClassNotFoundException ex) {
|
||||||
|
System.out.println(ex.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message getMessage() {
|
||||||
|
return new ScreenCastMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -66,15 +66,15 @@ A classe `Pitiupi` é a interface que o seu plugin vai receber quando for criado
|
|||||||
|
|
||||||
Envia uma mensagem multicast, retorna `boolean` indicando se teve sucesso.
|
Envia uma mensagem multicast, retorna `boolean` indicando se teve sucesso.
|
||||||
|
|
||||||
### boolean sendUdpUnicast(Message message, InetAddress receiverAddress)
|
### boolean sendUdpUnicast(Message message, InetAddress address)
|
||||||
|
|
||||||
Envia uma mensagem unicast via udp para o endereço fornecido, retorna `boolean` indicando se teve sucesso.
|
Envia uma mensagem unicast via udp para o endereço fornecido, retorna `boolean` indicando se teve sucesso.
|
||||||
|
|
||||||
### boolean sendTcp(Message message, InetAddress receiverAddress)
|
### boolean sendTcp(Message message, InetAddress address)
|
||||||
|
|
||||||
Envia uma mensagem unicast via tcp para o endereço fornecido, retorna `boolean` indicando se teve sucesso.
|
Envia uma mensagem unicast via tcp para o endereço fornecido, retorna `boolean` indicando se teve sucesso.
|
||||||
|
|
||||||
### <span style="color: red;">[REF]</span> TcpConnection connectTcp(InetAddress receiverAddress, String senderId, String receiverId)
|
### <span style="color: red;">[REF]</span> TcpConnection connectTcp(InetAddress address, String senderId, String receiverId)
|
||||||
|
|
||||||
Pede uma conexão tcp privada para o seu plugin com outro usuário, para utilizar essa funcionalidade é necessário implementar alguns métodos opcionais em Plugin. Maisinformações serão discutidas na seção TcpConnection.
|
Pede uma conexão tcp privada para o seu plugin com outro usuário, para utilizar essa funcionalidade é necessário implementar alguns métodos opcionais em Plugin. Maisinformações serão discutidas na seção TcpConnection.
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ Esse método é chamado quando o Pitiupi precisa entregar uma `TcpConnection` ao
|
|||||||
|
|
||||||
### <span style="color: red;">[REF]</span> receiveBytes(TcpConnection connection, byte[] bytes)
|
### <span style="color: red;">[REF]</span> receiveBytes(TcpConnection connection, byte[] bytes)
|
||||||
|
|
||||||
Esse método é chamado para entregar as mensagens recebidas por um `TcpConnection`, o tcpPitiupi no parametro indica qual a fonte da mensagem.
|
Esse método é chamado para entregar as mensagens recebidas por um `TcpConnection`, o tcpConnection no parametro indica qual a fonte da mensagem.
|
||||||
|
|
||||||
### <span style="color: red;">[REF]</span> onSocketClosed(TcpConnection connection)
|
### <span style="color: red;">[REF]</span> onSocketClosed(TcpConnection connection)
|
||||||
|
|
||||||
|
|||||||
@@ -700,7 +700,7 @@ is divided into following sections:
|
|||||||
<sequential>
|
<sequential>
|
||||||
<j2seproject3:junit excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
|
<j2seproject3:junit excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
|
||||||
<customize>
|
<customize>
|
||||||
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},receiverAddress=${jpda.receiverAddress}"/>
|
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},address=${jpda.address}"/>
|
||||||
<customizeDebuggee/>
|
<customizeDebuggee/>
|
||||||
</customize>
|
</customize>
|
||||||
</j2seproject3:junit>
|
</j2seproject3:junit>
|
||||||
@@ -843,7 +843,7 @@ is divided into following sections:
|
|||||||
<attribute default="${debug.classpath}" name="classpath"/>
|
<attribute default="${debug.classpath}" name="classpath"/>
|
||||||
<attribute default="" name="stopclassname"/>
|
<attribute default="" name="stopclassname"/>
|
||||||
<sequential>
|
<sequential>
|
||||||
<nbjpdastart addressproperty="jpda.receiverAddress" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
|
<nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
|
||||||
<modulepath>
|
<modulepath>
|
||||||
<path path="@{modulepath}"/>
|
<path path="@{modulepath}"/>
|
||||||
</modulepath>
|
</modulepath>
|
||||||
@@ -882,7 +882,7 @@ is divided into following sections:
|
|||||||
<sequential>
|
<sequential>
|
||||||
<j2seproject1:java classname="@{classname}" classpath="@{classpath}" modulename="@{modulename}" modulepath="@{modulepath}">
|
<j2seproject1:java classname="@{classname}" classpath="@{classpath}" modulename="@{modulename}" modulepath="@{modulepath}">
|
||||||
<customize>
|
<customize>
|
||||||
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},receiverAddress=${jpda.receiverAddress}"/>
|
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},address=${jpda.address}"/>
|
||||||
<customizeDebuggee/>
|
<customizeDebuggee/>
|
||||||
</customize>
|
</customize>
|
||||||
</j2seproject1:java>
|
</j2seproject1:java>
|
||||||
@@ -1434,7 +1434,7 @@ is divided into following sections:
|
|||||||
</nbprofiledirect>
|
</nbprofiledirect>
|
||||||
<j2seproject3:junit excludes="${excludes}" includes="${includes}" testincludes="${profile.class}" testmethods="">
|
<j2seproject3:junit excludes="${excludes}" includes="${includes}" testincludes="${profile.class}" testmethods="">
|
||||||
<customize>
|
<customize>
|
||||||
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},receiverAddress=${jpda.receiverAddress}"/>
|
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},address=${jpda.address}"/>
|
||||||
<env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
|
<env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
|
||||||
<jvmarg value="${profiler.info.jvmargs.agent}"/>
|
<jvmarg value="${profiler.info.jvmargs.agent}"/>
|
||||||
<jvmarg line="${profiler.info.jvmargs}"/>
|
<jvmarg line="${profiler.info.jvmargs}"/>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import pitiupi.control.plugins.PluginManager;
|
|||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Janela principal da aplicação.
|
* Janela principal da aplicação.
|
||||||
@@ -34,9 +33,9 @@ public class MainWindow extends JFrame {
|
|||||||
private JLabel status;
|
private JLabel status;
|
||||||
OnlineUsersPanel onlineUsersPanel;
|
OnlineUsersPanel onlineUsersPanel;
|
||||||
|
|
||||||
private ConnectionManager connectionManager;
|
private final ConnectionManager connectionManager;
|
||||||
private PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
private GuiManager guiManager;
|
private final GuiManager guiManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cria a janela principal da aplicação.
|
* Cria a janela principal da aplicação.
|
||||||
@@ -45,11 +44,10 @@ public class MainWindow extends JFrame {
|
|||||||
* @param port porta utilizada na comunicação multicast.
|
* @param port porta utilizada na comunicação multicast.
|
||||||
*/
|
*/
|
||||||
public MainWindow(String userName, int port) {
|
public MainWindow(String userName, int port) {
|
||||||
try {
|
|
||||||
initMenu();
|
initMenu();
|
||||||
|
|
||||||
pluginManager = new PluginManager();
|
pluginManager = new PluginManager();
|
||||||
connectionManager = new ConnectionManager(userName, pluginManager, port);
|
connectionManager = new ConnectionManager(this, userName, pluginManager, port);
|
||||||
|
|
||||||
initUserPanel();
|
initUserPanel();
|
||||||
initStatusPanel();
|
initStatusPanel();
|
||||||
@@ -62,18 +60,6 @@ public class MainWindow extends JFrame {
|
|||||||
|
|
||||||
this.setSize(new java.awt.Dimension(1024, 768));
|
this.setSize(new java.awt.Dimension(1024, 768));
|
||||||
}
|
}
|
||||||
catch (UnknownHostException e) {
|
|
||||||
JOptionPane.showMessageDialog(
|
|
||||||
null,
|
|
||||||
"Ocorreu um erro abrindo a conexão TCP, verifique sua rede. \n" +
|
|
||||||
"Se o problema persistir entre em contato com a equipe do Pitiupi.",
|
|
||||||
"Erro na conexão TCP",
|
|
||||||
JOptionPane.ERROR_MESSAGE
|
|
||||||
);
|
|
||||||
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initMenu() {
|
private void initMenu() {
|
||||||
menuBar = new MenuBar(this);
|
menuBar = new MenuBar(this);
|
||||||
|
|||||||
@@ -2,58 +2,46 @@ package pitiupi.control;
|
|||||||
|
|
||||||
import pitiupi.GUI.MainWindow;
|
import pitiupi.GUI.MainWindow;
|
||||||
import pitiupi.control.plugins.PluginManager;
|
import pitiupi.control.plugins.PluginManager;
|
||||||
import pitiupi.models.ConnectionData;
|
|
||||||
import pitiupi.models.Messages.HeartbeatMessage;
|
import pitiupi.models.Messages.HeartbeatMessage;
|
||||||
import pitiupi.models.Messages.Message;
|
import pitiupi.models.Messages.Message;
|
||||||
import pitiupi.net.tcp.TcpClient;
|
import pitiupi.net.tcp.SocketTCP;
|
||||||
import pitiupi.net.tcp.TcpManager;
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
import pitiupi.net.tcp.TcpServer;
|
|
||||||
import pitiupi.net.udp.SocketUDP;
|
import pitiupi.net.udp.SocketUDP;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
public class ConnectionManager {
|
public class ConnectionManager {
|
||||||
|
private final String pitiupiIdentifier;
|
||||||
|
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
|
|
||||||
private final HeartbeatManager heartbeatManager;
|
private final HeartbeatManager heartbeatManager;
|
||||||
|
|
||||||
private TcpManager tcpManager;
|
private SocketTCP socketTCP;
|
||||||
private SocketUDP socketUDP;
|
private SocketUDP socketUDP;
|
||||||
private String username;
|
private String username;
|
||||||
private final InetAddress address;
|
|
||||||
private int port;
|
private int port;
|
||||||
|
|
||||||
public ConnectionManager(String username, PluginManager pluginManager, int port) throws UnknownHostException {
|
public ConnectionManager(MainWindow main, String username, PluginManager pluginManager, int port) {
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
|
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.address = InetAddress.getLocalHost();
|
|
||||||
this.port = port;
|
this.port = port;
|
||||||
|
|
||||||
|
pitiupiIdentifier = "Pitiupi.Alice.2026";
|
||||||
|
|
||||||
reconnect();
|
reconnect();
|
||||||
|
|
||||||
heartbeatManager = new HeartbeatManager(this);
|
heartbeatManager = new HeartbeatManager(this);
|
||||||
heartbeatManager.start();
|
heartbeatManager.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TcpClient connectToTcpServer(ConnectionData serverData, String pluginId) {
|
public TcpConnection connectPluginTcp(InetAddress destinationAdress, String senderId, String receiverId) {
|
||||||
return tcpManager.getClient(serverData, pluginId, username);
|
return socketTCP.createPrivateConnection(destinationAdress, senderId, receiverId);
|
||||||
}
|
|
||||||
|
|
||||||
public TcpServer getTcpServer(String serverName, String pluginId) {
|
|
||||||
return tcpManager.getServer(serverName, pluginId, username, address, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void closeTcpServer(TcpServer tcpServer) {
|
|
||||||
tcpManager.closeServer(tcpServer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean sendTcp(Message message, InetAddress destinationAddress) {
|
public boolean sendTcp(Message message, InetAddress destinationAddress) {
|
||||||
return tcpManager.send(message, destinationAddress, port);
|
return socketTCP.send(message, destinationAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean sendUdpUnicast(Message message, InetAddress destinationAddress) {
|
public boolean sendUdpUnicast(Message message, InetAddress destinationAddress) {
|
||||||
@@ -75,7 +63,7 @@ public class ConnectionManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receiveMessage(byte[] message) {
|
public void broadcastLocal(byte[] message) {
|
||||||
pluginManager.handleMessage(message);
|
pluginManager.handleMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,34 +84,29 @@ public class ConnectionManager {
|
|||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getIdentifier(){
|
||||||
|
return this.pitiupiIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
private void reconnect() {
|
private void reconnect() {
|
||||||
try {
|
|
||||||
if (this.socketUDP != null) {
|
if (this.socketUDP != null) {
|
||||||
this.socketUDP.close();
|
this.socketUDP.close();
|
||||||
}
|
}
|
||||||
if (this.tcpManager != null) {
|
if (this.socketTCP != null) {
|
||||||
this.tcpManager.closeManager();
|
this.socketTCP.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.socketUDP = new SocketUDP(this, pluginManager);
|
this.socketUDP = new SocketUDP(this, pluginManager);
|
||||||
this.tcpManager = new TcpManager(port, this::receiveMessage);
|
this.socketTCP = new SocketTCP(this, pluginManager);
|
||||||
|
|
||||||
this.socketUDP.start();
|
this.socketUDP.start();
|
||||||
this.tcpManager.start();
|
this.socketTCP.start();
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
Logger.getLogger(ConnectionManager.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUserName() {
|
public String getUserName() {
|
||||||
return this.username;
|
return this.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InetAddress getAddress() {
|
|
||||||
return this.address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUserName(String userName) {
|
public void setUserName(String userName) {
|
||||||
this.username = userName;
|
this.username = userName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import pitiupi.Main;
|
|||||||
import pitiupi.control.ConnectionManager;
|
import pitiupi.control.ConnectionManager;
|
||||||
import pitiupi.control.GuiManager;
|
import pitiupi.control.GuiManager;
|
||||||
import pitiupi.models.Messages.Message;
|
import pitiupi.models.Messages.Message;
|
||||||
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
import pitiupi.models.Plugin;
|
import pitiupi.models.Plugin;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -66,6 +67,29 @@ public class PluginManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void handleMessageFromConnection(TcpConnection connection, byte[] msg) {
|
||||||
|
Plugin plugin = getPlugin(connection.getReceiver());
|
||||||
|
if (plugin == null) return;
|
||||||
|
|
||||||
|
plugin.receiveBytes(connection, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean handleConnection(TcpConnection connection, String receiverId) {
|
||||||
|
Plugin plugin = getPlugin(receiverId);
|
||||||
|
if (plugin == null) return false;
|
||||||
|
|
||||||
|
plugin.receivePrivateConnection(connection);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleDisconnection(TcpConnection connection, String receiverId) {
|
||||||
|
Plugin plugin = getPlugin(receiverId);
|
||||||
|
if (plugin == null) return;
|
||||||
|
|
||||||
|
plugin.onSocketClosed(connection);
|
||||||
|
}
|
||||||
|
|
||||||
public void loadPlugins(ConnectionManager connectionManager, GuiManager guiManager) {
|
public void loadPlugins(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||||
File pluginDir = new File("plugins");
|
File pluginDir = new File("plugins");
|
||||||
File[] jarFiles = pluginDir.listFiles((dir, name) -> name.endsWith(".jar"));
|
File[] jarFiles = pluginDir.listFiles((dir, name) -> name.endsWith(".jar"));
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
package pitiupi.models;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public final class ConnectionData implements Serializable {
|
|
||||||
private final UUID uuid;
|
|
||||||
private final int port;
|
|
||||||
|
|
||||||
private final String connectionName;
|
|
||||||
|
|
||||||
private final String ownerName;
|
|
||||||
private final InetAddress ownerAddress;
|
|
||||||
private final String pluginId;
|
|
||||||
private final boolean isServer;
|
|
||||||
|
|
||||||
public ConnectionData(String connectionName, String ownerName, InetAddress ownerAddress, String pluginId, int port, boolean isServer) {
|
|
||||||
uuid = UUID.randomUUID();
|
|
||||||
|
|
||||||
this.port = port;
|
|
||||||
this.connectionName = connectionName;
|
|
||||||
this.ownerName = ownerName;
|
|
||||||
this.ownerAddress = ownerAddress;
|
|
||||||
this.pluginId = pluginId;
|
|
||||||
this.isServer = isServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public UUID getUuid() {
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getConnectionName() {
|
|
||||||
return connectionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOwnerName() {
|
|
||||||
return ownerName;
|
|
||||||
}
|
|
||||||
public InetAddress getOwnerAddress() {
|
|
||||||
return ownerAddress;
|
|
||||||
}
|
|
||||||
public String getPluginId() {
|
|
||||||
return pluginId;
|
|
||||||
}
|
|
||||||
public boolean isServer() {
|
|
||||||
return isServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
ConnectionData that = (ConnectionData) o;
|
|
||||||
|
|
||||||
if (port != that.port) return false;
|
|
||||||
if (!uuid.equals(that.uuid)) return false;
|
|
||||||
if (!connectionName.equals(that.connectionName)) return false;
|
|
||||||
if (!Objects.equals(ownerName, that.ownerName)) return false;
|
|
||||||
if (!Objects.equals(ownerAddress, that.ownerAddress)) return false;
|
|
||||||
if (!Objects.equals(pluginId, that.pluginId)) return false;
|
|
||||||
return isServer == that.isServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(
|
|
||||||
uuid,
|
|
||||||
port,
|
|
||||||
connectionName,
|
|
||||||
ownerName,
|
|
||||||
ownerAddress,
|
|
||||||
pluginId,
|
|
||||||
isServer
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return connectionName + ": " + pluginId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package pitiupi.models.Messages;
|
||||||
|
|
||||||
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class InfoMessage extends Message {
|
||||||
|
private final InfoType type;
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
public InfoMessage(String message, InfoType type) {
|
||||||
|
this.message = message;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InfoType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendSystemInfo(TcpConnection connection, InfoType type, String description) {
|
||||||
|
try {
|
||||||
|
InfoMessage error = new InfoMessage(description, type);
|
||||||
|
connection.send(error.toByteArray());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.getLogger(InfoMessage.class.getName()).log(Level.WARNING, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InfoMessage parseSystemInfo(byte[] bytes) {
|
||||||
|
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||||
|
ObjectInputStream ois = new ObjectInputStream(bis)) {
|
||||||
|
|
||||||
|
Object object = ois.readObject();
|
||||||
|
if (object instanceof InfoMessage) {
|
||||||
|
return (InfoMessage) object;
|
||||||
|
}
|
||||||
|
} catch (IOException | ClassNotFoundException ex) {
|
||||||
|
Logger.getLogger(InfoMessage.class.getName()).log(Level.WARNING, null, ex);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package pitiupi.models.Messages;
|
||||||
|
|
||||||
|
public enum InfoType {
|
||||||
|
CONNECTION_REJECTED,
|
||||||
|
RECEIVER_NOT_FOUND,
|
||||||
|
MESSAGE_REJECTED,
|
||||||
|
INTERNAL_ERROR,
|
||||||
|
CONNECTION_CLOSED,
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
package pitiupi.models;
|
package pitiupi.models;
|
||||||
import pitiupi.plugin.Pitiupi;
|
import pitiupi.plugin.Pitiupi;
|
||||||
import pitiupi.models.Messages.Message;
|
import pitiupi.models.Messages.Message;
|
||||||
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -49,5 +50,13 @@ public abstract class Plugin {
|
|||||||
public abstract void receiveMessage(Message message);
|
public abstract void receiveMessage(Message message);
|
||||||
|
|
||||||
public boolean receiveAllTypes() {return false;}
|
public boolean receiveAllTypes() {return false;}
|
||||||
|
public boolean receivePrivateConnection(TcpConnection connection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public boolean receiveBytes(TcpConnection connection, byte[] bytes) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public boolean onSocketClosed(TcpConnection connection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
package pitiupi.net.tcp;
|
||||||
|
|
||||||
|
import pitiupi.models.Messages.Message;
|
||||||
|
import pitiupi.models.Messages.InfoMessage;
|
||||||
|
import pitiupi.models.Messages.InfoType;
|
||||||
|
|
||||||
|
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 = 1000;
|
||||||
|
private static final int STANDARD_TIMEOUT = 300_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria uma conexão TCP de saída com o peer de destino e realiza o handshake
|
||||||
|
* necessário para validá-la.
|
||||||
|
*
|
||||||
|
* <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) {
|
||||||
|
InfoMessage sysInfo = InfoMessage.parseSystemInfo(bytes);
|
||||||
|
if (sysInfo != null) return null;
|
||||||
|
|
||||||
|
|
||||||
|
if (message instanceof HandshakeMessage handshake) {
|
||||||
|
return (validateHandshake(handshake, connection)) ? handshake : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoMessage.sendSystemInfo(
|
||||||
|
connection,
|
||||||
|
InfoType.CONNECTION_REJECTED,
|
||||||
|
"The message was not from an allowed type."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (SocketTimeoutException ex) {
|
||||||
|
InfoMessage.sendSystemInfo(
|
||||||
|
connection,
|
||||||
|
InfoType.CONNECTION_REJECTED,
|
||||||
|
"No handshake was received within the allowed time."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException | IOException ex) {
|
||||||
|
Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
|
||||||
|
InfoMessage.sendSystemInfo(
|
||||||
|
connection,
|
||||||
|
InfoType.INTERNAL_ERROR,
|
||||||
|
"The server could not process the handshake."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valida os identificadores presentes em uma mensagem de handshake.
|
||||||
|
*
|
||||||
|
* <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()) {
|
||||||
|
InfoMessage.sendSystemInfo(
|
||||||
|
connection,
|
||||||
|
InfoType.CONNECTION_REJECTED,
|
||||||
|
"Invalid Sender: " + sender + "."
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (receiver == null || receiver.isBlank()) {
|
||||||
|
InfoMessage.sendSystemInfo(
|
||||||
|
connection,
|
||||||
|
InfoType.CONNECTION_REJECTED,
|
||||||
|
"Invalid Receiver: " + receiver + "."
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package pitiupi.net.tcp;
|
||||||
|
|
||||||
|
import pitiupi.models.Messages.Message;
|
||||||
|
|
||||||
|
class HandshakeMessage extends Message {
|
||||||
|
private final boolean isPrivate;
|
||||||
|
private final String sender;
|
||||||
|
private final String receiver;
|
||||||
|
|
||||||
|
|
||||||
|
public HandshakeMessage(boolean isPrivate, String sender, String receiver) {
|
||||||
|
this.isPrivate = isPrivate;
|
||||||
|
this.sender = sender;
|
||||||
|
this.receiver = receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrivate() {
|
||||||
|
return isPrivate;
|
||||||
|
}
|
||||||
|
public String getSender() {
|
||||||
|
return sender;
|
||||||
|
}
|
||||||
|
public String getReceiver() {
|
||||||
|
return receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HandshakeMessage createResponse() {
|
||||||
|
return new HandshakeMessage(isPrivate, receiver, sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,456 @@
|
|||||||
|
package pitiupi.net.tcp;
|
||||||
|
|
||||||
|
import pitiupi.control.ConnectionManager;
|
||||||
|
import pitiupi.control.plugins.PluginManager;
|
||||||
|
import pitiupi.models.Messages.Message;
|
||||||
|
import pitiupi.models.Messages.InfoMessage;
|
||||||
|
import pitiupi.models.Messages.InfoType;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gerencia a comunicação TCP da aplicação.
|
||||||
|
*
|
||||||
|
* <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 final PluginManager pluginManager;
|
||||||
|
|
||||||
|
private final TcpConnectionManager tcpConnectionManager;
|
||||||
|
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
private InetAddress address;
|
||||||
|
|
||||||
|
private volatile boolean running = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria o servidor TCP da aplicação.
|
||||||
|
*
|
||||||
|
* <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 connectionManager interface de conexão da aplicação, usada para obter a porta,
|
||||||
|
* o identificador local e os plugins disponíveis.
|
||||||
|
*/
|
||||||
|
public SocketTCP(ConnectionManager connectionManager, PluginManager pluginManager) {
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
|
||||||
|
logger = Logger.getLogger(getClass().getName());
|
||||||
|
tcpConnectionManager = new TcpConnectionManager();
|
||||||
|
|
||||||
|
try {
|
||||||
|
serverSocket = new ServerSocket(connectionManager.getPort());
|
||||||
|
address = InetAddress.getByName(InetAddress.getLocalHost().getHostAddress());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.out.println("There is no socket connection. Sorry.");
|
||||||
|
System.out.println(ex.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envia uma mensagem por meio de uma conexão TCP pública com o peer de destino.
|
||||||
|
*
|
||||||
|
* <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 boolean send(Message msg, InetAddress destinationAddress) {
|
||||||
|
try {
|
||||||
|
TcpConnection connection = getPublicConnection(
|
||||||
|
destinationAddress,
|
||||||
|
connectionManager.getIdentifier(),
|
||||||
|
connectionManager.getIdentifier()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (connection == null) {
|
||||||
|
System.out.println("Could not establish TCP connection.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
connection.send(msg.toByteArray());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
System.out.println("Could not send TCP message.");
|
||||||
|
System.out.println(ex.toString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estabelece uma conexão TCP privada com um plugin específico do peer de destino.
|
||||||
|
*
|
||||||
|
* <p>A conexão é criada como privada e passa a ser gerenciada pelo
|
||||||
|
* {@link TcpConnectionManager}. O método também inicia a tarefa responsável
|
||||||
|
* por receber os dados dessa conexão.</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 TcpConnectionManager}.
|
||||||
|
* O método primeiro verifica se já existe uma conexão pública registrada
|
||||||
|
* para o endereço de destino e, caso ela exista e não esteja fechada,
|
||||||
|
* reutiliza-a. Caso contrário, uma nova conexão pública é estabelecida,
|
||||||
|
* registrada no {@link TcpConnectionManager} e disponibilizada para uso.</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 = tcpConnectionManager.getPublicConnection(destinationAddress);
|
||||||
|
if (connection != null && !connection.isClosed()) return connection;
|
||||||
|
|
||||||
|
connection = establishOutgoingConnection(destinationAddress, senderId, receiverId, false);
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aguarda e aceita novas conexões TCP.
|
||||||
|
*
|
||||||
|
* <p>Cada socket aceito é encaminhado para processamento assíncrono,
|
||||||
|
* interpretado como uma {@link TcpConnection}, registrado pelo
|
||||||
|
* {@link TcpConnectionManager} 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, connectionManager.getPort(), sender, receiver, isPrivate);
|
||||||
|
if (connection == null) return null;
|
||||||
|
|
||||||
|
saveConnection(connection);
|
||||||
|
|
||||||
|
connectionExecutor.submit(() -> {
|
||||||
|
startReceiver(connection);
|
||||||
|
});
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agenda o processamento de uma conexão TCP recebida.
|
||||||
|
*
|
||||||
|
* <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()) {
|
||||||
|
tcpConnectionManager.addPrivateConnection(connection);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tcpConnectionManager.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(connectionManager.getIdentifier())) {
|
||||||
|
systemConnectionReceiver(connection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pluginManager.handleConnection(connection, connection.getReceiver()))
|
||||||
|
privateConnectionReceiver(connection);
|
||||||
|
else {
|
||||||
|
InfoMessage.sendSystemInfo(
|
||||||
|
connection,
|
||||||
|
InfoType.RECEIVER_NOT_FOUND,
|
||||||
|
"This peer does not have the plugin: " + connection.getReceiver()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processa continuamente as mensagens recebidas em uma conexão pública.
|
||||||
|
*
|
||||||
|
* <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 TcpConnectionManager} 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();
|
||||||
|
|
||||||
|
InfoMessage sysMessage = InfoMessage.parseSystemInfo(bytes);
|
||||||
|
|
||||||
|
if (sysMessage == null) {
|
||||||
|
pluginManager.handleMessage(bytes);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (sysMessage.getType()) {
|
||||||
|
case CONNECTION_CLOSED, INTERNAL_ERROR -> {
|
||||||
|
tcpConnectionManager.closeConnection(connection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case MESSAGE_REJECTED -> {
|
||||||
|
logger.log(Level.SEVERE, "Somehow sent something that was not an Message");
|
||||||
|
}
|
||||||
|
default -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketTimeoutException ex) {
|
||||||
|
if (connection.hasTimedOut()) {
|
||||||
|
tcpConnectionManager.closeConnection(connection, "Connection timeout");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
tcpConnectionManager.closeConnection(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processa os dados recebidos em uma conexão privada e os encaminha
|
||||||
|
* ao plugin destinatário.
|
||||||
|
*
|
||||||
|
* <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.
|
||||||
|
*/
|
||||||
|
private void privateConnectionReceiver(TcpConnection connection) {
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
byte[] message = connection.receive();
|
||||||
|
InfoMessage sysMessage = InfoMessage.parseSystemInfo(message);
|
||||||
|
|
||||||
|
if (sysMessage == null) {
|
||||||
|
pluginManager.handleMessageFromConnection(connection, message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (sysMessage.getType()) {
|
||||||
|
case CONNECTION_CLOSED, INTERNAL_ERROR -> {
|
||||||
|
pluginManager.handleDisconnection(connection, connection.getReceiver());
|
||||||
|
tcpConnectionManager.closeConnection(connection);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketTimeoutException ex) {
|
||||||
|
if (connection.hasTimedOut()) {
|
||||||
|
tcpConnectionManager.closeConnection(connection, "Connection timeout");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
tcpConnectionManager.closeConnection(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processa mensagens recebidas em uma conexão destinada ao próprio sistema.
|
||||||
|
*
|
||||||
|
* <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)) {
|
||||||
|
|
||||||
|
InfoMessage sysMessage = InfoMessage.parseSystemInfo(message);
|
||||||
|
if (sysMessage != null) continue;
|
||||||
|
|
||||||
|
//TODO: Ainda não existem mensagens trocadas entre o sistema.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (SocketTimeoutException ex) {
|
||||||
|
if (connection.hasTimedOut()) return;
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
tcpConnectionManager.closeConnection(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encerra uma conexão TCP e registra o motivo informado.
|
||||||
|
*
|
||||||
|
* <p>Quando nenhuma descrição é fornecida, é utilizado um motivo padrão.
|
||||||
|
* O fechamento efetivo da conexão é delegado ao {@link TcpConnectionManager}.</p>
|
||||||
|
*
|
||||||
|
* @param connection conexão que será encerrada.
|
||||||
|
* @param description motivo do encerramento; pode ser {@code null}.
|
||||||
|
*/
|
||||||
|
public void disconnect(TcpConnection connection, String description) {
|
||||||
|
tcpConnectionManager.closeConnection(
|
||||||
|
connection,
|
||||||
|
Objects.requireNonNullElse(description, "No reason provided")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
receive();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encerra o servidor TCP e libera os recursos associados.
|
||||||
|
*
|
||||||
|
* <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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpConnectionManager.closeAllConnections("The peer must have closed the application or changed ports");
|
||||||
|
|
||||||
|
connectionExecutor.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package pitiupi.net.tcp;
|
|
||||||
|
|
||||||
import pitiupi.models.ConnectionData;
|
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.net.Socket;
|
|
||||||
|
|
||||||
public final class TcpClient extends TcpPitiupi {
|
|
||||||
private final ConnectionData clientData;
|
|
||||||
private final ConnectionData serverData;
|
|
||||||
|
|
||||||
TcpClient(
|
|
||||||
ConnectionData clientData,
|
|
||||||
ConnectionData serverData,
|
|
||||||
Socket socket,
|
|
||||||
DataInputStream input,
|
|
||||||
DataOutputStream output
|
|
||||||
) throws IllegalArgumentException {
|
|
||||||
|
|
||||||
super(socket, input, output);
|
|
||||||
|
|
||||||
if (clientData == null) throw new IllegalArgumentException("clientData is null");
|
|
||||||
if (serverData == null) throw new IllegalArgumentException("ServerData is null");
|
|
||||||
|
|
||||||
this.clientData = clientData;
|
|
||||||
this.serverData = serverData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionData getServerData() {
|
|
||||||
return serverData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionData getClientData() {
|
|
||||||
return clientData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,264 @@
|
|||||||
|
package pitiupi.net.tcp;
|
||||||
|
|
||||||
|
import pitiupi.models.Messages.InfoType;
|
||||||
|
import pitiupi.models.User;
|
||||||
|
import pitiupi.control.PeerListener;
|
||||||
|
import pitiupi.models.Messages.InfoMessage;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gerencia as conexões TCP estabelecidas com os peers da aplicação.
|
||||||
|
*
|
||||||
|
* <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 TcpConnectionManager 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 TcpConnectionManager() {
|
||||||
|
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(), e -> 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) {
|
||||||
|
InfoMessage.sendSystemInfo(connection, InfoType.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) {
|
||||||
|
InfoMessage.sendSystemInfo(connection, InfoType.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) {
|
||||||
|
InfoMessage.sendSystemInfo(connection, InfoType.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<User> peers) {
|
||||||
|
Set<InetAddress> activePeers = peers.stream().map(User::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
package pitiupi.net.tcp;
|
|
||||||
|
|
||||||
import pitiupi.models.ConnectionData;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
final class TcpFactory {
|
|
||||||
private static final int HANDSHAKE_TIMEOUT = 3000;
|
|
||||||
private static final int DISABLE_TIMEOUT = 0;
|
|
||||||
|
|
||||||
private final TcpManager manager;
|
|
||||||
|
|
||||||
TcpFactory(TcpManager manager) {
|
|
||||||
this.manager = manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
TcpPitiupi createPitiupiConnection(InetAddress receiverAddress, int port) {
|
|
||||||
Socket socket = new Socket();
|
|
||||||
try {
|
|
||||||
socket = new Socket(receiverAddress, port);
|
|
||||||
socket.connect(new InetSocketAddress(receiverAddress, port), HANDSHAKE_TIMEOUT);
|
|
||||||
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
|
|
||||||
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
|
|
||||||
|
|
||||||
sendHandshake(dataOutputStream);
|
|
||||||
socket.setSoTimeout(HANDSHAKE_TIMEOUT);
|
|
||||||
TcpHandshakeResponse response = receiveHandshakeResponse(dataInputStream);
|
|
||||||
socket.setSoTimeout(DISABLE_TIMEOUT);
|
|
||||||
|
|
||||||
if (response.getSucess()) return new TcpPitiupi(socket, dataInputStream, dataOutputStream);
|
|
||||||
}
|
|
||||||
catch (IOException | ClassNotFoundException e) {
|
|
||||||
Logger.getLogger(TcpFactory.class.getName()).log(Level.SEVERE, null, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
tryClose(socket);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
TcpClient createClient(ConnectionData serverData, String pluginIdentifier, String username, InetAddress receiverAddress, int port) {
|
|
||||||
Socket socket = null;
|
|
||||||
try {
|
|
||||||
socket = new Socket(receiverAddress, port);
|
|
||||||
socket.connect(new InetSocketAddress(receiverAddress, port), HANDSHAKE_TIMEOUT);
|
|
||||||
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
|
|
||||||
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
|
|
||||||
|
|
||||||
ConnectionData clientData = new ConnectionData(
|
|
||||||
"Connection to " + serverData.getConnectionName() + " (" + serverData.getOwnerAddress() + ")",
|
|
||||||
username,
|
|
||||||
receiverAddress,
|
|
||||||
pluginIdentifier,
|
|
||||||
port,
|
|
||||||
false);
|
|
||||||
|
|
||||||
sendHandshake(dataOutputStream, serverData, clientData);
|
|
||||||
socket.setSoTimeout(HANDSHAKE_TIMEOUT);
|
|
||||||
TcpHandshakeResponse response = receiveHandshakeResponse(dataInputStream);
|
|
||||||
socket.setSoTimeout(DISABLE_TIMEOUT);
|
|
||||||
|
|
||||||
if (response.getSucess()) return new TcpClient(clientData, serverData, socket, dataInputStream, dataOutputStream);
|
|
||||||
}
|
|
||||||
catch (IOException | ClassNotFoundException e) {
|
|
||||||
Logger.getLogger(TcpFactory.class.getName()).log(Level.SEVERE, null, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (socket != null) tryClose(socket);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
TcpServer createServer(String serverName, String pluginIdentifier, String username, InetAddress receiverAddress, int port) {
|
|
||||||
ConnectionData data = new ConnectionData(
|
|
||||||
serverName,
|
|
||||||
username,
|
|
||||||
receiverAddress,
|
|
||||||
pluginIdentifier,
|
|
||||||
port,
|
|
||||||
true);
|
|
||||||
|
|
||||||
return new TcpServer(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
TcpPitiupi createFromReceiver(Socket socket) throws IOException {
|
|
||||||
DataOutputStream dataOutputStream = null;
|
|
||||||
DataInputStream dataInputStream = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
dataOutputStream = new DataOutputStream(socket.getOutputStream());
|
|
||||||
dataInputStream = new DataInputStream(socket.getInputStream());
|
|
||||||
|
|
||||||
socket.setSoTimeout(HANDSHAKE_TIMEOUT);
|
|
||||||
TcpHandshake tcpHandshake = receiveHandshake(dataInputStream);
|
|
||||||
socket.setSoTimeout(DISABLE_TIMEOUT);
|
|
||||||
|
|
||||||
if (tcpHandshake.isPrivate()) {
|
|
||||||
ConnectionData clientData = tcpHandshake.getClientData();
|
|
||||||
ConnectionData serverData = tcpHandshake.getServerData();
|
|
||||||
|
|
||||||
if (manager.findServer(serverData) == null) {
|
|
||||||
sendHandshakeResponse(false, dataOutputStream);
|
|
||||||
tryClose(socket);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendHandshakeResponse(true, dataOutputStream);
|
|
||||||
return new TcpClient(clientData, serverData, socket, dataInputStream, dataOutputStream);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sendHandshakeResponse(true, dataOutputStream);
|
|
||||||
return new TcpPitiupi(socket, dataInputStream, dataOutputStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException | ClassNotFoundException e) {
|
|
||||||
Logger.getLogger(TcpFactory.class.getName()).log(Level.SEVERE, null, e);
|
|
||||||
try {
|
|
||||||
sendHandshakeResponse(false, dataOutputStream);
|
|
||||||
}
|
|
||||||
catch (IOException e1) {
|
|
||||||
Logger.getLogger(TcpFactory.class.getName()).log(Level.INFO, null, e1);
|
|
||||||
}
|
|
||||||
tryClose(socket);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendHandshake(DataOutputStream output, ConnectionData serverData, ConnectionData clientData) throws IOException {
|
|
||||||
TcpHandshake handshake = TcpHandshake.createPrivate(serverData, clientData);
|
|
||||||
byte[] message = handshake.toByteArray();
|
|
||||||
|
|
||||||
sendMessage(message, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendHandshake(DataOutputStream output) throws IOException {
|
|
||||||
TcpHandshake handshake = TcpHandshake.createPublic();
|
|
||||||
byte[] message = handshake.toByteArray();
|
|
||||||
|
|
||||||
sendMessage(message, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendHandshakeResponse(boolean success, DataOutputStream output) throws IOException {
|
|
||||||
TcpHandshakeResponse response = new TcpHandshakeResponse(success);
|
|
||||||
|
|
||||||
byte[] message = response.toByteArray();
|
|
||||||
sendMessage(message, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMessage(byte[] message, DataOutputStream output) throws IOException {
|
|
||||||
output.writeInt(message.length);
|
|
||||||
output.write(message);
|
|
||||||
output.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private TcpHandshake receiveHandshake(DataInputStream input) throws IOException, ClassNotFoundException {
|
|
||||||
byte[] msg = receiveMessage(input);
|
|
||||||
|
|
||||||
if (byteToObject(msg) instanceof TcpHandshake handshake) return handshake;
|
|
||||||
else throw new IOException("Not a handshake");
|
|
||||||
}
|
|
||||||
|
|
||||||
private TcpHandshakeResponse receiveHandshakeResponse(DataInputStream input) throws IOException, ClassNotFoundException {
|
|
||||||
byte[] msg = receiveMessage(input);
|
|
||||||
|
|
||||||
if (byteToObject(msg) instanceof TcpHandshakeResponse response) return response;
|
|
||||||
else throw new IOException("Not a handshake");
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] receiveMessage(DataInputStream input) throws IOException {
|
|
||||||
int length = input.readInt();
|
|
||||||
if (length < 0) throw new IOException("Invalid length");
|
|
||||||
|
|
||||||
byte[] msg = input.readNBytes(length);
|
|
||||||
if (msg.length != length) throw new IOException("Socket closed while receiving data");
|
|
||||||
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object byteToObject(byte[] bytes) throws IOException, ClassNotFoundException {
|
|
||||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
|
||||||
ObjectInput in = new ObjectInputStream(bais);
|
|
||||||
|
|
||||||
return in.readObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tryClose(Socket socket) {
|
|
||||||
try {
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
Logger.getLogger(TcpFactory.class.getName()).log(Level.WARNING, null, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package pitiupi.net.tcp;
|
|
||||||
|
|
||||||
import pitiupi.models.ConnectionData;
|
|
||||||
import pitiupi.models.Messages.Message;
|
|
||||||
|
|
||||||
final class TcpHandshake extends Message {
|
|
||||||
private final boolean isPrivate;
|
|
||||||
private final ConnectionData serverData;
|
|
||||||
private final ConnectionData clientData;
|
|
||||||
|
|
||||||
private TcpHandshake(boolean isPrivate, ConnectionData serverData, ConnectionData clientData) {
|
|
||||||
this.isPrivate = isPrivate;
|
|
||||||
this.serverData = serverData;
|
|
||||||
this.clientData = clientData;
|
|
||||||
}
|
|
||||||
|
|
||||||
static TcpHandshake createPrivate(ConnectionData serverData, ConnectionData clientData) {
|
|
||||||
return new TcpHandshake(true, serverData, clientData);
|
|
||||||
}
|
|
||||||
|
|
||||||
static TcpHandshake createPublic() {
|
|
||||||
return new TcpHandshake(false, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPrivate() {
|
|
||||||
return isPrivate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionData getServerData() {
|
|
||||||
return serverData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionData getClientData() {
|
|
||||||
return clientData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package pitiupi.net.tcp;
|
|
||||||
|
|
||||||
import pitiupi.models.Messages.Message;
|
|
||||||
|
|
||||||
class TcpHandshakeResponse extends Message {
|
|
||||||
private final boolean sucess;
|
|
||||||
|
|
||||||
TcpHandshakeResponse(boolean sucess) {
|
|
||||||
this.sucess = sucess;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getSucess() {
|
|
||||||
return sucess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
package pitiupi.net.tcp;
|
|
||||||
|
|
||||||
import pitiupi.models.ConnectionData;
|
|
||||||
import pitiupi.models.Messages.Message;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.net.*;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
public final class TcpManager extends Thread{
|
|
||||||
private final TcpFactory tcpFactory;
|
|
||||||
|
|
||||||
private final Map<ConnectionData, TcpServer> servers;
|
|
||||||
|
|
||||||
// TODO: Atualizar a identificação das conexões públicas quando
|
|
||||||
// a identificação de usuário/processo for revisada.
|
|
||||||
// Atualmente, o mapa permite apenas uma conexão pública por endereço IP.
|
|
||||||
private final Map<InetAddress, TcpPitiupi> connections;
|
|
||||||
private final List<TcpClient> clients;
|
|
||||||
private final ExecutorService connectionExecutor;
|
|
||||||
private final ServerSocket serverSocket;
|
|
||||||
|
|
||||||
private final Consumer<byte[]> handleMessage;
|
|
||||||
|
|
||||||
private volatile boolean isRunning;
|
|
||||||
|
|
||||||
public TcpManager(int port, Consumer<byte[]> handleMessage) throws IOException {
|
|
||||||
servers = new ConcurrentHashMap<>();
|
|
||||||
connections = new ConcurrentHashMap<>();
|
|
||||||
clients = new ArrayList<>();
|
|
||||||
|
|
||||||
tcpFactory = new TcpFactory(this);
|
|
||||||
|
|
||||||
connectionExecutor = Executors.newCachedThreadPool();
|
|
||||||
|
|
||||||
serverSocket = new ServerSocket(port);
|
|
||||||
this.handleMessage = handleMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TcpServer getServer(String serverName, String pluginIdentifier, String username, InetAddress userAddress, int port) {
|
|
||||||
TcpServer tcpServer = tcpFactory.createServer(serverName, pluginIdentifier, username, userAddress, port);
|
|
||||||
servers.put(tcpServer.getConnectionData(), tcpServer);
|
|
||||||
|
|
||||||
return tcpServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TcpClient getClient(ConnectionData serverData, String pluginIdentifier, String username) {
|
|
||||||
TcpClient tcpClient = tcpFactory.createClient(serverData, pluginIdentifier, username, serverData.getOwnerAddress(), serverData.getPort());
|
|
||||||
|
|
||||||
if (tcpClient == null) return null;
|
|
||||||
|
|
||||||
clients.add(tcpClient);
|
|
||||||
return tcpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TcpPitiupi createConnection(InetAddress receiverAddress, int port) {
|
|
||||||
TcpPitiupi tcpPitiupi = tcpFactory.createPitiupiConnection(receiverAddress, port);
|
|
||||||
|
|
||||||
if (tcpPitiupi == null) return null;
|
|
||||||
|
|
||||||
manageConnectionList(tcpPitiupi, receiverAddress);
|
|
||||||
startConnectionReceiver(tcpPitiupi);
|
|
||||||
|
|
||||||
return tcpPitiupi;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean send(Message message, InetAddress receiverAddress, int port) {
|
|
||||||
synchronized (connections) {
|
|
||||||
TcpPitiupi tcpPitiupi = findConnection(receiverAddress);
|
|
||||||
|
|
||||||
if (tcpPitiupi == null || tcpPitiupi.isClosed()) tcpPitiupi = createConnection(receiverAddress, port);
|
|
||||||
if (tcpPitiupi == null) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
tcpPitiupi.send(message.toByteArray());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, null, ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void receive() {
|
|
||||||
while (isRunning) {
|
|
||||||
Socket socket = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
socket = serverSocket.accept();
|
|
||||||
processConnection(socket);
|
|
||||||
}
|
|
||||||
catch (SocketException e) {
|
|
||||||
if (isRunning) {
|
|
||||||
Logger.getLogger(TcpManager.class.getName()).log(Level.SEVERE, "Erro no servidor TCP.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, "Erro ao aceitar conexão.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processConnection(Socket socket) {
|
|
||||||
connectionExecutor.submit(() -> {
|
|
||||||
try {
|
|
||||||
TcpPitiupi tcpPitiupi = tcpFactory.createFromReceiver(socket);
|
|
||||||
|
|
||||||
if (tcpPitiupi == null) return;
|
|
||||||
|
|
||||||
if (tcpPitiupi instanceof TcpClient client) {
|
|
||||||
ConnectionData serverData = client.getServerData();
|
|
||||||
|
|
||||||
TcpServer tcpServer;
|
|
||||||
|
|
||||||
tcpServer = servers.get(serverData);
|
|
||||||
|
|
||||||
clients.add(client);
|
|
||||||
tcpServer.addClient(client);
|
|
||||||
} else {
|
|
||||||
manageConnectionList(tcpPitiupi, socket.getInetAddress());
|
|
||||||
startConnectionReceiver(tcpPitiupi);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Logger.getLogger(TcpClient.class.getName()).log(Level.SEVERE, null, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void closeManager() {
|
|
||||||
try {
|
|
||||||
isRunning = false;
|
|
||||||
serverSocket.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, null, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TcpClient tcpClient : clients) {
|
|
||||||
try {
|
|
||||||
tcpClient.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, null, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TcpPitiupi tcpPitiupi : connections.values()) {
|
|
||||||
try {
|
|
||||||
tcpPitiupi.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, null, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
servers.clear();
|
|
||||||
clients.clear();
|
|
||||||
connections.clear();
|
|
||||||
|
|
||||||
connectionExecutor.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void closeServer(TcpServer tcpServer) {
|
|
||||||
ConnectionData serverData = tcpServer.getConnectionData();
|
|
||||||
if (serverData.isServer()) servers.remove(serverData);
|
|
||||||
|
|
||||||
tcpServer.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private TcpPitiupi findConnection(InetAddress receiverAddress) {
|
|
||||||
return connections.get(receiverAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TcpServer findServer(ConnectionData serverData) {
|
|
||||||
return servers.get(serverData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void manageConnectionList(TcpPitiupi tcpPitiupi, InetAddress receiverAddress) {
|
|
||||||
synchronized (connections) {
|
|
||||||
TcpPitiupi oldPitiupiConnection = connections.put(receiverAddress, tcpPitiupi);
|
|
||||||
if (oldPitiupiConnection != null) {
|
|
||||||
tcpFactory.tryClose(oldPitiupiConnection.getSocket());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startConnectionReceiver(TcpPitiupi tcpPitiupi) {
|
|
||||||
connectionExecutor.submit(() -> {
|
|
||||||
while (!tcpPitiupi.isClosed()) {
|
|
||||||
try {
|
|
||||||
handleMessage.accept(tcpPitiupi.receive());
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
Logger.getLogger(TcpManager.class.getName()).log(Level.SEVERE, null, e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
connections.remove(tcpPitiupi.getSocket().getInetAddress());
|
|
||||||
|
|
||||||
try {
|
|
||||||
tcpPitiupi.close();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
Logger.getLogger(TcpManager.class.getName()).log(Level.INFO, null, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
isRunning = true;
|
|
||||||
receive();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
package pitiupi.net.tcp;
|
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
class TcpPitiupi {
|
|
||||||
private static final int MAX_MESSAGE_SIZE = 1_048_576;
|
|
||||||
|
|
||||||
protected final Socket socket;
|
|
||||||
|
|
||||||
protected final DataInputStream input;
|
|
||||||
protected final DataOutputStream output;
|
|
||||||
|
|
||||||
protected boolean isClosed;
|
|
||||||
|
|
||||||
public TcpPitiupi(Socket socket, DataInputStream input, DataOutputStream output) {
|
|
||||||
this.socket = socket;
|
|
||||||
this.input = input;
|
|
||||||
this.output = output;
|
|
||||||
|
|
||||||
this.isClosed = socket.isClosed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 void send(byte[] message) throws IOException {
|
|
||||||
if (message.length > MAX_MESSAGE_SIZE) throw new IOException("Oversized message");
|
|
||||||
|
|
||||||
try {
|
|
||||||
output.writeInt(message.length);
|
|
||||||
output.write(message);
|
|
||||||
output.flush();
|
|
||||||
} catch (EOFException e) {
|
|
||||||
Logger.getLogger(TcpPitiupi.class.getName()).log(Level.INFO, null, e);
|
|
||||||
isClosed = true;
|
|
||||||
throw new IOException("Socket closed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recebe uma mensagem da conexão.
|
|
||||||
*
|
|
||||||
* <p>Primeiro lê o tamanho da mensagem e, em seguida, lê a quantidade
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
public byte[] receive() throws IOException {
|
|
||||||
try {
|
|
||||||
int length = input.readInt();
|
|
||||||
|
|
||||||
if (length < 0 || length > MAX_MESSAGE_SIZE) throw new IOException("Invalid message length.");
|
|
||||||
|
|
||||||
byte[] message = input.readNBytes(length);
|
|
||||||
|
|
||||||
if (message.length != length) {
|
|
||||||
Logger.getLogger(TcpPitiupi.class.getName()).log(Level.INFO, null, "Socket closed");
|
|
||||||
isClosed = true;
|
|
||||||
throw new IOException("Socket closed");
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
catch (EOFException e) {
|
|
||||||
Logger.getLogger(TcpPitiupi.class.getName()).log(Level.INFO, null, e);
|
|
||||||
isClosed = true;
|
|
||||||
throw new IOException("Socket closed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isClosed() {
|
|
||||||
if (socket.isClosed()) isClosed = true;
|
|
||||||
if (socket.isInputShutdown()) isClosed = true;
|
|
||||||
if (socket.isOutputShutdown()) isClosed = true;
|
|
||||||
|
|
||||||
return isClosed;
|
|
||||||
}
|
|
||||||
|
|
||||||
Socket getSocket() {
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() throws IOException {
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package pitiupi.net.tcp;
|
|
||||||
|
|
||||||
import pitiupi.models.ConnectionData;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
public final class TcpServer {
|
|
||||||
private final ConnectionData connectionData;
|
|
||||||
|
|
||||||
private final BlockingQueue<TcpClient> buffer;
|
|
||||||
private final Map<ConnectionData, TcpClient> clients;
|
|
||||||
|
|
||||||
TcpServer(ConnectionData connectionData) {
|
|
||||||
this.connectionData = connectionData;
|
|
||||||
|
|
||||||
this.buffer = new LinkedBlockingQueue<>();
|
|
||||||
this.clients = new ConcurrentHashMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void addClient(TcpClient tcpClient) {
|
|
||||||
buffer.add(tcpClient);
|
|
||||||
clients.put(tcpClient.getClientData(), tcpClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TcpClient receive() throws IOException {
|
|
||||||
try {
|
|
||||||
return buffer.take();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e) {
|
|
||||||
Logger.getLogger(TcpServer.class.getName()).log(Level.SEVERE, null, e);
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ConnectionData> getClients() {
|
|
||||||
return clients.keySet().stream().toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeClient(ConnectionData clientData) {
|
|
||||||
TcpClient client = clients.remove(clientData);
|
|
||||||
try {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
Logger.getLogger(TcpServer.class.getName()).log(Level.INFO, null, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionData getConnectionData() {
|
|
||||||
return connectionData;
|
|
||||||
}
|
|
||||||
|
|
||||||
void close() {
|
|
||||||
for (TcpClient tcpClient : clients.values()) {
|
|
||||||
try {
|
|
||||||
tcpClient.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Logger.getLogger(TcpServer.class.getName()).log(Level.WARNING, null, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.clear();
|
|
||||||
clients.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,12 +2,10 @@ package pitiupi.plugin;
|
|||||||
|
|
||||||
import pitiupi.control.ConnectionManager;
|
import pitiupi.control.ConnectionManager;
|
||||||
import pitiupi.control.GuiManager;
|
import pitiupi.control.GuiManager;
|
||||||
import pitiupi.models.ConnectionData;
|
|
||||||
import pitiupi.models.Menus.Menu;
|
import pitiupi.models.Menus.Menu;
|
||||||
import pitiupi.models.Menus.MenuCategory;
|
import pitiupi.models.Menus.MenuCategory;
|
||||||
import pitiupi.models.Messages.Message;
|
import pitiupi.models.Messages.Message;
|
||||||
import pitiupi.net.tcp.TcpClient;
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
import pitiupi.net.tcp.TcpServer;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
|
||||||
@@ -33,16 +31,8 @@ public class Pitiupi {
|
|||||||
return connectionManager.sendTcp(message, address);
|
return connectionManager.sendTcp(message, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TcpServer openTcpServer(String serverName, String pluginId) {
|
public TcpConnection connectTcp(InetAddress address, String senderId, String receiverId) {
|
||||||
return connectionManager.getTcpServer(serverName, pluginId);
|
return connectionManager.connectPluginTcp(address, senderId, receiverId);
|
||||||
}
|
|
||||||
|
|
||||||
public TcpClient connectToTcpServer(ConnectionData connectionData, String pluginId) {
|
|
||||||
return connectionManager.connectToTcpServer(connectionData, pluginId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void closeTcpServer(TcpServer tcpServer) {
|
|
||||||
connectionManager.closeTcpServer(tcpServer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUserName() {
|
public String getUserName() {
|
||||||
|
|||||||
Reference in new issue
Block a user