0

I've written a program which uses multicast to discover peers on a local network and allows file transfer between them. It works, except some process of acquiring files/initializing the transfer thread is ridiculously slow. It hangs for about 10-15 seconds, then begins transferring and completes normally:

Transfer.java

  • My JFrame GUI class. This was done with Netbeans for convenience, so any generated code won't be posted here.

    package transfer;
    
    import java.beans.PropertyChangeEvent;
    import java.io.File;
    import java.io.IOException;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.swing.JFileChooser;
    import static javax.swing.JFileChooser.FILES_AND_DIRECTORIES;
    import javax.swing.SwingWorker;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class Transfer extends javax.swing.JFrame {
    
        private final FileDrop fileDrop;
        private Client client;
        private final Server server;
        private final ClientMulticast clientMulticast;
        private final ServerMulticast serverMulticast;
    
        public Transfer() throws IOException {
            initComponents();
    
            this.setTitle("Transfer");
            peerBox.setEditable(false);
            peerBox.removeAllItems();
    
            fileDrop = new FileDrop(backgroundPanel, (java.io.File[] files) -> {
    
                System.out.println(files[0].isDirectory());
    
                if (peerBox.getSelectedIndex() != -1) {
    
                    sendLabel.setText("Hello");
                    try {
                        client = new Client(sendLabel, files, (Peer) peerBox.getSelectedItem());
    
                        //Client property change listener - listens for updates to progress bar
                        client.addPropertyChangeListener((PropertyChangeEvent evt1) -> {
    
                            if (null != evt1.getPropertyName()) switch (evt1.getPropertyName()) {
                                case "progress":
                                    sendProgressBar.setValue((Integer) evt1.getNewValue());
                                    break;
                            }
                        });
    
                        client.execute();
    
                    } catch (Exception ex) {
                        System.out.println("Unable to send! IOException in FileDrop call.");
                        ex.printStackTrace(System.out);
                    }
    
                } else {
                    sendLabel.setText("Host not found");
                }
            });
    
    
            sendProgressBar.setMaximum(100);
            sendProgressBar.setMinimum(0);
            receiveProgressBar.setMaximum(100);
            receiveProgressBar.setMinimum(0);
    
    
            server = new Server(receiveLabel);
    
            //Server property change listener - listens for updates to progress bar
            server.addPropertyChangeListener((PropertyChangeEvent evt1) -> {
                if ("progress".equals(evt1.getPropertyName())) {
                    receiveProgressBar.setValue((Integer) evt1.getNewValue());
                }
            });
    
            server.execute();
    
            serverMulticast = new ServerMulticast();
            serverMulticast.execute();
            clientMulticast = new ClientMulticast(peerBox);
            clientMulticast.execute();
    
        }
    
        ...GENERATED CODE...
    
        private void openButtonActionPerformed(java.awt.event.ActionEvent evt) {                                           
            Transfer guiObject = this;
    
            SwingWorker openFile = new SwingWorker<Void, String>() {
    
                @Override
                protected Void doInBackground() throws Exception {
    
                    openButton.setEnabled(false);
    
                    fileChooser.setFileSelectionMode(FILES_AND_DIRECTORIES);
    
                    int returnVal = fileChooser.showOpenDialog(guiObject);
    
                    if (returnVal == JFileChooser.APPROVE_OPTION && peerBox.getSelectedIndex() != -1) {
    
                    File[] fileArray = fileChooser.getSelectedFiles();                    
    
                    client = new Client(sendLabel, fileArray, (Peer) peerBox.getSelectedItem());
    
                    //Client property change listener - listens for updates to progress bar
                    client.addPropertyChangeListener((PropertyChangeEvent evt1) -> {
                        if ("progress".equals(evt1.getPropertyName())) {
                            sendProgressBar.setValue((Integer) evt1.getNewValue());
                        }
                    });
    
                    client.execute();
    
                    //block this swingworker until client worker is done sending
                    while(!client.isDone()) { }
                }
    
                    openButton.setEnabled(true);
    
                    return null;
                }
            };
    
                openFile.execute();
        }                                          
    
    
        /**
         * @param args the command line arguments
         * @throws java.lang.ClassNotFoundException
         * @throws java.lang.InstantiationException
         * @throws java.lang.IllegalAccessException
         * @throws javax.swing.UnsupportedLookAndFeelException
         */
        public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException {
        System.setProperty("apple.laf.useScreenMenuBar", "true");
        System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Transfer");
    
        UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
    
            /* Create and display the form */
            java.awt.EventQueue.invokeLater(() -> {
                try {
                    new Transfer().setVisible(true);
                } catch (IOException ex) {
                Logger.getLogger(Transfer.class.getName()).log(Level.SEVERE, null, ex);
            }
        });
    }
    
        // Variables declaration - do not modify                     
        private javax.swing.JPanel backgroundPanel;
        private javax.swing.JFileChooser fileChooser;
        private javax.swing.JButton openButton;
        private javax.swing.JComboBox<Peer> peerBox;
        private javax.swing.JLabel receiveHeaderLabel;
        private javax.swing.JLabel receiveLabel;
        private javax.swing.JPanel receivePanel;
        private javax.swing.JProgressBar receiveProgressBar;
        private javax.swing.JLabel sendHeaderLabel;
        private javax.swing.JLabel sendLabel;
        private javax.swing.JPanel sendPanel;
        private javax.swing.JProgressBar sendProgressBar;
        // End of variables declaration                   
    }
    

ClientMutlicast.java

  • Sends out multicast packets and receives responses - modifies the GUI accordingly. This class is where the errors were.

    package transfer;
    
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    import java.net.SocketException;
    import java.net.SocketTimeoutException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    import javax.swing.JComboBox;
    import javax.swing.SwingUtilities;
    import javax.swing.SwingWorker;
    
    public class ClientMulticast extends SwingWorker<Void, Peer> {
    
    private boolean peerPreviouslyFound;
    private byte[] sendData, receiveData;
    private final DatagramSocket mSocket;
    private DatagramPacket receivePacket;
    private final JComboBox<Peer> peerBox;
    private Peer peer;
    private ArrayList<Peer> peerList;
    
        public ClientMulticast(JComboBox<Peer> peerBox) throws SocketException {
    
            peerList = new ArrayList<>();
            this.peerBox = peerBox;
    
            mSocket = new DatagramSocket();
            mSocket.setBroadcast(true);
            mSocket.setSoTimeout(300);
    
            sendData = "CLIENT_MSG".getBytes();
        }
    
            @Override
            protected Void doInBackground() throws IOException, InterruptedException, InvocationTargetException {
                while (true) {
                    try {
                        receiveData = new byte[1024];
                        receivePacket = new DatagramPacket(receiveData, receiveData.length);
    
                        peerPreviouslyFound = false;
    
                        //send broadcast message
                        mSocket.send(new DatagramPacket(sendData, sendData.length, InetAddress.getByName("239.255.255.255"), 8888));
    
                        //receive response
                        mSocket.receive(receivePacket);
    
                        //don't have to worry about responses from local host because
                        //server rejects multicast packets from local host
                        peer = new Peer(receivePacket.getAddress(), receivePacket.getPort(),                         System.currentTimeMillis());
    
                    for (Peer peerList1 : peerList) {
                        if (peerList1.getIPAddress().equals(peer.getIPAddress())) {
                            peerList1.setTimestamp(System.currentTimeMillis());
                            peerPreviouslyFound = true;
                            break;
                        }
                    }
    
                    //add to peer list only if reponse is valid, not from local host, and not previously received from this host
                    if ("SERVER_RESPONSE".equalsIgnoreCase(new String(receivePacket.getData()).trim())
                    && !peerPreviouslyFound) {
    
                        //publish(peer);
                        peerBox.addItem(peer);
                        peerList.add(peer);
                    }
    
                    for (int i = 0; i < peerList.size(); i++) {
                        //if peer is greater than 5 seconds old, remove from list
                        if (peerList.get(i).getTimestamp() + 5000 < System.currentTimeMillis()) {
                            peerBox.removeItemAt(i);
                            peerList.remove(i);
                        }
                    }
                } catch (SocketTimeoutException ex) {
                    for (int i = 0; i < peerList.size(); i++) {
                        //if peer is greater than 5 seconds old, remove from list
                        if (peerList.get(i).getTimestamp() + 5000 < System.currentTimeMillis()) {
                            final int j = i;
    
                            SwingUtilities.invokeAndWait(() -> {
                                peerBox.removeItemAt(j);
                                peerList.remove(j);
                            });
    
                        }
                    }
                }
    
                TimeUnit.MILLISECONDS.sleep(500);
            }//end while
        }
    
        @Override
        protected void process(List<Peer> p) {
            peerBox.addItem(p.get(p.size() - 1));
            peerList.add(p.get(p.size() - 1));
        }
    
    }
    

I'm pretty sure the issue is when the FileDrop object within the UI constructor tries to execute the Client SwingWorker using client.execute(), there's a large delay. I can debug it but it doesn't show any issues. Also, I know the issue can't be with my socket.connect() call within Client(), because the print statement immediately before socket.connect() doesn't print until the program has resumes from wherever it's hanging. Any ideas? I'm completely lost.

-- EDIT: All code above works as desired, bugs noted here have been solved. If anyone wants the full source, I'll gladly share it.

Timmah339
  • 37
  • 1
  • 9
  • A related example using `ProcessBuilder` in a `SwingWorker` is shown [here](http://stackoverflow.com/a/20603012/230513). – trashgod Jul 10 '14 at 15:20
  • Could you elaborate a little bit? That example is doing the same thing I do, just in a different way. My SwingWorkers use a property change listener to update the progress bar and the process() method to schedule a gui update on the EDT. They're using a background task (from what I can understand). Are you saying I'm incorrectly updating the GUI? – Timmah339 Jul 10 '14 at 15:46
  • The word _hangs_ always make me suspect blocking the EDT; but, at a glance, I don't see a violation in your worker; I meant to suggest a complete, working example for reference. – trashgod Jul 10 '14 at 19:15
  • I managed to figure it out. There was another class which handled multicasting that was eating up all execution time on the EDT. I changed that and it's working perfectly. – Timmah339 Jul 11 '14 at 17:40
  • Glad you got it sorted; if you think it might be helpful to others, you can [answer your own question](http://meta.stackoverflow.com/q/17463/163188). – trashgod Jul 11 '14 at 21:35

1 Answers1

0

I am passing my combo box, called peerBox, to my ClientMuticast class. I was modifying this structure directly, storing peers within. This was blocking the interface thread, as the ClientMulticast class was constantly accessing/changing the combo box.
I changed the code to use the combo box to ONLY display values to the gui and store the discovered peers. An array list was used to copy all the values from the combo box upon each invocation of the ClientMulticast constructor. If a peer is discovered, the combo box is updated via the publish() and process() methods within the ClientMulticast class. Any code placed within process() will be scheduled to execute on the EDT.

Timmah339
  • 37
  • 1
  • 9