Java——如何从输入流(socket/socketServer)读取未知数量的字节?

发布于 2024-11-01 16:37:46 字数 205 浏览 5 评论 0原文

希望使用 inputStream 通过套接字读取一些字节。服务器发送的字节数量可能是可变的,并且客户端事先并不知道字节数组的长度。如何实现这一点?


byte b[]; 
sock.getInputStream().read(b);

这会导致 Net BzEAnSZ 出现“可能未初始化错误”。帮助。

Looking to read in some bytes over a socket using an inputStream. The bytes sent by the server may be of variable quantity, and the client doesn't know in advance the length of the byte array. How may this be accomplished?


byte b[]; 
sock.getInputStream().read(b);

This causes a 'might not be initialized error' from the Net BzEAnSZ. Help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

夏至、离别 2024-11-08 16:37:46

您需要根据需要扩展缓冲区,通过读取字节块,一次读取 1024 个字节,如我不久前编写的示例代码所示

    byte[] resultBuff = new byte[0];
    byte[] buff = new byte[1024];
    int k = -1;
    while((k = sock.getInputStream().read(buff, 0, buff.length)) > -1) {
        byte[] tbuff = new byte[resultBuff.length + k]; // temp buffer size = bytes already read + bytes last read
        System.arraycopy(resultBuff, 0, tbuff, 0, resultBuff.length); // copy previous bytes
        System.arraycopy(buff, 0, tbuff, resultBuff.length, k);  // copy current lot
        resultBuff = tbuff; // call the temp buffer as your result buff
    }
    System.out.println(resultBuff.length + " bytes read.");
    return resultBuff;

You need to expand the buffer as needed, by reading in chunks of bytes, 1024 at a time as in this example code I wrote some time ago

    byte[] resultBuff = new byte[0];
    byte[] buff = new byte[1024];
    int k = -1;
    while((k = sock.getInputStream().read(buff, 0, buff.length)) > -1) {
        byte[] tbuff = new byte[resultBuff.length + k]; // temp buffer size = bytes already read + bytes last read
        System.arraycopy(resultBuff, 0, tbuff, 0, resultBuff.length); // copy previous bytes
        System.arraycopy(buff, 0, tbuff, resultBuff.length, k);  // copy current lot
        resultBuff = tbuff; // call the temp buffer as your result buff
    }
    System.out.println(resultBuff.length + " bytes read.");
    return resultBuff;
世界等同你 2024-11-08 16:37:46

假设发送方在数据结束时关闭流:

ByteArrayOutputStream baos = new ByteArrayOutputStream();

byte[] buf = new byte[4096];
while(true) {
  int n = is.read(buf);
  if( n < 0 ) break;
  baos.write(buf,0,n);
}

byte data[] = baos.toByteArray();

Assuming the sender closes the stream at the end of the data:

ByteArrayOutputStream baos = new ByteArrayOutputStream();

byte[] buf = new byte[4096];
while(true) {
  int n = is.read(buf);
  if( n < 0 ) break;
  baos.write(buf,0,n);
}

byte data[] = baos.toByteArray();
幸福丶如此 2024-11-08 16:37:46

读取一个int,它是正在接收的下一段数据的大小。创建具有该大小的缓冲区,或使用宽敞的预先存在的缓冲区。读入缓冲区,确保其限制在前面读取的大小。冲洗并重复:)

如果您真的如您所说提前不知道大小,请按照其他答案提到的那样读入扩展的 ByteArrayOutputStream 。不过,尺寸法确实是最可靠的。

Read an int, which is the size of the next segment of data being received. Create a buffer with that size, or use a roomy pre-existing buffer. Read into the buffer, making sure it is limited to the aforeread size. Rinse and repeat :)

If you really don't know the size in advance as you said, read into an expanding ByteArrayOutputStream as the other answers have mentioned. However, the size method really is the most reliable.

萌无敌 2024-11-08 16:37:46

无需重新发明轮子,使用 Apache Commons:

IOUtils.toByteArray(inputStream);

例如,带有错误处理的完整代码:

    public static byte[] readInputStreamToByteArray(InputStream inputStream) {
    if (inputStream == null) {
        // normally, the caller should check for null after getting the InputStream object from a resource
        throw new FileProcessingException("Cannot read from InputStream that is NULL. The resource requested by the caller may not exist or was not looked up correctly.");
    }
    try {
        return IOUtils.toByteArray(inputStream);
    } catch (IOException e) {
        throw new FileProcessingException("Error reading input stream.", e);
    } finally {
        closeStream(inputStream);
    }
}

