web-dev-qa-db-ja.com

Java-inputStream(socket / socketServer)から不明なバイト数を読み取る方法は?

InputStreamを使用してソケットを介していくつかのバイトを読み取ることを探しています。サーバーによって送信されるバイトは可変量である可能性があり、クライアントはバイト配列の長さを事前に知りません。これはどのように達成できますか?


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

これにより、Net BzEAnSZから「初期化されない可能性があるエラー」が発生します。助けて。

14
farm ostrich

Intを読み取ります。これは、受信するデータの次のセグメントのサイズです。そのサイズのバッファーを作成するか、既存の広いバッファーを使用します。バッファに読み込み、先読みしたサイズに制限されていることを確認します。すすぎと繰り返し:)

実際に前に言ったようにサイズがわからない場合は、他の答えが述べているように、拡大するByteArrayOutputStreamを読んでください。ただし、サイズ方法は実際に最も信頼できます。

11
Chris Dennett

バッファを拡張する必要があります必要に応じて、先ほど書いたこのサンプルコードのように、一度に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;
24
d-live

送信者がデータの最後でストリームを閉じると仮定します:

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

簡単な答えは:

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より小さい場合、アプリケーションstillはわかりません。存在するすべてのものを受信した可能性があり、データの一部が遅延した可能性があります(ネットワークパケットの断片化、ネットワークパケット損失、ネットワークパーティションなど)、またはデータ送信の途中で相手側がブロックまたはクラッシュした可能性があります。

最良の答えは、[〜#〜] either [〜#〜]アプリケーションが何バイトを予想するかを事前に知る必要があることです[〜#〜] or [〜#〜]アプリケーションプロトコルは何らかの方法で必要ですtellアプリケーションに期待するバイト数、またはすべてのバイトが送信されたとき。

可能なアプローチは次のとおりです。

  • アプリケーションプロトコルは固定メッセージサイズを使用します(例には適用されません)
  • アプリケーションプロトコルのメッセージサイズは、メッセージヘッダーで指定されます。
  • アプリケーションプロトコルはメッセージの終わりマーカーを使用します
  • アプリケーションプロトコルはメッセージベースではないため、もう一方の端は接続を閉じて、つまり終了と言います。

これらの戦略のいずれかがなければ、アプリケーションは推測する必要があり、ときどき間違える可能性があります。

次に、複数の読み取り呼び出しと(おそらく)複数のバッファーを使用します。

8
Stephen C

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例外で、その間はコードを汚染せずに適切なハンドラーに中断せずに移動します。

6
constv

すべての入力データを出力ストリームにストリーミングします。これが実際の例です:

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

この質問は7歳ですが、 [〜#〜] nio [〜#〜] およびOIO互換システム(クライアントとサーバーは何でもかまいませんが、OIOまたはNIO)。

InputStreamsがブロックされているため、これは挑戦をやめました。

似たような問題を持つ人々を助ける方法を見つけました。それを可能にし、投稿したいと思います。

ダイナミックサイスのバイト配列の読み取りは、ここで DataInputStream を使用して行われます。これは、単にsocketInputStreamをラップするだけです。また、特定の通信プロトコル(最初に送信するバイトサイズを送信するなど)を導入したくないのは、これを可能な限り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のバイト<=>バイトオートボクシングは、このリストで動作します。この例では、これはまったく必要ありませんが、この説明から何かを省いてください。

次に、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では、 consumer-producer-problems を心配することなく、ブロッキングの性質を利用しました。単にreadBlockingは、新しいバイト配列が受信されるまでブロックします。このブロッキングメソッドを呼び出す前に、Buffer-sizeを割り当てます。最初の読み取りの後(whileループ内)でreallocateを呼び出しました。これは必要ありません。この行を安全に削除でき、コードは引き続き機能します。私の問題の一意性のため、私はそれをしました。

2つのこと、私は詳細に説明しませんでした:1. in(DataInputStreamとここで唯一の短い変数、ごめんなさい)2. disconnect(切断ルーチン)

全体として、次のように使用できます。

// 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)上の任意の形状または形式のバイト配列を読み取る方法が提供されます。お役に立てれば!

0
Thorben Kuck

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()を使用します。 (これをそのまま記載するのはよくある問題のようです。)

0

どちらか:

  1. バイトを転送した後、送信者にソケットを閉じてもらいます。その後、受信機でEOSまで読み続けるだけです。

  2. クリスの提案に従って、送信者にWordの長さのプレフィックスを付けてから、そのバイト数を読み取ります。

  3. XML、シリアル化などの自己記述型プロトコルを使用します...

0
user207421

これは遅い回答であり、自己広告でもありますが、この質問をチェックしている人はここを見てみたいかもしれません: https://github.com/GregoryConrad/SmartSocket

0
Flare Cat

BufferedInputStreamを使用し、読み取りに使用できるバイトのサイズを返すavailable()メソッドを使用して、byte[]そのサイズで。問題が解決しました。 :)

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