Visualizzazione dei risultati da 1 a 7 su 7
  1. #1

    chat pear to pear in java

    Ciao sono nuova di questo sito. Volevo cheiedere un'aiutino per un progetto. Devo creare una chat java UDP pear to pear tra almeno tre diversi computer.il problema principale è che i pacchetti non vengono scambiati e quindi io leggo i messaggi ma gli altri nn leggono i miei. qualcuno mi può aiutare a capire dove sbaglio?
    .qui il codice: (SPEZZATO IN DUE MES PERCHE' IL SITO NN ME LO FA CARICARE TUTTO!)

    codice:
    /*
     * NewJFrameChat.java
     *
     * Created on 15-feb-2012, 13.32.51
     */
    package chatp2p;
    
    import java.net.*;
    /**
     *
     * @author Elena
     */
    public class NewJFrameChat extends javax.swing.JFrame {
        
        String username;
        Boolean isConnected = false;
        MulticastSocket socket;
        InetAddress gruppo;
        DatagramPacket pack;
        String message;
    
        /** Creates new form NewJFrameChat */
        public NewJFrameChat() {
            initComponents();
        }
        
        public class IncomingReader implements Runnable{
    
            public void run() {
                String stream;
                String[] data;
                String connect = "Connect", disconnect = "Disconnect", chat = "Chat";
                DatagramPacket p;
                byte buffer[] = new byte [1000];
                try 
                {
                    while (isConnected == true)
                    {   
                        p = new DatagramPacket(buffer,buffer.length);
                        socket.receive(p);
                        stream = new String(p.getData(), 0, p.getLength());            
                        /*data = stream.split("¥");
                         * 
    
                         if (data[2].equals(chat)) 
                         {
                            chatArea.append(data[0] + ": " + data[1] + "\n");
                         } 
                         if (data[2].equals(connect))
                         {  
                            chatArea.removeAll();
                            chatArea.append(data[0]+" si è unito alla conversazione.\n");
                            userslistArea.append(data[0]+"\n");
                         }
                         if (data[2].equals(disconnect)) 
                         {
                            chatArea.append(data[0]+" è uscito dalla conversazione");
                         }*/
                         chatArea.append(stream+"\n");
                     }
                     
                  }
               catch(Exception e) 
               {
                   chatArea.append("Errore: "+e.toString()+".\n");
               }
            }
        }
    
        public void ListenThread() 
        {
             Thread IncomingReader = new Thread(new IncomingReader());
             IncomingReader.start();
        }
        
        public void inviomessconnessione (String u)
        {
            String m;
            byte buff[] = new byte[1000];
            try
            {
                m = (u+": è connesso.\n");
                buff = m.getBytes();
                pack = new DatagramPacket(buff,buff.length,gruppo,5000);
                socket.send(pack);
            }
            catch(Exception e)
            {
                chatArea.append("Errore1:"+e.toString()+".\n");
            }
        }
        public void inviomessdisconnessione(String u)
        {
            String m;
            byte buff[]= new byte[1000];
            try
            {
                m = (u+": si è disconnesso.\n");
                buff = m.getBytes();
                pack = new DatagramPacket(buff,buff.length,gruppo,5000);
                socket.send(pack);
            }
            catch(Exception e)
            {
                //chatArea.append("Errore2:"+e.toString()+".\n");
            }
        }
    
        /** This method is called from within the constructor to
         * initialize the form.
         * WARNING: Do NOT modify this code. The content of this method is
         * always regenerated by the Form Editor.
         */
        @SuppressWarnings("unchecked")
        // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
        private void initComponents() {
    
            connectButton = new javax.swing.JButton();
            jButton1 = new javax.swing.JButton();
            jLabel1 = new javax.swing.JLabel();
            userField = new javax.swing.JTextField();
            jScrollPane1 = new javax.swing.JScrollPane();
            chatArea = new javax.swing.JTextArea();
            jScrollPane2 = new javax.swing.JScrollPane();
            userslistArea = new javax.swing.JTextArea();
            jLabel2 = new javax.swing.JLabel();
            jScrollPane3 = new javax.swing.JScrollPane();
            messArea = new javax.swing.JTextArea();
            sentButton = new javax.swing.JButton();
    
            setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    
            connectButton.setText("Connetti");
            connectButton.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    connectButtonActionPerformed(evt);
                }
            });
    
            jButton1.setText("Disconnetti");
            jButton1.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    jButton1ActionPerformed(evt);
                }
            });
    
            jLabel1.setText("Username:");
    
            chatArea.setColumns(20);
            chatArea.setEditable(false);
            chatArea.setLineWrap(true);
            chatArea.setRows(5);
            jScrollPane1.setViewportView(chatArea);
    
            userslistArea.setColumns(20);
            userslistArea.setEditable(false);
            userslistArea.setRows(5);
            jScrollPane2.setViewportView(userslistArea);
    
            jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
            jLabel2.setText("Utenti connessi");
    
            messArea.setColumns(20);
            messArea.setRows(5);
            jScrollPane3.setViewportView(messArea);
    
            sentButton.setText("Invia");
            sentButton.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    sentButtonActionPerformed(evt);
                }
            });
    
            javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
            getContentPane().setLayout(layout);
            layout.setHorizontalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup()
                    .addContainerGap()
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addGroup(layout.createSequentialGroup()
                            .addComponent(jScrollPane3, javax.swing.GroupLayout.PREFERRED_SIZE, 494, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                            .addComponent(sentButton, javax.swing.GroupLayout.DEFAULT_SIZE, 105, Short.MAX_VALUE))
                        .addGroup(layout.createSequentialGroup()
                            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                                .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.LEADING)
                                .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                                    .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE)
                                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                    .addComponent(userField, javax.swing.GroupLayout.PREFERRED_SIZE, 153, javax.swing.GroupLayout.PREFERRED_SIZE)
                                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                    .addComponent(connectButton, javax.swing.GroupLayout.PREFERRED_SIZE, 82, javax.swing.GroupLayout.PREFERRED_SIZE)
                                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                    .addComponent(jButton1)))
                            .addGap(18, 18, 18)
                            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                                .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 188, Short.MAX_VALUE))))
                    .addContainerGap())
            );
            layout.setVerticalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup()
                    .addContainerGap()
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(jLabel1)
                        .addComponent(userField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addComponent(connectButton)
                        .addComponent(jButton1)
                        .addComponent(jLabel2))
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                        .addComponent(jScrollPane2, javax.swing.GroupLayout.Alignment.LEADING)
                        .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 273, Short.MAX_VALUE))
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 11, Short.MAX_VALUE)
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                        .addComponent(sentButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(jScrollPane3))
                    .addContainerGap())
            );
    
            pack();
        }
    // </editor-fold>

  2. #2
    (2 PARTE DEL CODICE)
    codice:
    private void connectButtonActionPerformed(java.awt.event.ActionEvent evt) {                                              
    // TODO add your handling code here:
        
        if(isConnected == false)
        {
            System.out.println("siamo qui 1");
            username = userField.getText();
            userField.setEditable(false);
            try
            {
                socket = new MulticastSocket (5000);
                gruppo = InetAddress.getByName("224.0.0.10");
                socket.joinGroup(gruppo);
                chatArea.append("Ti sei unito alla chat.\n");
                isConnected = true;
                inviomessconnessione(username);
            }
            catch(Exception e)
            {
                chatArea.append("Errore durante la connessione: "+e.toString()+".\n");
                userField.setEditable(true);
            }
            ListenThread();
        }
        else
            if(isConnected == true)
            {
                chatArea.append("Sei già connesso!!!\n");
            }
    }                                             
    
    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                         
    // TODO add your handling code here: DISCONNETTI
        inviomessdisconnessione(username);
        if(isConnected == false)
        {
            chatArea.append("Sei già disconnesso\n");
        }
        if(isConnected == true)
        {
            
            try
            {
                
                socket.leaveGroup(gruppo);
                socket.close();
                userField.setEditable(true);
                isConnected = false;
            }
            catch(Exception e) 
            {
                chatArea.append("Errore durante la disconnessione.\n");
            }
        }
        
    }                                        
    
    private void sentButtonActionPerformed(java.awt.event.ActionEvent evt) {                                           
    // TODO add your handling code here:
        byte buff[]= new byte [1000];
        try{
            message = (username+": "+messArea.getText());
            buff = message.getBytes();
            pack = new DatagramPacket(buff,buff.length,gruppo,5000);
            socket.send(pack);
        }
        catch(Exception e)
        {
            chatArea.append("Errore durante la spedizione del messaggio: "+e.toString()+".\n");
        }
    }                                          
    
        /**
         * @param args the command line arguments
         */
        public static void main(String args[]) {
            /* Set the Nimbus look and feel */
            //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
            /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
             * For details see http://download.oracle.com/javase/tu...feel/plaf.html 
             */
            try {
                for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                    if ("Nimbus".equals(info.getName())) {
                        javax.swing.UIManager.setLookAndFeel(info.getClassName());
                        break;
                    }
                }
            } catch (ClassNotFoundException ex) {
                java.util.logging.Logger.getLogger(NewJFrameChat.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
            } catch (InstantiationException ex) {
                java.util.logging.Logger.getLogger(NewJFrameChat.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
            } catch (IllegalAccessException ex) {
                java.util.logging.Logger.getLogger(NewJFrameChat.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
            } catch (javax.swing.UnsupportedLookAndFeelException ex) {
                java.util.logging.Logger.getLogger(NewJFrameChat.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
            }
            //</editor-fold>
    
            /* Create and display the form */
            java.awt.EventQueue.invokeLater(new Runnable() {
    
                public void run() {
                    new NewJFrameChat().setVisible(true);
                }
            });
        }
        // Variables declaration - do not modify                     
        private javax.swing.JTextArea chatArea;
        private javax.swing.JButton connectButton;
        private javax.swing.JButton jButton1;
        private javax.swing.JLabel jLabel1;
        private javax.swing.JLabel jLabel2;
        private javax.swing.JScrollPane jScrollPane1;
        private javax.swing.JScrollPane jScrollPane2;
        private javax.swing.JScrollPane jScrollPane3;
        private javax.swing.JTextArea messArea;
        private javax.swing.JButton sentButton;
        private javax.swing.JTextField userField;
        private javax.swing.JTextArea userslistArea;
        // End of variables declaration                   
    }

  3. #3
    Utente di HTML.it
    Registrato dal
    Feb 2007
    Messaggi
    4,157
    due note:

    1. peer to peer
    2. usa i tag code.

    poi invece di buttare tutto il codice, perché non evidenzi i punti che ritieni importanti per la tua problematica? esempio, non leggi...allora evidenzia

    1. come crei la connessione
    2. come fai l'invio
    3. come fai la ricezione

    in questo modo per aiutarti dovrei prendere il tuo codice e iniziare a debuggare, è più semplice se vai ad indicare più o meno dove guardare. Magari non vedi perché è sbagliato (se è sbagliato)
    RTFM Read That F*** Manual!!!

  4. #4
    Grazie mille per i tuoi consigli ma sono già riuscita a risolvere il mio precedente problema. Ora ve ne sottopongo un'altro sempre riguardante la stessa chat. Il codice (senza le parti di implementazione grafica) modificato è questo. La chat funziona correttamente ma quando un utente si disconnette mi lancia un errore la funzione implements Runnable. Come faccio a far si che prima si facciano le operazioni di chiusura e poi si chiuda effettivamente la connessione?

    codice:
     //Created on 15-feb-2012, 13.32.51
    
    package chatp2p;
    import java.net.*;
    import java.awt.*;
    
    /**
      * @author Elena! 
     */
    public class NewJFrameChat extends javax.swing.JFrame {
        
        String username;
        Boolean isConnected = false;
        MulticastSocket socket;
        InetAddress gruppo;
        DatagramPacket pack;
        String message;
        List userslist = new List();
    
       
        public NewJFrameChat() {
            initComponents();
        }
        
        public class IncomingReader implements Runnable{
    
            public void run() {
                String stream;
                String[] data;
                String connect = "Connect", disconnect = "Disconnect", chat = "Chat";
                DatagramPacket p;
                byte buffer[] = new byte [6000];
                try 
                {
                    while (true)
                    {   
                        p = new DatagramPacket(buffer,buffer.length,gruppo,5555);
                        socket.receive(p);
                        stream = new String(p.getData(),0,p.getLength());
                        data = stream.split("¥");
    
                         if (data[2].equals(chat)) 
                         {
                            chatArea.append(data[0] + ": " + data[1] + "\n");
                         } 
                         if (data[2].equals(connect))
                         {  
                            chatArea.removeAll();
                            chatArea.append(data[0]+": si è unito alla conversazione.\n");
                            userslist.add(data[0]);
                            showuser(userslist);
                         }
                         if (data[2].equals(disconnect)) 
                         {
                            chatArea.append(data[0]+" è uscito dalla conversazione");
                            removeuser(userslist,data[0]);
                         }
                         
                     }
                     
                  }
               catch(Exception e) 
               {
                   chatArea.append("Errore: "+e.toString()+".\n"); 
    //mi dice java.net.SocketException:socket close quando clicco su disconnetti
               }
            }
        }
    
        public void ListenThread() 
        {
             Thread IncomingReader = new Thread(new IncomingReader());
             IncomingReader.start();
        }
        
        public void inviomessconnessione (String u)
        {
            String m;
            
            byte buff[] = new byte[6000];
            try
            {
                m = (u+"¥"+" "+"¥"+"Connect");
                buff = m.getBytes();
                pack = new DatagramPacket(buff,buff.length,gruppo,5555);
                socket.send(pack);
            }
            catch(Exception e)
            {
                chatArea.append("Errore1:"+e.toString()+".\n");
            }
        }
        public void inviomessdisconnessione(String u)
        {
            String m;
            byte buff[]= new byte [6000];
            try
            {
                m = (u+"¥"+" "+"¥"+"Disconnect");
                buff = m.getBytes();
                pack = new DatagramPacket(buff,buff.length,gruppo,5555);
                socket.send(pack);
            }
            catch(Exception e)
            {
                chatArea.append("Errore2:"+e.toString()+".\n");
            }
        }
        public void showuser(List l)
        {
            String [] temp = l.getItems();
            userslistArea.setText("");
            for (String token: temp)
            {
                userslistArea.append(token+"\n");
            }
        }
        public void removeuser(List l, String u)
        {
            try
            {
                l.remove(u);
                String [] temp = l.getItems();
                userslistArea.setText("");
                for (String token: temp)
                {
                    userslistArea.append(token+"\n");
                }
            }
            catch (Exception e)
            {
                chatArea.append(e.toString()+"\n");
            }
        }
    
     
        @SuppressWarnings("unchecked")
         
    
    private void connectButtonActionPerformed(java.awt.event.ActionEvent evt) {                                              
    // TODO add your handling code here:
       if(isConnected == false)
        {
            username = userField.getText();
            userField.setEditable(false);
            try
            {
    //creo la connessione multicast e invio tramite funzione un messaggio a tutti gli utenti //connessi
                socket = new MulticastSocket (5555);
                gruppo = InetAddress.getByName("224.10.0.2");
                socket.joinGroup(gruppo);
                inviomessconnessione(username);
                isConnected = true;
            }
            catch(Exception e)
            {
                chatArea.append("Errore durante la connessione: "+e.toString()+".\n");
                userField.setEditable(true);
                isConnected = false;
            }
            ListenThread();
        }
        else
            if(isConnected == true)
            {
                chatArea.append("Sei già connesso!!!\n");
            }
    }                                             
    
    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                         
    // Questa è la parte che mi da problemi credo! E' la funzione di disconnessione che si attiva 
    // premendo il tasto disconnetti
        if(isConnected == false)
        {
            chatArea.append("Sei già disconnesso.\n");
        }
        if(isConnected == true)
        {
             inviomessdisconnessione(username);
     //ho provato a metterla prima della disconnessione effettiva ma nn cambia nulla
            try
            {
                userField.setEditable(true);
                chatArea.setText("");
                userslistArea.setText("");
                messArea.setText("");
                isConnected = false;
                socket.leaveGroup(gruppo);
                socket.close();
            }
            catch(Exception e) 
            {
                chatArea.append("Errore durante la disconnessione"+e.toString()+".\n");
                isConnected = true;
            }
        }
        
    }                                        
    
    private void sentButtonActionPerformed(java.awt.event.ActionEvent evt) {                                           
    // questa funzione una volta che si clicca su sent invia un messaggio tramite pacchetti datagram
        byte buff[]= new byte [1000];
        try{
            message = (username+"¥"+messArea.getText()+"¥"+"Chat");
            buff = message.getBytes();
            pack = new DatagramPacket(buff,buff.length,gruppo,5555);
            socket.send(pack);
        }
        catch(Exception e)
        {
            chatArea.append("Errore durante la spedizione del messaggio: "+e.toString()+".\n");
        }
        messArea.setText("");
     //una volta premuto il tasto la finestra in cui scrivere i messaggi viene risettata
    }                                          
    
    
          
        // Variables declaration - do not modify                     
        private javax.swing.JTextArea chatArea;
        private javax.swing.JButton connectButton;
        private javax.swing.JButton jButton1;
        private javax.swing.JLabel jLabel1;
        private javax.swing.JLabel jLabel2;
        private javax.swing.JScrollPane jScrollPane1;
        private javax.swing.JScrollPane jScrollPane2;
        private javax.swing.JScrollPane jScrollPane3;
        private javax.swing.JTextArea messArea;
        private javax.swing.JButton sentButton;
        private javax.swing.JTextField userField;
        private javax.swing.JTextArea userslistArea;
        // End of variables declaration                   
    }

  5. #5
    Utente di HTML.it
    Registrato dal
    Feb 2007
    Messaggi
    4,157
    anche se togli la parte grafica, spero ti renda conto che se non dici in dettaglio il problema EVIDENZIANDO il punto in cui c'è il problema per noi diventa complicato leggere quel codice e aiutarti.

    Di nuovo, i tag code, in dettaglio dove è che fai la chiusura? quale è in dettaglio l'errore (lo stacktrace per intenderci)
    RTFM Read That F*** Manual!!!

  6. #6
    Moderatore di Programmazione L'avatar di LeleFT
    Registrato dal
    Jun 2003
    Messaggi
    17,304

    Moderazione

    Come valia ti ha già fatto notare due volte e come richiesto espressamente dal Regolamento interno, quando si posta del codice si devono usare i tag CODE, in particolar modo quando il codice è lungo, come nel tuo caso. In questo modo non vengono perse l'indentazione e la formattazione del codice stesso, rendendolo più leggibile (vedi dopo mio intervento sui tuoi post).

    Inoltre, cerca di circoscrivere il problema ad una parte di codice più esigua e posta solo quella, altrimenti risulta davvero difficile andare a caccia dell'errore. Se vi sono eccezioni sollevate dall'applicazione, riportale con tutto lo stackTrace.


    Ciao.
    "Perchè spendere anche solo 5 dollari per un S.O., quando posso averne uno gratis e spendere quei 5 dollari per 5 bottiglie di birra?" [Jon "maddog" Hall]
    Fatti non foste a viver come bruti, ma per seguir virtute e canoscenza

  7. #7

    Nuovo problema

    Salve ragazzi. Ho modificato la chat e i precedenti problemi gli ho risolti.
    Ricapitolando ho costruito una chat peer to peer UDP multicast in java che utilizza la classe di libreria Multisocket (estensione della DatagramSocket) che invia e riceve i messaggi in pacchetti sfruttando la classe DatagramPacket e le sue funzioni receive(Packet p) e send(Packet p). Mentre testavo la chat con altri miei amici ci siamo accorti che se gli utenti sono collegati tutti ad una stessa rete la chat funziona perfettamente mentre in caso di utenti collegati da rete diverse, sebbene tutti gli utenti abbiano specificato lo stesso indirizzo e la stessa porta in fase di connessione in questo modo(ovviamente ho scelto l'indirizzo IPv4 tra quelli di tipo D che sono appositamente riservati per connessioni multicast)
    codice:
    socket = new MulticastSocket (5555);
    gruppo = InetAddress.getByName("224.10.0.2"); 
    socket.joinGroup(gruppo);
    ,gli utenti non ricevono i pacchetti. Mi è stato detto che è "normale" perchè il programma che esegue il codice fa il broadcast sulla rete in cui esso è eseguito. IL MIO PROBLEMA E': chi mi può spiegare meglio questo concetto e dirmi se effettivamente ciò che mi è stato detto è vero?
    Ho provato a cercare su vari siti ma non ho ben afferrato il concetto.

    P.s. spero che questa volta io abbia meglio compreso il regolamento per quanto concerne la creazione di un nuovo post. Se ci sono problemi per favore ditemelo.

    Grazie in anticipo della vostra collaborazione

    Elena

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2024 vBulletin Solutions, Inc. All rights reserved.