private static void closeStream(Closeable closeable) {
    try {
        if (closeable != null) {
            closeable.close();
        }
    } catch (Exception e) {
        throw new FileProcessingException("IO Error closing a stream.", e);
    }
}

其中 FileProcessingException 是您的应用程序特定的有意义的 RT 异常,它将不间断地传输到正确的处理程序,不会污染之间的代码。

Without re-inventing the wheel, using Apache Commons:

IOUtils.toByteArray(inputStream);

For example, complete code with error handling:

    public static byte[] readInputStreamToByteArray(InputStream inputStream) {
    if (inputStream == null) {
        // normally, the caller should check for null after getting the InputStream object from a resource
        throw new FileProcessingException("Cannot read from InputStream that is NULL. The resource requested by the caller may not exist or was not looked up correctly.");
    }
    try {
        return IOUtils.toByteArray(inputStream);
    } catch (IOException e) {
        throw new FileProcessingException("Error reading input stream.", e);
    } finally {
        closeStream(inputStream);
    }
}

private static void closeStream(Closeable closeable) {
    try {
        if (closeable != null) {
            closeable.close();
        }
    } catch (Exception e) {
        throw new FileProcessingException("IO Error closing a stream.", e);
    }
}

Where FileProcessingException is your app-specific meaningful RT exception that will travel uninterrupted to your proper handler w/o polluting the code in between.

酒儿 2024-11-08 16:37:46

简单的答案是:

byte b[] = new byte[BIG_ENOUGH];
int nosRead = sock.getInputStream().read(b);

其中 BIG_ENOUGH 足够大。


但总的来说,这存在一个很大的问题。单个read调用不保证返回另一端已写入的所有内容。

  • 如果nosRead值为BIG_ENOUGH,您的应用程序无法确定是否还有更多字节即将到来;另一端可能恰好发送了 BIG_ENOUGH 字节...或超过 BIG_ENOUGH 字节。在前一种情况下,如果您尝试读取,您的应用程序将(永远)阻塞。在后一种情况下,您的应用程序必须(至少)执行另一次读取才能获取其余数据。

  • 如果 nosRead 值小于 BIG_ENOUGH,您的应用程序仍然不知道。它可能已收到所有数据,部分数据可能已延迟(由于网络数据包碎片、网络数据包丢失、网络分区等),或者另一端可能在发送数据的过程中阻塞或崩溃。< /p>

最好的答案是要么您的应用程序需要事先知道需要多少字节,或者应用程序协议需要以某种方式告诉应用程序如何预期的字节数或所有字节均已发送时。

可能的方法是:

  • 应用程序协议使用固定消息大小(不适用于您的示例)
  • 应用程序协议消息大小在消息标头中指定
  • 应用程序协议使用消息结束标记
  • 应用程序协议不是基于消息的,并且另一端关闭连接以表示结束

如果没有这些策略之一,您的应用程序就只能猜测,并且偶尔可能会出错。

然后您使用多个读取调用和(可能)多个缓冲区。

The simple answer is:

byte b[] = new byte[BIG_ENOUGH];
int nosRead = sock.getInputStream().read(b);

where BIG_ENOUGH is big enough.


But in general there is a big problem with this. A single read call is not guaranteed to return all that the other end has written.

  • If the nosRead value is BIG_ENOUGH, your application has no way of knowing for sure if there are more bytes to come; the other end may have sent exactly BIG_ENOUGH bytes ... or more than BIG_ENOUGH bytes. In the former case, you application will block (for ever) if you try to read. In the latter case, your application has to do (at least) another read to get the rest of the data.

  • If the nosRead value is less than BIG_ENOUGH, your application still doesn't know. It might have received everything there is, part of the data may have been delayed (due to network packet fragmentation, network packet loss, network partition, etc), or the other end might have blocked or crashed part way through sending the data.

The best answer is that EITHER your application needs to know beforehand how many bytes to expect, OR the application protocol needs to somehow tell the application how many bytes to expect or when all bytes have been sent.

Possible approaches are:

  • the application protocol uses fixed message sizes (not applicable to your example)
  • the application protocol message sizes are specified in message headers
  • the application protocol uses end-of-message markers
  • the application protocol is not message based, and the other end closes the connection to say that is the end.

Without one of these strategies, your application is left to guess, and is liable to get it wrong occasionally.

Then you use multiple read calls and (maybe) multiple buffers.

两个我 2024-11-08 16:37:46

将所有输入数据流式传输到输出流。这是工作示例:

    InputStream inputStream = null;
    byte[] tempStorage = new byte[1024];//try to read 1Kb at time
    int bLength;
    try{

        ByteArrayOutputStream outputByteArrayStream =  new ByteArrayOutputStream();     
        if (fileName.startsWith("http"))
            inputStream = new URL(fileName).openStream();
        else
            inputStream = new FileInputStream(fileName);            

        while ((bLength = inputStream.read(tempStorage)) != -1) {
                outputByteArrayStream.write(tempStorage, 0, bLength);
        }
        outputByteArrayStream.flush();
        //Here is the byte array at the end
        byte[] finalByteArray = outputByteArrayStream.toByteArray();
        outputByteArrayStream.close();
        inputStream.close();
    }catch(Exception e){
        e.printStackTrace();
        if (inputStream != null) inputStream.close();
    }

Stream all Input data into Output stream. Here is working example:

    InputStream inputStream = null;
    byte[] tempStorage = new byte[1024];//try to read 1Kb at time
    int bLength;
    try{

        ByteArrayOutputStream outputByteArrayStream =  new ByteArrayOutputStream();     
        if (fileName.startsWith("http"))
            inputStream = new URL(fileName).openStream();
        else
            inputStream = new FileInputStream(fileName);            

        while ((bLength = inputStream.read(tempStorage)) != -1) {
                outputByteArrayStream.write(tempStorage, 0, bLength);
        }
        outputByteArrayStream.flush();
        //Here is the byte array at the end
        byte[] finalByteArray = outputByteArrayStream.toByteArray();
        outputByteArrayStream.close();
        inputStream.close();
    }catch(Exception e){
        e.printStackTrace();
        if (inputStream != null) inputStream.close();
    }
冰雪之触 2024-11-08 16:37:46

要么:

  1. 让发送方在传输字节后关闭套接字。然后在接收方继续读取直到 EOS。

  2. 让发送者按照 Chris 的建议添加长度字前缀,然后读取那么多字节。

  3. 使用自描述协议,例如 XML、序列化...

Either:

  1. Have the sender close the socket after transferring the bytes. Then at the receiver just keep reading until EOS.

  2. Have the sender prefix a length word as per Chris's suggestion, then read that many bytes.

  3. Use a self-describing protocol such as XML, Serialization, ...

或十年 2024-11-08 16:37:46

使用 BufferedInputStream,并使用 available() 方法返回可供读取的字节大小,然后用该方法构造一个 byte[]尺寸。问题解决了。 :)

BufferedInputStream buf = new BufferedInputStream(is);  
int size = buf.available();

Use BufferedInputStream, and use the available() method which returns the size of bytes available for reading, and then construct a byte[] with that size. Problem solved. :)

BufferedInputStream buf = new BufferedInputStream(is);  
int size = buf.available();
一张白纸 2024-11-08 16:37:46

这是使用 ByteArrayOutputStream 的一个更简单的示例...

        socketInputStream = socket.getInputStream();
        int expectedDataLength = 128; //todo - set accordingly/experiment. Does not have to be precise value.
        ByteArrayOutputStream baos = new ByteArrayOutputStream(expectedDataLength);
        byte[] chunk = new byte[expectedDataLength];
        int numBytesJustRead;
        while((numBytesJustRead = socketInputStream.read(chunk)) != -1) {
            baos.write(chunk, 0, numBytesJustRead);
        }
        return baos.toString("UTF-8");

但是,如果服务器不返回 -1,您将需要以其他方式检测数据的结尾 - 例如,返回的内容可能总是以某个标记结尾(例如,“”),或者您可以使用socket.setSoTimeout()来解决。 (提到这一点似乎是一个常见问题。)

