Java 使用BufferedOutputStream/BufferedInputStream的套接字随机接收虚假数据

Java 使用BufferedOutputStream/BufferedInputStream的套接字随机接收虚假数据,java,sockets,tcp,stream,Java,Sockets,Tcp,Stream,我有一个客户机/服务器应用程序,它使用BufferedOutputStream/BufferedInputStream发送/接收数据。通信协议如下: 发送部分: 第一个字节是要执行的操作 接下来的4个字节是消息的长度 下一个x字节x=消息的长度是消息本身 接收部分: 读取第一个字节以获取操作 读取接下来的4个字节以获取消息长度 读取上一步字节上获得的x以获取消息 现在的问题是,有时当我在服务器端发送消息的长度ex:23045时,当我收到它时,我会得到一个巨大的整数ex:123106847 一个重

我有一个客户机/服务器应用程序,它使用BufferedOutputStream/BufferedInputStream发送/接收数据。通信协议如下:

发送部分:

第一个字节是要执行的操作 接下来的4个字节是消息的长度 下一个x字节x=消息的长度是消息本身 接收部分:

读取第一个字节以获取操作 读取接下来的4个字节以获取消息长度 读取上一步字节上获得的x以获取消息 现在的问题是,有时当我在服务器端发送消息的长度ex:23045时,当我收到它时,我会得到一个巨大的整数ex:123106847

一个重要的线索是,如果我发送了一个较小的消息(例如4-5k),则只有当消息超过10个字符时才会发生这种情况,一切正常

