Domanda CLIENT SERVER TRANSFER DATA PROCESS

masterix

Utente Electrum
22 Febbraio 2015
119
51
1
134
Salve a tutti ragazzi. Sto creando un client-server in Java e sto sviluppando l'opzione "Run Shell" ovvero un comando che mi consente di impartire comandi shell (dir, cd, netstat ecc..). Ho solamente un piccolo problema, come faccio a passare l'output del comando dal client al Server? Io faccio eseguire il comando sulla macchina client, mi salvo l'output in una StringBuilder e poi tramite un PrintWriter lo passo al Server che grazie ad un BufferedReader leggo e stampo.... ma il problema è che non mi stampa nulla. Sapreste indicarmi qualche altro metodo o se sbaglio in qualcosa? Se serve posso postare anche il codice. Grazie a tutti anticipatamente per la disponibilità.
 
Senza il codice e' difficile a dirsi. Se usi un socket TCP ti serve scrivere il tuo piccolo protocollo, dovresti creare una struttura da inviare come header di ogni pacchetto, non puoi trattare i dati in transito con le sole funzioni normali per file/stringhe.
Un esempio di struttura potrebbe essere:
Java:
class ProtoHeader
{
    public short Version;
    public int OperationType;
    public int BodyLength;
};

Compilando una struttura del genere e serializzandola hai a disposizione diverse info in soli 10 byte: la versione del protocollo (in futuro potresti aggiungere piu' campi), il tipo di operazione e la lunghezza del messaggio che segue questo header.
La parte critica e' proprio conoscere la lunghezza perche' devi sapere quanto leggere dal socket per evitare di restare in attesa e bloccare per nulla. Essendo la struttura a dimensione fissa invece ti basta leggere 10 byte, poi se BodyLength > 0 e < MAX_LENGTH allora leggi anche quel numero di bytes dal socket.

Esempio:
Java:
String outputCmd = ...;
final short CurrentProtoVersion = 1;
final int CMD_OUT_RESPONSE = 3;
byte[] message = outputCmd.getBytes(StandardCharsets.UTF_8);
ProtoHeader hdr = new ProtoHeader();
hdr.Version = CurrentProtoVersion;
hdr.OperationType = CMD_OUT_RESPONSE;
hdr.BodyLength = message.length;

// Serialize ProtoHeader to byte[] and then concat message before send
sendSocket(hdr, message);
 
Ciao, intanto ti ringrazio della risposta. Il punto è proprio questo che sono riuscito ad inviare l'output del comando ma poi rimango in attesa... Eseguo il comando sul client, memorizzo l'output, lo mando al server, lo leggo, lo stampo ma poi rimango in attesa. Mi dà l'impressione come se non uscisse mai dal ciclo while...
CODICE CLIENT
Java:
try {
                                    String command = "dir";
                                    Process process = Runtime.getRuntime().exec("cmd /c "+command);
            
                                    StringBuilder output = new StringBuilder();
                                    BufferedReader reader2 = new BufferedReader(new InputStreamReader (process.getInputStream()));
                                    
                                    String line;
                                    
                                    while((line = reader2.readLine()) != null) {
                                        output.append(line + "\n");
                                    }
                                    
                                    int exitVal = process.waitFor();
                                    if (exitVal == 0) {
                                        
                                        
                                        
                                        String str = output.toString();
                                        writer.println(str);
                                        writer.flush();
                                        System.out.println("Output inviato");
                                        process.destroy();
                                        reader2.close();
                                        
                                    } else {
                                        System.out.println("Something abnormal has haapened :( ");
                                    }
                                        
                                } catch (IOException e) {
                                    e.printStackTrace();
                                } catch (InterruptedException e1) {
                                    e1.printStackTrace();
                                }

CODICE SERVER
Java:
    String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
 
Non e' il ciclo a bloccare, sono writer e reader che immagino siano degli stream che leggono e scrivono direttamente sul socket. Se provi a leggere piu' di quanto ricevuto il socket blocking sospendera' l'esecuzione finche' non arrivano gli altri dati. Usando println e readLine implicitamente stai usando \n per capire dove finisce la stringa. Lo immaginavo, per questo nella risposta precedente ti ho consigliato di usare una struttura o almeno di scrivere prima la lunghezza. Inoltre la tua stringa ha diverse linee all'interno per cui non hai modo di sapere quale sia l'ultima linea e reader.readLine() non ritornera' mai null (quindi il server non funzionera' mai), piuttosto raggiunta l'ultima linea blocchera' per sempre aspettandosi infinite altre linee.

Il modo corretto per il server sarebbe:
Java:
ProtoHeader hdr = new ProtoHeader();
char[] hdrBuff = new char[10];
reader.read(hdrBuff, 0, 10);
// Unserialize hdrBuff to hdr
// ...
if (hdr.BodyLength > 0 && hdr.BodyLength < 65535)
{
    char[] buff = new char[hdr.BodyLength];
    reader.read(buff, 0, hdr.BodyLength);
    // adesso buff contiene l'intero messaggio originale
}
else
{
    // messaggio scartato, lunghezza invalida.
}
 
Ok quindi ho creato una nuova Classe "ProtoHeader" con all'interno gli attributi public da te sopra citati.
Ho copiato il tu codice inserendo una System.out.println(buff) nell'if, mentre, all'interno del blocco else ho inserito un messaggio di errore. Il risultato però è che mi dà sempre errore. Ho cambiato il comando da "dir" ad "cd" in modo tale da non avere una lunghezza eccessiva ma il risultato è sempre lo stesso.
CODICE:
Java:
ProtoHeader hdr = new ProtoHeader();
        char[] hdrBuff = new char[10];
        reader.read(hdrBuff, 0, 10);
        // Unserialize hdrBuff to hdr
        // ...
        if (hdr.BodyLength > 0 && hdr.BodyLength < 65535)
        {
            char[] buff = new char[hdr.BodyLength];
            reader.read(buff, 0, hdr.BodyLength);
            // adesso buff contiene l'intero messaggio originale
            System.out.println(buff);
        }
        else
        {
            System.out.println("Errore!!!");
            // messaggio scartato, lunghezza invalida.
        }
 
Perche' dove c'e' il commento unserialize va fatto l'unserialize altrimenti hdr.BodyLength sara' sempre 0 per come viene inizializzato.
Inoltre devi anche inviarla dal client la struttura (e serializzarla) prima del messaggio in se.

il contenuto di hdrBuff in hex sarebbe una cosa del tipo:
Codice:
01 00 03 00 00 00 C8 00 00 00
01 00 = Versione 1
03 00 00 00 = Tipo 3
C8 00 00 00 = Lunghezza 200 caratteri

Il serialize deve prendere la struttura e trasformarla in 10 byte come quelli dell'esempio.
L'unserialize e' l'opposto e ricostruisci la struttura dall'array di byte.

Se non ti importa niente di versione e tipo puoi anche usare solo la lunghezza, in questo modo non userai la struttura ma i primi 4 byte saranno sempre la lunghezza del messaggio da leggere. Di seguito le funzioni che ti permettono leggere e scrivere int in big-endian:

Java:
int fromByteArray(byte[] bytes) {
     return ((bytes[0] & 0xFF) << 24) |
            ((bytes[1] & 0xFF) << 16) |
            ((bytes[2] & 0xFF) << 8 ) |
            ((bytes[3] & 0xFF) << 0 );
}

byte[] toByteArray(int value) {
    return new byte[] {
        (byte)(value >> 24),
        (byte)(value >> 16),
        (byte)(value >> 8),
        (byte)value };
}