Here is a simpler example using ByteArrayOutputStream...

        socketInputStream = socket.getInputStream();
        int expectedDataLength = 128; //todo - set accordingly/experiment. Does not have to be precise value.
        ByteArrayOutputStream baos = new ByteArrayOutputStream(expectedDataLength);
        byte[] chunk = new byte[expectedDataLength];
        int numBytesJustRead;
        while((numBytesJustRead = socketInputStream.read(chunk)) != -1) {
            baos.write(chunk, 0, numBytesJustRead);
        }
        return baos.toString("UTF-8");

However, if the server does not return a -1, you will need to detect the end of the data some other way - e.g., maybe the returned content always ends with a certain marker (e.g., ""), or you could possibly solve using socket.setSoTimeout(). (Mentioning this as it is seems to be a common problem.)

暮倦 2024-11-08 16:37:46

这既是一个迟来的答案,也是自我广告,但任何检查这个问题的人可能想看看这里:
https://github.com/GregoryConrad/SmartSocket

This is both a late answer and self-advertising, but anyone checking out this question may want to take a look here:
https://github.com/GregoryConrad/SmartSocket

你的往事 2024-11-08 16:37:46

这个问题已经有7年历史了,但我在制作 NIO< 时遇到了类似的问题/a> 和 OIO 兼容系统(客户端和服务器可能是他们想要的任何东西,OIO 或 NIO)。

由于输入流阻塞,这就退出了挑战。

我找到了一种方法,这使得它成为可能,并且我想将其发布,以帮助有类似问题的人。

读取动态 sice 的字节数组是通过 完成的DataInputStream,它可以简单地包裹在socketInputStream 周围。另外,我不想引入特定的通信协议(比如首先发送将要发送的字节大小),因为我想让它尽可能简单。首先,我有一个简单的实用程序 Buffer 类,它看起来像这样:

import java.util.ArrayList;
import java.util.List;

public class Buffer {

    private byte[] core;
    private int capacity;

    public Buffer(int size){
        this.capacity = size;
        clear();
    }

    public List<Byte> list() {
        final List<Byte> result = new ArrayList<>();
        for(byte b : core) {
            result.add(b);
        }

        return result;
    }

    public void reallocate(int capacity) {
        this.capacity = capacity;
    }

    public void teardown() {
        this.core = null;
    }

    public void clear() {
        core = new byte[capacity];
    }

    public byte[] array() {
        return core;
    }
}

这个类仅存在,因为愚蠢的方式,字节 <=> Java 中的字节自动装箱可使用此列表。在这个例子中根本不需要这样做,但我不想在这个解释中遗漏一些东西。

接下来是 2 个简单的核心方法。在这些中,StringBuilder 用作“回调”。它将填充已读取的结果,并返回读取的字节数。当然,这可能会以不同的方式进行。

private int readNext(StringBuilder stringBuilder, Buffer buffer) throws IOException {
    // Attempt to read up to the buffers size
    int read = in.read(buffer.array());
    // If EOF is reached (-1 read)
    // we disconnect, because the
    // other end disconnected.
    if(read == -1) {
        disconnect();
        return -1;
    }
    // Add the read byte[] as
    // a String to the stringBuilder.
    stringBuilder.append(new String(buffer.array()).trim());
    buffer.clear();

    return read;
}

private Optional<String> readBlocking() throws IOException {
    final Buffer buffer = new Buffer(256);
    final StringBuilder stringBuilder = new StringBuilder();
    // This call blocks. Therefor
    // if we continue past this point
    // we WILL have some sort of
    // result. This might be -1, which
    // means, EOF (disconnect.)
    if(readNext(stringBuilder, buffer) == -1) {
        return Optional.empty();
    }
    while(in.available() > 0) {
        buffer.reallocate(in.available());
        if(readNext(stringBuilder, buffer) == -1) {
            return Optional.empty();
        }
    }

    buffer.teardown();

    return Optional.of(stringBuilder.toString());
}

第一个方法 readNext 将使用 DataInputStream 中的 byte[] 填充缓冲区,并返回以此方式读取的字节数。

在第二个方法中,readBlocking,我利用了阻塞性质,不用担心消费者-生产者-问题。只需readBlocking就会阻塞,直到收到新的字节数组。在调用这个阻塞方法之前,我们分配一个缓冲区大小。注意,我在第一次读取后调用了重新分配(在 while 循环内)。这是不需要的。您可以安全地删除此行,代码仍然可以工作。由于我的问题的独特性,我做到了。