客户端发送部分outputStream/inputStream的类型为BufferedXXXStream:

    private String getResponseFromServer( NormalizerActionEnum action, String message) throws IOException{

        writeByte( action.id());
        writeString( message);
        flush(;

        return read();
    }

    private String read() throws IOException{
        byte[] msgLen = new byte[4];
        inputStream.read(msgLen);
        int len = ByteBuffer.wrap(msgLen).getInt();
        byte[] bytes = new byte[len];
        inputStream.read(bytes);

        return new String(bytes);
    }

    private void writeByte( byte msg) throws IOException{
        outputStream.write(msg);
    }

    private void writeString( String msg) throws IOException{

        byte[] msgLen = ByteBuffer.allocate(4).putInt(msg.length()).array();

        outputStream.write(msgLen);
        outputStream.write(msg.getBytes());
    }

    private void flush() throws IOException{
        outputStream.flush();
    }
服务器部分_input/_output的类型为BufferedXXXStream

private byte readByte() throws IOException, InterruptedException {
    int b =  _input.read();
    while(b==-1){
        Thread.sleep(1);
        b = _input.read();
    }

    return (byte) b;
}

private String readString() throws IOException, InterruptedException {
    byte[] msgLen = new byte[4];
    int s = _input.read(msgLen);
    while(s==-1){
        Thread.sleep(1);
        s = _input.read(msgLen);
    }   

    int len = ByteBuffer.wrap(msgLen).getInt();     
    byte[] bytes = new byte[len];
    s = _input.read(bytes);
    while(s==-1){
        Thread.sleep(1);
        s = _input.read(bytes);
    }

    return new String(bytes);
}

private void writeString(String message) throws IOException {
    byte[] msgLen = ByteBuffer.allocate(4).putInt(message.length()).array();
    _output.write(msgLen);
    _output.write(message.getBytes());
    _output.flush();
}

....

byte cmd = readByte();
String message = readString();
任何帮助都将不胜感激。如果你需要更多的细节,请告诉我

更新:由于Jon Skeet和EJP的评论,我意识到服务器上的读取部分有一些无意义的操作,但抛开这一点,我最终得到了问题所在:关键是我在应用程序的整个长度上保持流打开,并且在发送消息长度的前几次,我能够在服务器上读取它服务器端,但正如Jon Skeet指出的,数据不会一次全部到达,所以当我再次尝试读取消息长度时,我实际上是从消息本身读取的,这就是为什么我有虚假的消息长度

~我没有发送数据长度,然后一次读取所有数据,而是发送了没有长度的数据,然后一次读取一个字节,直到字符串结束,这非常有效

private String readString() throws IOException, InterruptedException {
    StringBuilder sb = new StringBuilder();
    byte[] bytes = new byte[100];
    int s = 0;
    int index=0;
    while(true){
        s = _input.read();
        if(s == 10){
            break;
        }
        bytes[index++] = (byte) (s);
        if(index == bytes.length){
            sb.append(new String(bytes));
            bytes = new byte[100];
            index=0;
        }           
    }
    if(index > 0){
        sb.append(new String(Arrays.copyOfRange(bytes, 0, index)));
    }

    return sb.toString();
}
看看这个:

byte[] bytes = new byte[len];
s = _input.read(bytes);
while(s==-1){
    Thread.sleep(1);
    s = _input.read(bytes);
}

return new String(bytes);
首先,循环是没有意义的:read将返回-1的唯一时间是它是否关闭,在这种情况下,循环不会帮助您

其次,您忽略了数据将以多个块的形式出现的可能性。你假设如果你已经获得了任何数据,那么你就拥有了所有的数据。相反,您应该这样循环:

int bytesRead = 0;
while (bytesRead < bytes.length) {
    int chunk = _input.read(bytes, bytesRead, bytes.length - bytesRead);
    if (chunk == -1) {
        throw new IOException("Didn't get as much data as we should have");
    }
    bytesRead += chunk;
}
请注意,所有其他InputStream.read调用也假定您已经成功读取了数据,并且确实已经读取了所需的所有数据

哦,您使用平台默认编码在二进制数据和文本数据之间进行转换,这不是一个好主意

有没有任何理由不使用DataInputStream和DataOutputStream来进行此操作?目前,您正在重新发明轮子,并使用Bug进行改造。

看看这个:

byte[] bytes = new byte[len];
s = _input.read(bytes);
while(s==-1){
    Thread.sleep(1);
    s = _input.read(bytes);
}

return new String(bytes);
首先,循环是没有意义的:read将返回-1的唯一时间是它是否关闭,在这种情况下,循环不会帮助您

其次,您忽略了数据将以多个块的形式出现的可能性。你假设如果你已经获得了任何数据,那么你就拥有了所有的数据。相反,您应该这样循环:

int bytesRead = 0;
while (bytesRead < bytes.length) {
    int chunk = _input.read(bytes, bytesRead, bytes.length - bytesRead);
    if (chunk == -1) {
        throw new IOException("Didn't get as much data as we should have");
    }
    bytesRead += chunk;
}
请注意,所有其他InputStream.read调用也假定您已经成功读取了数据,并且确实已经读取了所需的所有数据

哦,您使用平台默认编码在二进制数据和文本数据之间进行转换,这不是一个好主意


有没有任何理由不使用DataInputStream和DataOutputStream来进行此操作?目前,您正在重新发明轮子,并使用bug进行操作。

您发送的代码存在bug:

byte[] msgLen = ByteBuffer.allocate(4).putInt(message.length()).array();
_output.write(msgLen);
_output.write(message.getBytes());
您将字符数作为消息长度发送,但之后将消息转换为字节。取决于平台编码字符串。getBytes可以为您提供比字符多得多的字节


永远不要假设String.length与String.getBytes.length有任何关系!这些是不同的概念,决不能混用。

您发送的代码有错误:

byte[] msgLen = ByteBuffer.allocate(4).putInt(message.length()).array();
_output.write(msgLen);
_output.write(message.getBytes());
您将字符数作为消息长度发送,但之后将消息转换为字节。取决于平台编码字符串。getBytes可以为您提供比字符多得多的字节


永远不要假设String.length与String.getBytes.length有任何关系!这些是不同的概念,不应混淆。

+1 thx对于输入,您是对的,我希望一次收到所有数据,因为我刷新了所有数据,我希望我可以应用区块管理,但问题在于len:在相同情况下,消息的长度是虚假的。参考DataInputStream这是我的第一种方法,但与缓冲区相比,性能较慢approach@Stephan:您可以在DataInputStream中包装BufferedInputStream,同样,对于DataOutputStream和缓冲输出流。不过别忘了冲水!是的,我想到了这一点,但我担心性能会因为额外的封装而下降,但我会尝试一下,如果
消息的长度是加扰的?+1 thx输入,你是对的,我希望一次收到所有数据,因为我刷新了所有数据,我希望我可以应用区块管理,但问题在于len:在相同情况下,消息的长度是虚假的。参考DataInputStream这是我的第一种方法,但与缓冲区相比,性能较慢approach@Stephan:您可以在DataInputStream中包装BufferedInputStream,同样,对于DataOutputStream和缓冲输出流。不过别忘了冲水!是的,我想到了这一点,但我担心性能会因为额外的封装而下降,但我会尝试一下,如果消息的长度被打乱了,你知道如何管理块吗?睡眠是没有意义的。读取已存在的块,直到输入可用。当返回值为-1时,睡眠和重读是毫无意义的,因为肯定不会有更多的东西要读。你是说当s!=-1?@EJP你说的对,我知道现在这毫无意义,但撇开这一点不谈,主要问题是我发送的长度,而不是我收到的长度,是一些随机情况。睡眠是毫无意义的。读取已存在的块,直到输入可用。当返回值为-1时,睡眠和重读是毫无意义的,因为肯定不会有更多的东西要读。你是说当s!=-1?@EJP你说的对,我知道现在这毫无意义,但撇开这一点不谈,主要问题是我发送的长度不是我收到的长度是一些随机的情况我也想到了,但事实并非如此,因为:1。它在客户端和服务器2上都是相同的平台。在同样的情况下,它在另一种情况下不起作用t@Stephan这并不是平台的不同,尽管这也是令人头痛的一个潜在原因,但是根据需要清除的字符串内容,String.length和String.getBytes.length可以给出不同的值:a.length==a.getBytesUTF-8.length,但\u20AC.length!=\u20AC.getBytesUTF-8.length。你的代码被窃听了,这就是为什么你会得到看似随机的结果。。。我使用了.getBytes.length而不是.length,但问题是相同的读取方法也有一个类似的常见错误,Jon Skeet已经指出了这一错误:您不能确保您确实读取了消息长度指定的字节数。Jon Skeet还显示了代码,以确保在第二个代码示例中读取指定的字节数。我想你已经修好了,但可能它的有效分数超过了?+1,尽管我在寻找答案,所以我终于明白了问题是什么,关键是我在应用程序的整个长度上保持流的打开状态,最初几次我发送消息长度时,我能够在服务器端读取它,但正如Jon Skeet指出的,数据不会一次全部到达,所以当我再次尝试读取消息长度时,我实际上是在从服务器端读取信息本身这就是为什么我有虚假的信息长度我也这么想,但事实并非如此,因为:1。它在客户端和服务器2上都是相同的平台。在同样的情况下,它在另一种情况下不起作用t@Stephan这并不是平台的不同,尽管这也是令人头痛的一个潜在原因,但是根据需要清除的字符串内容,String.length和String.getBytes.length可以给出不同的值:a.length==a.getBytesUTF-8.length,但\u20AC.length!=\u20AC.getBytesUTF-8.length。你的代码被窃听了,这就是为什么你会得到看似随机的结果。。。我使用了.getBytes.length而不是.length,但问题是相同的读取方法也有一个类似的常见错误,Jon Skeet已经指出了这一错误:您不能确保您确实读取了消息长度指定的字节数。Jon Skeet还显示了代码,以确保在第二个代码示例中读取指定的字节数。我想你已经修好了,但可能它的有效分数超过了?+1,尽管我在寻找答案,所以我终于明白了问题是什么,关键是我在应用程序的整个长度上保持流的打开状态,最初几次我发送消息长度时,我能够在服务器端读取它,但正如Jon Skeet指出的,数据不会一次全部到达,所以当我再次尝试读取消息长度时,我实际上是在从服务器端读取消息本身,这就是为什么我有虚假的消息长度