0

I got another problem with input/output streams. Here i'm sending data from a server to a client. Before sending the data, the server send a little string just to say to the client what he'll send, and so the client know which function he should use to receive.

I'm receiving the first string well, but then i don't get the good integer and after that the second string i receive is "null".

Moreover, if i do a System.out.println before using the DataOutputStream with dos.writeInt, then everything works well. I dont get it. here's the code:

Server:

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.SecureRandom;


public class Server {

    private static OutputStream out;

    static byte[] generateRandomBytes(int len) {
        SecureRandom random = new SecureRandom();
        byte[] bytes = new byte[len];
        random.nextBytes(bytes);
        return bytes;
    }

    public static void sendType(String type) {
        PrintWriter textWriter = new PrintWriter(out);
        textWriter.println(type);
        textWriter.flush();
    }

    public static void sendKeyNumber(int keyNumber) {
        sendType("keyNumber");
        try {
            DataOutputStream dos = new DataOutputStream(out);
            //System.out.println("Sending key number: " + keyNumber);
            dos.writeInt(keyNumber);
            //dos.flush();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
    }

    public static void sendKey(byte[] key) {
        sendType("key");
        try {
            DataOutputStream dos = new DataOutputStream(out);
            //System.out.println("key length to send: " +key.length);
            dos.writeInt(key.length); // write length of the byte array
            //dos.flush();
            dos.write(key);
            //dos.flush();
            System.out.println("key send: " +key);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Socket clientSocket ;        
        System.out.println("ouverture du server");
        try {
            ServerSocket serverSocket = new ServerSocket(2004);
            clientSocket = serverSocket.accept(); 
            out = clientSocket.getOutputStream();

            sendKeyNumber(0);
            byte[] keyBytes = generateRandomBytes(32);
            sendKey(keyBytes);

            clientSocket.close();


        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

Client:

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.crypto.spec.SecretKeySpec;



public class Main {

    static InputStream in;

    public static int receiveKeyNumber() {
        DataInputStream dis = new DataInputStream(in);
        int keyNumber = 0;
        try {
            keyNumber = dis.readInt();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return keyNumber;
    }

    public static SecretKeySpec receiveKey() {
        DataInputStream dIn = new DataInputStream(in);
        int length;
        byte[] keyBytes = null;
        try {
            length = dIn.readInt();                   // read length of incoming message
            System.out.println("key length: " + length);
            if(length!=32) {
                System.err.println("Incorrect size for key: "+ length);
            }       
            else {
                keyBytes = new byte[length];
                dIn.readFully(keyBytes, 0, keyBytes.length); 
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
        SecretKeySpec aesKey = new SecretKeySpec(keyBytes, "AES");
        return aesKey;
    }


    public static void main(String[] args) {

        Socket clientSocket;
        try {
            clientSocket = new Socket(InetAddress.getLocalHost(),2004);

            in = clientSocket.getInputStream();
            while(!clientSocket.isClosed()) {
                BufferedReader textReader = new BufferedReader(new InputStreamReader(in));
                String type = textReader.readLine();
                System.out.println(type);

                if(type.equals("keyNumber")) {
                    int KN = receiveKeyNumber();
                    System.out.println(KN);
                }

                if(type.equals("key")) {
                    SecretKeySpec key = receiveKey();
                    System.out.println(key);
                }
            }

            clientSocket.close();

        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }       
    }
}

End here's what i get (in Client console) when i don't do the System.out.println:

keyNumber
1801812234

null

I always get the same weird number; i've try to convert it to ASCII, but it's not readable.

Any suggestion?

Ablia
  • 317
  • 1
  • 3
  • 14
  • 2
    What's the principle of a **Buffered**Reader? What does its javadoc say? What happens when you call readLine() on it? – JB Nizet Aug 16 '18 at 13:40
  • Well, i've been told that when i use a PrintWriter on one end, i should use a BufferedReader on the other end. But i've also read that BufferedReader are a bit dangerous, because they can read more thant what we want them to read. Ah, usually i always print "\r\n" at the end of my output; adding it here cause the keyNumber to be corrctly receive, and the second string too. However i still got an error at the "readInt" line of my "receiveKey" function. – Ablia Aug 16 '18 at 13:46
  • 2
    *i've also read that BufferedReader are a bit dangerous, because they can read more thant what we want them to read*. Yes, exactly. It's **designed** to do that: you ask for a line, and it reads 8096 characters (or whatever the size of the buffer is) into a **buffer** to speed things up. So, after you've read the line, youhave in fact read much more than a line from your input stream. So, the int that you read next is in fact the 4 bytes coming after these 8096 characters, and not the int that you've written. – JB Nizet Aug 16 '18 at 13:51
  • The easiest way to fix that is to change your protocol. Use a single byte for the type (that makes 256 possible types, which is probably much more than what you need), and only use a DataOutputSTream on one side, and a DataInputStream on the other side. – JB Nizet Aug 16 '18 at 13:51
  • I will also have to use ObjectInputStream later. Will that be a problem too? – Ablia Aug 16 '18 at 13:56
  • If you have an ObjectOutputStream, you shouldn't bother with strings, ints and bytes. Just send objects with writeObject() on one side, and receive objects with readObject() on the other side. The Object streams will handle all the gory details. – JB Nizet Aug 16 '18 at 13:58
  • But then, each time i read an object with readobject, i'll have to do a cast, right? I've been told that sometimes those casts won't works (because a String is an Object but an Object isn't a String) and i'm a bit afraid of using it. – Ablia Aug 16 '18 at 14:17
  • Are you sure you should be using `DataOutputStream`? You seem to be using a confusion of different types of data transformations on output streams. – Mark Rotteveel Aug 16 '18 at 14:32
  • If you don't know what a cast is, then learn about it instead of avoiding it by fear of screwing up. – JB Nizet Aug 16 '18 at 15:00
  • wow man, you don't need to be so rude. Two months ago, the only thing i knew about Java was "it's an object-oriented language". I'm learning it (alone) while trying to make a programm work, and I don't have a lot of time to do so. I can't spend it learning things that I won't use. However, I use casts, which mean I did read about it, And precisely what I read makes me wonder if it is really better to use ObjectIn/OutputStream when a DataIn/OutputStream should be enough. I don't have "fear of screwing up", I have fear of adding unnecessary complexity, following KISS principle. – Ablia Aug 17 '18 at 08:58

1 Answers1

1

In this case where binary data is sent, go entirely for DataOutputStream. (Alternatively you could go for text.)

private static DataOutputStream out;

out = new DataOutputStream(clientSocket.getOutputStream());

public static void sendType(String type) {
    out.writeUTF(type);
    out.flush();
}

Flushing is important with binary conversations.

The problem with wrapping classes DataOutputStream, Printer, BufferedReader and such is that they start their own "cursor" and would close the wrapped I/O on their own closing. Having several DataOutputStreams is somewhat worrying; at least not as intended.

By the way my normal pattern is to do in main: new Server().exec(); or such. That would remove all those statics.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • Ok, thanks. But if BufferedReader is so bad, what's the use of it? And of Printwriter too? Should we use them only when we send Strings? Oh, and don't worry: i don't really do it this way, it's just a minimal & usable version, to run some tests when i got a bugs and to be able to post it here as runnable code with only the problematic part. – Ablia Aug 16 '18 at 13:54
  • @Ablia yes, readers in general are for reading text. It's not bad at all. But it does what it's documented to do, and you need to understand it. When reading a text file (which is quite common), a BufferedReader is appropriate. – JB Nizet Aug 16 '18 at 13:56
  • No, in fact I would prefer text, for development and debugging and verification. Though I would explicitly mention UTF-8 as encoding in InputStreamReader and OutputStreamWriter. The problem they do not mix well with binary data, especially when buffered. – Joop Eggen Aug 16 '18 at 13:58
  • @Ablia calling something bad because you don't know how it works is ... Unhelpful. They are more complicated and therefore require more advanced treatment. They are also far more performant. – Boris the Spider Aug 16 '18 at 13:59
  • Yeah, ok, i try for long to understand the difference between all those input/Output Streams and until there i just got "you should use this in this case, and not use this in that case". This answer and comments were really helpful, thank you all. And now i won't have a bunch of different input/output streams in my programm \o/. Also, everything is working better :) – Ablia Aug 16 '18 at 14:15
  • Do not feel too criticized; JB Nizet and Boris the Spiders are more or less the top brass around here. – Joop Eggen Aug 16 '18 at 14:17
  • 1
    The importance of flushing has nothing to do with "binary conversations". Flushing would also be necessary when using character-based communication using `Writer`. – Mark Rotteveel Aug 16 '18 at 14:34