简体   繁体   English

Java - 如何从inputStream(socket / socketServer)读取未知数量的字节?

[英]Java — How to read an unknown number of bytes from an inputStream (socket/socketServer)?

Looking to read in some bytes over a socket using an inputStream. 希望使用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. 这会导致Net BzEAnSZ出现“可能未初始化错误”。 Help. 救命。

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 你需要扩大缓冲区根据需要 ,通过一次以字节为单位,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;

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

Read an int, which is the size of the next segment of data being received. 读取一个int,它是接收的下一个数据段的大小。 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. 如果你真的不像你说的那样事先知道大小,请阅读扩展的ByteArrayOutputStream,正如其他答案所提到的那样。 However, the size method really is the most reliable. 但是,尺寸方法确实是最可靠的。

The simple answer is: 简单的答案是:

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

where BIG_ENOUGH is big enough. 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. 单个read调用不保证返回另一端写入的所有内容。

  • If the nosRead value is BIG_ENOUGH , your application has no way of knowing for sure if there are more bytes to come; 如果nosRead值为BIG_ENOUGH ,则您的应用程序无法确定是否还有更多字节; the other end may have sent exactly BIG_ENOUGH bytes ... or more than BIG_ENOUGH bytes. 另一端可能已经发送了BIG_ENOUGH字节...或者超过BIG_ENOUGH个字节。 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. 在后一种情况下,您的应用程序必须(至少)执行另一次read以获取其余数据。

  • If the nosRead value is less than BIG_ENOUGH , your application still doesn't know. 如果nosRead值小于BIG_ENOUGH ,则您的应用程序仍然不知道。 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. 然后你使用多个读取调用和(可能)多个缓冲区。

Without re-inventing the wheel, using Apache Commons: 无需重新发明轮子,使用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. 其中FileProcessingException是您的特定于应用程序的有意义的RT异常,它将不间断地传递给您正确的处理程序,而不会污染其间的代码。

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

Here is a simpler example using ByteArrayOutputStream... 这是一个使用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 - eg, maybe the returned content always ends with a certain marker (eg, ""), or you could possibly solve using socket.setSoTimeout(). 但是,如果服务器没有返回-1,则需要以其他方式检测数据的结尾 - 例如,返回的内容可能始终以某个标记结束(例如,“”),或者您可以解决使用socket.setSoTimeout()。 (Mentioning this as it is seems to be a common problem.) (提及这似乎是一个常见的问题。)

这是一个迟到的答案和自我广告,但任何人都可以查看这个问题: https//github.com/GregoryConrad/SmartSocket

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). 这个问题是7年了,但我有一个类似的问题,同时制作一个NIO和OIO兼容系统(客户端和服务器可能是他们想要的任何东西,OIO或NIO)。

This was quit the challenge, because of the blocking InputStreams. 这是因为阻塞了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. 在这里使用DataInputStream读取动态sice的字节数组,该数据简单地包含在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. 另外,我不想引入特定的通信协议1(比如首先发送将要发送的字节大小),因为我想尽可能地将其作为vanilla。 First of, i have a simple utility Buffer class, which looks like this: 首先,我有一个简单的实用程序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;
    }
}

This class only exists, because of the dumb way, byte <=> Byte autoboxing in Java works with this List. 这个类只存在,因为愚蠢的方式,Java中的字节<=>字节自动装箱与此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. 接下来,2个简单的核心方法。 In those, a StringBuilder is used as a "callback". 在那些中,StringBuilder用作“回调”。 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. 第一个方法readNext将填充缓冲区,使用DataInputStream中的byte[]并返回以这种方式读取的字节数。

In the secon method, readBlocking , i utilized the blocking nature, not to worry about consumer-producer-problems . 在secon方法中, readBlocking ,我利用了阻塞性质,而不用担心消费者 - 生产者问题 Simply readBlocking will block, untill a new byte-array is received. 只需readBlocking将阻塞,直到接收到新的字节数组。 Before we call this blocking method, we allocate a Buffer-size. 在我们调用这个阻塞方法之前,我们分配一个Buffer-size。 Note, i called reallocate after the first read (inside the while loop). 注意,我在第一次读取后调用reallocate(在while循环内)。 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) 我没有详细解释的两件事是:1。在(DataInputStream和这里唯一的短变量,对不起)2。断开(你的断开程序)

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). 这将为您提供一种在套接字(或任何InputStream realy)上读取任何形状或形式的字节数组的方法。 Hope this helps! 希望这可以帮助!

Either: 或者:

  1. Have the sender close the socket after transferring the bytes. 在传输字节后让发送方关闭套接字。 Then at the receiver just keep reading until EOS. 然后在接收器处继续阅读直到EOS。

  2. Have the sender prefix a length word as per Chris's suggestion, then read that many bytes. 根据Chris的建议,让发件人为长度字加前缀,然后读取那么多字节。

  3. Use a self-describing protocol such as XML, Serialization, ... 使用自描述协议,如XML,序列化,...

Use BufferedInputStream , and use the available() method which returns the size of bytes available for reading, and then construct a byte[] with that size. 使用BufferedInputStream ,并使用available()方法返回可用于读取的字节大小,然后构造一个具有该大小的byte[] Problem solved. 问题解决了。 :) :)

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM