67 changed files with 1784 additions and 1233 deletions

No files matched your search

+1
View File
@@ -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
View File
@@ -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>
File renamed without changes.
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK"> <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" /> <output url="file://$PROJECT_DIR$/out" />
@@ -2,7 +2,7 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/pitiupi.screenCast.iml" filepath="$PROJECT_DIR$/pitiupi.screenCast.iml" /> <module fileurl="file://$PROJECT_DIR$/pitiupi.mpChat.iml" filepath="$PROJECT_DIR$/pitiupi.mpChat.iml" />
</modules> </modules>
</component> </component>
</project> </project>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" /> <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component> </component>
</project> </project>
-85
View File
@@ -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">{
&quot;associatedIndex&quot;: 6
}</component>
<component name="ProjectId" id="3K7MYfvmfmI7sWODbOakShpPyuR" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;update/mpChat&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/extre/OneDrive/Documentos/Faculdade/2026_02/Monitoria/Pitiupi/pitiupi.mpChat&quot;,
&quot;project.structure.last.edited&quot;: &quot;Modules&quot;,
&quot;project.structure.proportion&quot;: &quot;0.15&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.keymap&quot;
}
}</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>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-40
View File
@@ -1,40 +0,0 @@
package GUI;
import pitiupi.models.Menus.MenuCategory;
import pitiupi.models.Menus.MulticastMenus.MulticastSubMenu;
import plugin.MultiProtocolChat;
import javax.swing.*;
import java.awt.*;
public class ChatMenu extends MulticastSubMenu {
private final MultiProtocolChat multiProtocolChat;
public ChatMenu(MultiProtocolChat multiProtocolChat) {
this.multiProtocolChat = multiProtocolChat;
this.setText("MP-Chat");
JMenuItem startPrivateChat = new JMenuItem("start Private Chat");
startPrivateChat.addActionListener(e -> showPrivateChatDialog());
this.add(startPrivateChat);
}
public void newMenuItem(JMenuItem newMenuItem, ChatWindow chatWindow) {
newMenuItem.addActionListener(e -> chatWindow.setVisible(true));
this.add(newMenuItem);
}
public void removeMenuItem(JMenuItem removeMenuItem) {
this.remove(removeMenuItem);
}
private void showPrivateChatDialog() {
}
@Override
public String getText() {
return "MP-Chat";
}
}
-94
View File
@@ -1,94 +0,0 @@
package GUI;
import net.MpChatMessage;
import pitiupi.control.ConnectionManager;
import javax.swing.*;
import java.awt.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
public abstract class ChatWindow extends JFrame {
protected final ConnectionManager connectionManager;
private JTextArea receivedMessagesArea;
protected JTextField messageField;
public ChatWindow(ConnectionManager connectionManager) {
this.connectionManager = connectionManager;
initComponents();
}
private void initComponents() {
setTitle("Socket Test");
setSize(700, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
initReceivedMessagesPanel(mainPanel);
initBottomPanel(mainPanel);
setContentPane(mainPanel);
}
private void initReceivedMessagesPanel(JPanel mainPanel) {
receivedMessagesArea = new JTextArea();
receivedMessagesArea.setEditable(false);
receivedMessagesArea.setLineWrap(true);
receivedMessagesArea.setWrapStyleWord(true);
JScrollPane messagesScrollPane = new JScrollPane(receivedMessagesArea);
messagesScrollPane.setBorder(BorderFactory.createTitledBorder("Mensagens recebidas"));
mainPanel.add(messagesScrollPane, BorderLayout.CENTER);
}
private void initBottomPanel(JPanel mainPanel) {
messageField = new JTextField();
JPanel bottomPanel = new JPanel(new BorderLayout(10, 10));
bottomPanel.add(messageField, BorderLayout.CENTER);
initButtons(bottomPanel);
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
}
protected abstract void initButtons(JPanel bottomPanel);
protected MpChatMessage createMessage(String tipoMensagem) {
try {
String nome = connectionManager.getUserName();
String ip = InetAddress.getLocalHost().getHostAddress();
String message = messageField.getText();
return new MpChatMessage(nome, ip, tipoMensagem, message);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
public void receiveMessage(MpChatMessage msg) {
SwingUtilities.invokeLater(() -> {
String mensagem = String.format(
"[%s] %s (%s): %s%n",
msg.getTipoMensagem(),
msg.getNome(),
msg.getIp(),
msg.getMessage()
);
receivedMessagesArea.append(mensagem);
receivedMessagesArea.setCaretPosition(receivedMessagesArea.getDocument().getLength());
});
}
}
@@ -2,10 +2,10 @@ package GUI;
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu; import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;
public class PublicChatMenu extends MulticastActionMenu { public class MulticastMpChatMenu extends MulticastActionMenu {
PublicChatWindow publicChatWindow; PublicChatWindow publicChatWindow;
PublicChatMenu(PublicChatWindow publicChatWindow) { public MulticastMpChatMenu(PublicChatWindow publicChatWindow) {
this.publicChatWindow = publicChatWindow; this.publicChatWindow = publicChatWindow;
} }
@@ -14,8 +14,9 @@ public class PublicChatMenu extends MulticastActionMenu {
publicChatWindow.setVisible(true); publicChatWindow.setVisible(true);
} }
@Override @Override
public String getText() { public String getText() {
return ""; return "MP-Chat";
} }
} }
@@ -1,38 +0,0 @@
package GUI;
import control.ChatController;
import pitiupi.models.Menus.UnicastMenus.UnicastActionMenu;
import pitiupi.models.User;
import plugin.MultiProtocolChat;
import javax.swing.*;
import java.awt.*;
import java.net.InetAddress;
public class PrivateChatMenu extends UnicastActionMenu {
PrivateChatWindow privateChatWindow;
private final MultiProtocolChat multiProtocolChat;
private final ChatController chatController;
public PrivateChatMenu(PrivateChatWindow PrivateChatWindow, MultiProtocolChat multiProtocolChat, ChatController chatController) {
this.privateChatWindow = PrivateChatWindow;
this.multiProtocolChat = multiProtocolChat;
this.chatController = chatController;
}
@Override
public void onClick(User user) {
if (multiProtocolChat.openConnection(user.getAddress())
else errorLabel.setVisible(true);
dialog.setContentPane(contentPanel);
dialog.pack();
dialog.setLocationRelativeTo(SwingUtilities.getWindowAncestor(privateChatWindow));
dialog.setVisible(true);
}
@Override
public String getText() {
return "start Private Chat";
}
}
+93 -14
View File
@@ -1,43 +1,122 @@
package GUI; package GUI;
import net.MpChatMessage; import net.MpChatMessage;
import pitiupi.control.ConnectionManager;
import pitiupi.net.tcp.TcpConnection; import pitiupi.net.tcp.TcpConnection;
import pitiupi.plugin.Pitiupi;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.net.InetAddress;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public class PrivateChatWindow extends ChatWindow { public class PrivateChatWindow extends JFrame {
private final Pitiupi pitiupi;
private final TcpConnection connection; private final TcpConnection connection;
public PrivateChatWindow(ConnectionManager ConnectionManager, TcpConnection connection) { private final JTextArea receivedMessagesArea;
super(ConnectionManager); private final JTextField messageField;
public PrivateChatWindow(
Pitiupi pitiupi,
TcpConnection connection
) {
this.pitiupi = pitiupi;
this.connection = connection; this.connection = connection;
}
@Override setTitle("Chat privado");
protected void initButtons(JPanel bottomPanel) { setSize(700, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(
BorderFactory.createEmptyBorder(10, 10, 10, 10)
);
receivedMessagesArea = new JTextArea();
receivedMessagesArea.setEditable(false);
receivedMessagesArea.setLineWrap(true);
receivedMessagesArea.setWrapStyleWord(true);
JScrollPane messagesScrollPane =
new JScrollPane(receivedMessagesArea);
messagesScrollPane.setBorder(
BorderFactory.createTitledBorder("Mensagens")
);
mainPanel.add(
messagesScrollPane,
BorderLayout.CENTER
);
JPanel bottomPanel = new JPanel(
new BorderLayout(10, 10)
);
messageField = new JTextField();
JButton tcpButton = new JButton("TCP"); JButton tcpButton = new JButton("TCP");
tcpButton.addActionListener(e -> sendTCP()); tcpButton.addActionListener(e -> sendTCP());
JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0)); bottomPanel.add(
messageField,
BorderLayout.CENTER
);
buttonsPanel.add(tcpButton); bottomPanel.add(
tcpButton,
BorderLayout.EAST
);
bottomPanel.add(buttonsPanel, BorderLayout.EAST); mainPanel.add(
bottomPanel,
BorderLayout.SOUTH
);
setContentPane(mainPanel);
} }
private void sendTCP() { private void sendTCP() {
try { try {
MpChatMessage msg = createMessage("TCP Private"); String nome = pitiupi.getUserName();
connection.send(msg.toByteArray());
MpChatMessage message = new MpChatMessage(
nome,
InetAddress.getLocalHost().getHostAddress(),
"TCP Private",
messageField.getText()
);
connection.send(message.toByteArray());
receiveMessage(message);
messageField.setText("");
} catch (Exception ex) { } catch (Exception ex) {
Logger.getLogger(PrivateChatWindow.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(
PrivateChatWindow.class.getName()
).log(Level.SEVERE, null, ex);
} }
} }
public void receiveMessage(MpChatMessage message) {
SwingUtilities.invokeLater(() -> {
String texto = String.format(
"[%s] %s (%s): %s%n",
message.getTipoMensagem(),
message.getNome(),
message.getIp(),
message.getMessage()
);
receivedMessagesArea.append(texto);
receivedMessagesArea.setCaretPosition(
receivedMessagesArea.getDocument().getLength()
);
});
}
} }
+141 -16
View File
@@ -2,36 +2,95 @@ package GUI;
import net.MpChatMessage; import net.MpChatMessage;
import pitiupi.control.ConnectionManager; import pitiupi.control.ConnectionManager;
import pitiupi.plugin.Pitiupi;
import javax.swing.*; import javax.swing.*;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import java.awt.*; import java.awt.*;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public class PublicChatWindow extends ChatWindow { public class PublicChatWindow extends JFrame {
private final Pitiupi pitiupi;
private JTextPane receivedMessagesArea;
private JTextField messageField;
private JTextField ipField; private JTextField ipField;
public PublicChatWindow(ConnectionManager connectionManager) { public PublicChatWindow(Pitiupi pitiupi) {
super(connectionManager); this.pitiupi = pitiupi;
initComponents();
} }
@Override private void initComponents() {
protected void initButtons(JPanel bottomPanel) { setTitle("Public Chat");
setSize(700, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(
BorderFactory.createEmptyBorder(10, 10, 10, 10)
);
initReceivedMessagesPanel(mainPanel);
initBottomPanel(mainPanel);
setContentPane(mainPanel);
}
private void initReceivedMessagesPanel(JPanel mainPanel) {
receivedMessagesArea = new JTextPane();
receivedMessagesArea.setEditable(false);
JScrollPane messagesScrollPane = new JScrollPane(receivedMessagesArea);
messagesScrollPane.setBorder(
BorderFactory.createTitledBorder("Mensagens recebidas")
);
mainPanel.add(messagesScrollPane, BorderLayout.CENTER);
}
private void initBottomPanel(JPanel mainPanel) {
messageField = new JTextField();
JPanel bottomPanel = new JPanel(
new BorderLayout(10, 10)
);
bottomPanel.add(messageField, BorderLayout.CENTER);
initIpField(bottomPanel); initIpField(bottomPanel);
initCommunicationButtons(bottomPanel); initCommunicationButtons(bottomPanel);
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
} }
private void initIpField(JPanel bottomPanel) { private void initIpField(JPanel bottomPanel) {
ipField = new JTextField(); ipField = new JTextField();
ipField.setPreferredSize(new Dimension(130, 30)); ipField.setPreferredSize(new Dimension(130, 30));
JPanel ipPanel = new JPanel(new BorderLayout(5, 5)); JPanel ipPanel = new JPanel(
new BorderLayout(5, 5)
);
ipPanel.add(new JLabel("IP destino:"), BorderLayout.WEST); ipPanel.add(
new JLabel("IP destino:"),
BorderLayout.WEST
);
ipPanel.add(ipField, BorderLayout.CENTER); ipPanel.add(
ipField,
BorderLayout.CENTER
);
bottomPanel.add(ipPanel, BorderLayout.WEST); bottomPanel.add(ipPanel, BorderLayout.WEST);
} }
@@ -45,13 +104,71 @@ public class PublicChatWindow extends ChatWindow {
udpButton.addActionListener(e -> sendUDP()); udpButton.addActionListener(e -> sendUDP());
multicastButton.addActionListener(e -> sendMulticast()); multicastButton.addActionListener(e -> sendMulticast());
JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0)); JPanel buttonsPanel = new JPanel(
new FlowLayout(FlowLayout.RIGHT, 5, 0)
);
buttonsPanel.add(tcpButton); buttonsPanel.add(tcpButton);
buttonsPanel.add(udpButton); buttonsPanel.add(udpButton);
buttonsPanel.add(multicastButton); buttonsPanel.add(multicastButton);
bottomPanel.add(buttonsPanel, BorderLayout.EAST); bottomPanel.add(
buttonsPanel,
BorderLayout.EAST
);
}
private MpChatMessage createMessage(String tipoMensagem) {
try {
String nome = pitiupi.getUserName();
String ip = InetAddress.getLocalHost().getHostAddress();
String message = messageField.getText();
return new MpChatMessage(
nome,
ip,
tipoMensagem,
message
);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public void receiveMessage(MpChatMessage msg) {
SwingUtilities.invokeLater(() -> {
String mensagem = String.format(
"[%s] %s (%s): %s%n",
msg.getTipoMensagem(),
msg.getNome(),
msg.getIp(),
msg.getMessage()
);
StyledDocument document = receivedMessagesArea.getStyledDocument();
SimpleAttributeSet attributes = new SimpleAttributeSet();
if (msg.getTipoMensagem().equals("TCP Unicast") || msg.getTipoMensagem().equals("UDP Unicast")) {
StyleConstants.setForeground(attributes, new Color(128, 0, 128));
}
try {
document.insertString(
document.getLength(),
mensagem,
attributes
);
receivedMessagesArea.setCaretPosition(
document.getLength()
);
} catch (BadLocationException ex) {
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex);
}
});
} }
private void sendTCP() { private void sendTCP() {
@@ -60,10 +177,13 @@ public class PublicChatWindow extends ChatWindow {
InetAddress ip = InetAddress.getByName(ipField.getText()); InetAddress ip = InetAddress.getByName(ipField.getText());
connectionManager.sendTcp(msg, ip); pitiupi.sendTcp(msg, ip);
receiveMessage(msg);
} catch (Exception ex) { } catch (Exception ex) {
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(
PublicChatWindow.class.getName()
).log(Level.SEVERE, null, ex);
} }
} }
@@ -73,10 +193,13 @@ public class PublicChatWindow extends ChatWindow {
InetAddress ip = InetAddress.getByName(ipField.getText()); InetAddress ip = InetAddress.getByName(ipField.getText());
connectionManager.sendUdpUnicast(msg, ip); pitiupi.sendUdpUnicast(msg, ip);
receiveMessage(msg);
} catch (Exception ex) { } catch (Exception ex) {
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(
PublicChatWindow.class.getName()
).log(Level.SEVERE, null, ex);
} }
} }
@@ -84,10 +207,12 @@ public class PublicChatWindow extends ChatWindow {
try { try {
MpChatMessage msg = createMessage("UDP Multicast"); MpChatMessage msg = createMessage("UDP Multicast");
connectionManager.sendUdpMulticast(msg); pitiupi.sendUdpMulticast(msg);
} catch (Exception ex) { } catch (Exception ex) {
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(
PublicChatWindow.class.getName()
).log(Level.SEVERE, null, ex);
} }
} }
} }
@@ -0,0 +1,26 @@
package GUI;
import control.PrivateChatController;
import pitiupi.models.Menus.UnicastMenus.UnicastActionMenu;
import pitiupi.models.User;
public class UnicastMpChatMenu extends UnicastActionMenu {
private final PrivateChatController controller;
public UnicastMpChatMenu(
PrivateChatController controller
) {
this.controller = controller;
}
@Override
public void onClick(User user) {
controller.openChat(user);
}
@Override
public String getText() {
return "Start Private Chat";
}
}
@@ -0,0 +1,116 @@
package control;
import GUI.PrivateChatWindow;
import net.MpChatMessage;
import pitiupi.models.User;
import pitiupi.net.tcp.TcpConnection;
import pitiupi.plugin.Pitiupi;
import plugin.MultiProtocolChat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class PrivateChatController {
private final Pitiupi pitiupi;
private final MultiProtocolChat multiProtocolChat;
/*
* Relaciona o endereço do usuário à janela de chat.
*/
private final Map<String, PrivateChatWindow> windows;
/*
* Endereços para os quais uma conexão já foi solicitada,
* mas ainda não chegou.
*/
private final Set<String> pendingConnections;
public PrivateChatController(
Pitiupi pitiupi,
MultiProtocolChat multiProtocolChat
) {
this.pitiupi = pitiupi;
this.multiProtocolChat = multiProtocolChat;
this.windows = new HashMap<>();
this.pendingConnections = new HashSet<>();
}
public void openChat(User user) {
String address = user.getAddress().getHostAddress();
PrivateChatWindow window = windows.get(address);
/*
* Já existe uma janela para esse usuário.
*/
if (window != null) {
window.setVisible(true);
window.toFront();
window.requestFocus();
return;
}
/*
* Já estamos esperando uma conexão para esse usuário.
*/
if (pendingConnections.contains(address)) {
return;
}
/*
* Ainda não existe conexão.
*/
pendingConnections.add(address);
multiProtocolChat.openConnection(address);
}
public void receiveMessage(TcpConnection connection, MpChatMessage message) {
String address = connection.getAddress().toString();
PrivateChatWindow window = windows.get(address);
if (window == null) {
return;
}
window.receiveMessage(message);
}
/**
* Deve ser chamado quando uma nova conexão privada chegar.
*/
public void connectionReceived(TcpConnection connection) {
String address = connection.getAddress().toString();
pendingConnections.remove(address);
PrivateChatWindow window = new PrivateChatWindow(
pitiupi,
connection
);
windows.put(address, window);
window.setVisible(true);
window.toFront();
window.requestFocus();
}
/**
* Fecha a janela e remove o relacionamento.
*/
public void closeChat(TcpConnection connection) {
String address = connection.getAddress().toString();
PrivateChatWindow window = windows.remove(address);
if (window != null) {
window.dispose();
}
}
}
@@ -1,24 +1,29 @@
package plugin; package plugin;
import GUI.ChatMenu; import GUI.MulticastMpChatMenu;
import control.ChatController; import GUI.PrivateChatWindow;
import GUI.PublicChatWindow;
import GUI.UnicastMpChatMenu;
import control.PrivateChatController;
import net.MpChatMessage; import net.MpChatMessage;
import pitiupi.control.ConnectionManager; import pitiupi.models.Menus.MenuCategory;
import pitiupi.control.GuiManager;
import pitiupi.models.Messages.Message; import pitiupi.models.Messages.Message;
import pitiupi.net.tcp.TcpConnection; import pitiupi.net.tcp.TcpConnection;
import pitiupi.models.Plugin; import pitiupi.models.Plugin;
import pitiupi.plugin.Pitiupi;
import javax.swing.*; import javax.swing.*;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.List;
public class MultiProtocolChat extends Plugin { public class MultiProtocolChat extends Plugin {
ChatController chatController; Pitiupi pitiupi;
ConnectionManager connectionManager;
GuiManager guiManager; PrivateChatController privateChatController;
PublicChatWindow publicChatWindow;
@Override @Override
public String getName() { public String getName() {
@@ -41,30 +46,37 @@ public class MultiProtocolChat extends Plugin {
} }
@Override @Override
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) { public void createPlugin(Pitiupi pitiupi) {
this.connectionManager = connectionManager; this.pitiupi = pitiupi;
this.guiManager = guiManager;
ChatMenu chatMenu = new ChatMenu(this); privateChatController = new PrivateChatController(pitiupi, this);
chatController = new ChatController(connectionManager, chatMenu);
guiManager.addMulticastMenu(chatMenu); publicChatWindow = new PublicChatWindow(pitiupi);
MulticastMpChatMenu multicastMpChatMenu = new MulticastMpChatMenu(publicChatWindow);
UnicastMpChatMenu unicastMpChatMenu = new UnicastMpChatMenu(privateChatController);
pitiupi.createMenu(MenuCategory.Social, multicastMpChatMenu);
pitiupi.createMenu(MenuCategory.Social, unicastMpChatMenu);
} }
@Override @Override
public Message getMessage() { public List<Class<? extends Message>> getMessages() {
return new MpChatMessage(); return List.of(MpChatMessage.class);
} }
@Override @Override
public void receiveMessage(byte[] message) { public void receiveMessage(Message message) {
MpChatMessage mpChatMessage = parseMessage(message); if (message instanceof MpChatMessage mpChatMessage) {
chatController.receivePublic(mpChatMessage); publicChatWindow.receiveMessage(mpChatMessage);
} }
}
@Override @Override
public boolean receivePrivateConnection(TcpConnection connection) { public boolean receivePrivateConnection(TcpConnection connection) {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
chatController.addChatWindow(connection); privateChatController.connectionReceived(connection);
}); });
return true; return true;
} }
@@ -72,25 +84,29 @@ public class MultiProtocolChat extends Plugin {
@Override @Override
public boolean receiveBytes(TcpConnection connection, byte[] bytes) { public boolean receiveBytes(TcpConnection connection, byte[] bytes) {
MpChatMessage mpChatMessage = parseMessage(bytes); MpChatMessage mpChatMessage = parseMessage(bytes);
chatController.receivePrivate(connection, mpChatMessage); if (mpChatMessage == null) {
System.out.println("Invalid message received");
return true;
}
else System.out.println(mpChatMessage.getMessage());
privateChatController.receiveMessage(connection, mpChatMessage);
return true; return true;
} }
@Override @Override
public boolean onSocketClosed(TcpConnection connection) { public boolean onSocketClosed(TcpConnection connection) {
chatController.removeChatWindow(connection); privateChatController.closeChat(connection);
return true; return true;
} }
public boolean openConnection(String ip) { public void openConnection(String ip) {
try { try {
connectionManager.connectPluginTcp(InetAddress.getByName(ip), getIdentifier(), getIdentifier()); pitiupi.connectTcp(InetAddress.getByName(ip), getIdentifier(), getIdentifier());
return true;
} }
catch (Exception e) { catch (Exception e) {
System.out.println("Error opening connection: " + e.getMessage()); System.out.println("Error opening connection: " + e.getMessage());
} }
return false;
} }
private MpChatMessage parseMessage(byte[] bytes) { private MpChatMessage parseMessage(byte[] bytes) {
-8
View File
@@ -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
View File
@@ -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>
+18 -16
View File
@@ -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=
@@ -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();
}
} }
+4 -4
View File
@@ -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)
+4 -4
View File
@@ -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}"/>
+4 -18
View File
@@ -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);
-1
View File
@@ -111,7 +111,6 @@ public class MenuBar extends JMenuBar {
} }
public void addPluginMenu(MenuCategory category, JMenuItem menu){ public void addPluginMenu(MenuCategory category, JMenuItem menu){
System.out.println("addPluginMenu");
categories.get(category).add(menu); categories.get(category).add(menu);
} }
} }
@@ -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;
@@ -60,12 +61,36 @@ public class PluginManager {
private Plugin getPlugin(String pluginId) { private Plugin getPlugin(String pluginId) {
for (LoadedPlugin loadedPlugin : loadedPlugins) { for (LoadedPlugin loadedPlugin : loadedPlugins) {
if (loadedPlugin.getPlugin().getIdentifier().equals(pluginId)) if (loadedPlugin.getPlugin().getIdentifier().equals(pluginId)) {
return loadedPlugin.getPlugin(); return loadedPlugin.getPlugin();
} }
}
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 true;
}
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"));
@@ -93,7 +118,6 @@ public class PluginManager {
ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, classLoader); ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, classLoader);
for (Plugin plugin : serviceLoader) { for (Plugin plugin : serviceLoader) {
System.out.println("Loading: " + plugin.getName());
LoadedPlugin loadedPlugin = new LoadedPlugin(plugin, classLoader); LoadedPlugin loadedPlugin = new LoadedPlugin(plugin, classLoader);
plugin.createPlugin(pitiupi); plugin.createPlugin(pitiupi);
@@ -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,
}
+10 -1
View File
@@ -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);
}
}
+457
View File
@@ -0,0 +1,457 @@
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 pitiupi.models.Plugin}. 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,173 @@
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;
import java.util.Arrays;
/**
* 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);
}
}
}
}
-197
View File
@@ -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;
}
}
-224
View File
@@ -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();
}
}
-100
View File
@@ -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();
}
}
+3 -13
View File
@@ -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() {