我没有更详细解释的两件事是:
1. 在(DataInputStream 和这里唯一的短变量,抱歉)
2.断开连接(您的断开连接例程)

总而言之,您现在可以这样使用它:

// The in has to be an attribute, or an parameter to the readBlocking method
DataInputStream in = new DataInputStream(socket.getInputStream());
final Optional<String> rawDataOptional = readBlocking();
rawDataOptional.ifPresent(string -> threadPool.execute(() -> handle(string)));

这将为您提供一种通过套接字(或任何输入流)读取任何形状或形式的字节数组的方法。希望这有帮助!

This question is 7 years old, but i had a similiar problem, while making a NIO and OIO compatible system (Client and Server might be whatever they want, OIO or NIO).

This was quit the challenge, because of the blocking InputStreams.

I found a way, which makes it possible and i want to post it, to help people with similiar problems.

Reading a byte array of dynamic sice is done here with the DataInputStream, which kann be simply wrapped around the socketInputStream. Also, i do not want to introduce a specific communication protocoll (like first sending the size of bytes, that will be send), because i want to make this as vanilla as possible. First of, i have a simple utility Buffer class, which looks like this:

import java.util.ArrayList;
import java.util.List;

public class Buffer {

    private byte[] core;
    private int capacity;

    public Buffer(int size){
        this.capacity = size;
        clear();
    }

    public List<Byte> list() {
        final List<Byte> result = new ArrayList<>();
        for(byte b : core) {
            result.add(b);
        }

        return result;
    }

    public void reallocate(int capacity) {
        this.capacity = capacity;
    }

    public void teardown() {
        this.core = null;
    }

    public void clear() {
        core = new byte[capacity];
    }

    public byte[] array() {
        return core;
    }
}

This class only exists, because of the dumb way, byte <=> Byte autoboxing in Java works with this List. This is not realy needed at all in this example, but i did not want to leave something out of this explanation.

Next up, the 2 simple, core methods. In those, a StringBuilder is used as a "callback". It will be filled with the result which has been read and the amount of bytes read will be returned. This might be done different of course.

private int readNext(StringBuilder stringBuilder, Buffer buffer) throws IOException {
    // Attempt to read up to the buffers size
    int read = in.read(buffer.array());
    // If EOF is reached (-1 read)
    // we disconnect, because the
    // other end disconnected.
    if(read == -1) {
        disconnect();
        return -1;
    }
    // Add the read byte[] as
    // a String to the stringBuilder.
    stringBuilder.append(new String(buffer.array()).trim());
    buffer.clear();

    return read;
}

private Optional<String> readBlocking() throws IOException {
    final Buffer buffer = new Buffer(256);
    final StringBuilder stringBuilder = new StringBuilder();
    // This call blocks. Therefor
    // if we continue past this point
    // we WILL have some sort of
    // result. This might be -1, which
    // means, EOF (disconnect.)
    if(readNext(stringBuilder, buffer) == -1) {
        return Optional.empty();
    }
    while(in.available() > 0) {
        buffer.reallocate(in.available());
        if(readNext(stringBuilder, buffer) == -1) {
            return Optional.empty();
        }
    }

    buffer.teardown();

    return Optional.of(stringBuilder.toString());
}

The first method readNext will fill the buffer, with byte[] from the DataInputStream and return the amount bytes read this way.

In the secon method, readBlocking, i utilized the blocking nature, not to worry about consumer-producer-problems. Simply readBlocking will block, untill a new byte-array is received. Before we call this blocking method, we allocate a Buffer-size. Note, i called reallocate after the first read (inside the while loop). This is not needed. You can safely delete this line and the code will still work. I did it, because of the uniqueness of my problem.

The 2 things, i did not explain in more detail are:
1. in (the DataInputStream and the only short varaible here, sorry for that)
2. disconnect (your disconnect routine)

All in all, you can now use it, this way:

// The in has to be an attribute, or an parameter to the readBlocking method
DataInputStream in = new DataInputStream(socket.getInputStream());
final Optional<String> rawDataOptional = readBlocking();
rawDataOptional.ifPresent(string -> threadPool.execute(() -> handle(string)));

This will provide you with a way of reading byte arrays of any shape or form over a socket (or any InputStream realy). Hope this helps!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文