簡體   English   中英

Java - 如何從inputStream(socket / socketServer)讀取未知數量的字節?

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

希望使用inputStream在套接字上讀取一些字節。 服務器發送的字節數可能是可變的,客戶端事先並不知道字節數組的長度。 怎么可能完成?


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

這會導致Net BzEAnSZ出現“可能未初始化錯誤”。 救命。

你需要擴大緩沖區根據需要 ,通過一次以字節為單位,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;

假設發件人在數據末尾關閉流:

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

讀取一個int,它是接收的下一個數據段的大小。 創建具有該大小的緩沖區,或使用寬敞的預先存在的緩沖區。 讀入緩沖區,確保它僅限於上述大小。 沖洗並重復:)

如果你真的不像你說的那樣事先知道大小,請閱讀擴展的ByteArrayOutputStream,正如其他答案所提到的那樣。 但是,尺寸方法確實是最可靠的。

簡單的答案是:

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

BIG_ENOUGH夠大的地方。


但總的來說,這有一個很大的問題。 單個read調用不保證返回另一端寫入的所有內容。

  • 如果nosRead值為BIG_ENOUGH ,則您的應用程序無法確定是否還有更多字節; 另一端可能已經發送了BIG_ENOUGH字節...或者超過BIG_ENOUGH個字節。 在前一種情況下,如果您嘗試閱讀,您的應用程序將阻止(永遠)。 在后一種情況下,您的應用程序必須(至少)執行另一次read以獲取其余數據。

  • 如果nosRead值小於BIG_ENOUGH ,則您的應用程序仍然不知道。 它可能已收到所有內容,部分數據可能已被延遲(由於網絡數據包碎片,網絡數據包丟失,網絡分區等),或者另一端可能已通過發送數據阻止或崩潰。

最好的答案是, 要么你的應用程序需要預先知道多少字節期望, 應用協議需要以某種方式告訴應用程序有多少字節期望或當所有字節發送完畢。

可能的方法是:

  • 應用程序協議使用固定的郵件大小(不適用於您的示例)
  • 應用程序協議消息大小在消息頭中指定
  • 應用程序協議使用消息結束標記
  • 應用程序協議不是基於消息的,另一端關閉連接以表示結束

如果沒有這些策略之一,您的應用程序就會被猜測,並且偶爾會出錯。

然后你使用多個讀取調用和(可能)多個緩沖區。

無需重新發明輪子,使用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異常,它將不間斷地傳遞給您正確的處理程序,而不會污染其間的代碼。

將所有輸入數據流式傳輸到輸出流。 這是工作示例:

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

這是一個使用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()。 (提及這似乎是一個常見的問題。)

這是一個遲到的答案和自我廣告,但任何人都可以查看這個問題: https//github.com/GregoryConrad/SmartSocket

這個問題是7年了,但我有一個類似的問題,同時制作一個NIO和OIO兼容系統(客戶端和服務器可能是他們想要的任何東西,OIO或NIO)。

這是因為阻塞了InputStreams而退出了挑戰。

我找到了一種方法,這使得它成為可能,我想發布它,以幫助有類似問題的人。

在這里使用DataInputStream讀取動態sice的字節數組,該數據簡單地包含在socketInputStream中。 另外,我不想引入特定的通信協議1(比如首先發送將要發送的字節大小),因為我想盡可能地將其作為vanilla。 首先,我有一個簡單的實用程序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中的字節<=>字節自動裝箱與此List一起使用。 在這個例子中根本不需要這個,但我不想在這個解釋中留下一些東西。

接下來,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[]並返回以這種方式讀取的字節數。

在secon方法中, readBlocking ,我利用了阻塞性質,而不用擔心消費者 - 生產者問題 只需readBlocking將阻塞,直到接收到新的字節數組。 在我們調用這個阻塞方法之前,我們分配一個Buffer-size。 注意,我在第一次讀取后調用reallocate(在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)));

這將為您提供一種在套接字(或任何InputStream realy)上讀取任何形狀或形式的字節數組的方法。 希望這可以幫助!

或者:

  1. 在傳輸字節后讓發送方關閉套接字。 然后在接收器處繼續閱讀直到EOS。

  2. 根據Chris的建議,讓發件人為長度字加前綴,然后讀取那么多字節。

  3. 使用自描述協議,如XML,序列化,...

使用BufferedInputStream ,並使用available()方法返回可用於讀取的字節大小,然后構造一個具有該大小的byte[] 問題解決了。 :)

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM