【问题标题】:DataOutputStream works only preceded by System.out.printlnDataOutputStream 仅在 System.out.println 之前有效
【发布时间】:2018-08-16 21:55:47
【问题描述】:

我在输入/输出流方面遇到了另一个问题。在这里,我将数据从服务器发送到客户端。在发送数据之前,服务器会发送一个小字符串来告诉客户端他将发送什么,这样客户端就知道他应该使用哪个函数来接收。

我很好地接收了第一个字符串,但是我没有得到好的整数,之后我收到的第二个字符串是“null”。

此外,如果我在将 DataOutputStream 与 dos.writeInt 一起使用之前执行 System.out.println,则一切正常。 我不明白。这是代码:

服务器:

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();
        }
    }
}

客户:

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();
        }       
    }
}

这是我在不执行 System.out.println 时得到的结果(在客户端控制台中):

keyNumber
1801812234

null

我总是得到同一个奇怪的数字;我尝试将其转换为 ASCII,但它不可读。

有什么建议吗?

【问题讨论】:

  • BufferedReader的原理是什么?它的 javadoc 说什么?调用 readLine() 会发生什么?
  • 嗯,有人告诉我,当我在一端使用 PrintWriter 时,我应该在另一端使用 BufferedReader。但我也读到 BufferedReader 有点危险,因为他们可以阅读比我们希望他们阅读的内容更多的内容。啊,通常我总是在输出的末尾打印“\r\n”;在此处添加它会导致 keyNumber 被正确接收,第二个字符串也是如此。但是我的“receiveKey”函数的“readInt”行仍然出错。
  • 我还读到 BufferedReader 有点危险,因为它们可以读取比我们希望它们读取的更多的内容。对,就是这样。它设计就是为了做到这一点:你要求一行,它会将 8096 个字符(或任何大小的缓冲区)读入 缓冲区 以加快处理速度。所以,在你读完这一行之后,实际上你从输入流中读到的不仅仅是一行。因此,您接下来读取的 int 实际上是这 8096 个字符之后的 4 个字节,而不是您编写的 int。
  • 最简单的解决方法是更改​​协议。对类型使用单个字节(这会产生 256 种可能的类型,这可能比您需要的要多得多),并且只在一侧使用 DataOutputSTream,在另一侧使用 DataInputStream。
  • 我以后还要使用 ObjectInputStream。这也会是个问题吗?

标签: java system.out dataoutputstream


【解决方案1】:

在这种发送二进制数据的情况下,完全使用 DataOutputStream。 (或者,您可以使用文本。)

private static DataOutputStream out;

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

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

刷新对于二元对话很重要。

包装类 DataOutputStream、Printer、BufferedReader 等的问题在于它们会启动自己的“光标”并且会在自己关闭时关闭包装的 I/O。拥有多个 DataOutputStream 有点令人担忧;至少不像预期的那样。

顺便说一句,我的正常模式是在mainnew Server().exec(); 或类似的。 这将消除所有这些静态因素。

【讨论】:

  • 好的,谢谢。但是如果 BufferedReader 这么差,那它有什么用呢?还有Printwriter?我们应该只在发送字符串时使用它们吗?哦,别担心:我不是真的这样做,它只是一个最小且可用的版本,当我遇到错误时运行一些测试并能够将其作为可运行代码发布在这里,只有有问题部分。
  • @Ablia 是的,读者一般都是为了阅读文本。这一点都不坏。但它做了它记录在案的事情,你需要理解它。读取文本文件(这很常见)时,使用 BufferedReader 是合适的。
  • 不,实际上我更喜欢文本,用于开发和调试和验证。虽然我会在 InputStreamReader 和 OutputStreamWriter 中明确提到 UTF-8 作为编码。它们不能很好地与二进制数据混合的问题,尤其是在缓冲时。
  • @Ablia 称某事不好,因为你不知道它是如何工作的……没有帮助。它们更复杂,因此需要更先进的治疗。它们的性能也要好得多。
  • 刷新的重要性与“二进制对话”无关。当使用Writer 使用基于字符的通信时,也需要刷新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-25
相关资源
最近更新 更多