java nioでセレクタにsocketchannelキーを変更させる方法

私は使い方Java nioに関する質問を持っています。Javaの知識が豊富な人が誤解を明確にするのに役立つことを願っています。

私はJavaのnioソケットを使用しています。書き込みバッファがsocketchannel.write()を使用して埋められる可能性があります。この場合、残りのバッファはキューに入れられ、キーはOP_WRITEに変更されます。私のシナリオの1つは、待ち行列の長さがかなり長いことです。コールセレクタ.select()の前に、pendingRequestという別のキューからOP_WRITEにキーを変更します。しかし、読み込み処理が非常に遅いので、送信処理が完了した後、多くのメッセージが書き込まれていないので、まだキューに入っています。この問題をどのように処理するのですか?

私のコードでは、私は2つの書き込み場所があります。 1つはジェネレータからのものです。パブリッシュするメッセージがあるときは、直接チャネルに書き込みます。バッファがいっぱいになると、データはキューに入れられます。 2番目の場所はディスパッチャです。キーが書き込み可能な場合、write()を呼び出してキューに入れられたデータを書き込みます。私は2つの部分が書き込みのために競争することができると思います。私は自分のコードに2つの書き込みを協力させるための処理がないと感じています。

上記の私の問題を解決する解決策はありますか?私は私のコードでは、多くのキューに入れられたデータを書き出すことができません。キーが書き込み可能な場合、ジェネレータはデータを再度書き込む可能性があり、キューに入れられたデータの書き換えが少なくなります。この部分を正しく作るには?ありがとう

// WriteListener()では、記述コードは以下の3つの部分

   public synchronized int writeData(EventObject source) {      
    int n = 0; 
    int count = 0;

    SocketChannel socket = (SocketChannel)source.getSource();       
    ByteBuffer buffer = ((WriteEvent)source).getBuffer();   
    try {
        write(socket);
    } catch (IOException e1) {          
        e1.printStackTrace();
    }       

    while (buffer.position()>0) {   
        try {           
                buffer.flip();  
                n = socket.write(buffer);                                   
                if(n == 0) {
                        key.interestOps(SelectionKey.OP_WRITE);                         synchronized (this.pendingData) {  
                            List queue = (List) this.pendingData.get(socket); 
                            if(queue == null) {
                                queue = new ArrayList();
                                this.pendingData.put(socket, queue); 
                        }
                        queue.add(buffer);

                        logger.logInfo("queue length:" + queue.size());
                    }                                               
                    break;
                }               
                count += n; 

        } catch (IOException e) {               
            e.printStackTrace();
        }   finally {                       
            buffer.compact();              
        }
    }   

    if(buffer.position()==0) {                      
        key.interestOps(SelectionKey.OP_READ);                  
    }
            return count;   

}   

//このメソッドは、キューに入れられたバッファを書き込むために使用されます

  public synchronized int write(SocketChannel sc, ByteBuffer wbuf) {        
    int n = 0; 
    int count = 0;

    SelectionKey key = sc.keyFor(this.dispatcher.getDemultiplexer().getDemux());                
    while (wbuf.position()>0) {     
        try {           
            wbuf.flip();        

            n = sc.write(wbuf);             

            if(n == 0) {    
                   key.interestOps(SelectionKey.OP_WRITE);                                  
                    synchronized (this.pendingData) {  
                        List queue = (List) this.pendingData.get(sc); 
                        if(queue == null) {
                                queue = new ArrayList();
                                this.pendingData.put(sc, queue); 
                        }
                        queue.add(wbuf);
                    }

                    break;
                }               
                count += n; 

        } catch (IOException e) {               
            e.printStackTrace();
        }   finally {               

            wbuf.compact();                
        }
    }   

    if(wbuf.position()==0) {    
        wbuf.clear();               
        key.interestOps(SelectionKey.OP_READ);          
    }

return n;       
}   

// ====このメソッドは、key.isWritable()がtrueの場合にDispatchのコールバックです。

public void write(SocketChannel socketChannel) throws IOException {         
   SelectionKey key = socketChannel.keyFor(this.dispatcher.getDemultiplexer().getDemux());     
    synchronized (this.pendingData) {             
        List queue = (List) this.pendingData.get(socketChannel);              
        if(queue == null || queue.isEmpty()) {                 
           //We wrote away all data, so we're no longer interested                 
           //in writing on this socket. Switch back to waiting for  data.                 
            try {                     
                if (key!=null)                         
                    key.interestOps(SelectionKey.OP_READ);                 
            } catch(Exception ex) {                     
                if (key!=null)                         
                    key.cancel();                 
                }             
        }           

       //Write until there's not more data ...    
        int n = 0;
        while (queue != null && !queue.isEmpty()) {                 
            ByteBuffer buf = (ByteBuffer) queue.get(0);   
           //zero length write, break the loop and wait for next writable time 
            n = write(socketChannel, buf);

            logger.logInfo("queue length:" + queue.size() + " used time: " + (t2-t1) + " ms.");

            if(n==0)  {             
                break;
            }
                      queue.remove(0); 

        }        

 }   
0
最終的な方法は、キューを書き込むメソッドで、 if(n == 0)であってもバッファを常にキューから削除します。そのテストを行い、バッファを削除する前に 破棄しなければなりません。実際にはバッファが空であれば削除するだけです。現時点ではあなたはデータを失っています。
追加された 著者 EJP,
あなたが持っている問題を示すいくつかのコード(できれば SSCCE )を投稿してください。
追加された 著者 Brian Roach,
現在のコードでは、書き込まれたバイト数numbrをテストした後にremove(0)が置かれます。現在のところ、コードは機能しますが、パフォーマンスはあまり良くありません。バッファがいっぱいになってから次の書き込み可能な時間がかかるまでにどれくらいかかりますか?
追加された 著者 susan,

2 答え

  1. 問題が解決しない場合は、実際には2つの選択肢しかありません。誤動作の理由でクライアントを切断するか、バックログが消去されるまで出力を停止します。両方の可能性があります。

最初のvia経由で長いselect()タイムアウトを使用することができます。 select()がゼロを返す場合は、登録されたチャンネルがないか、タイムアウト期間中に何も起こっていないことを意味します。その場合、すべてのクライアントから切断することを考えてください。同時に多くのクライアントが働きすぎて仕事ができなくなった場合は、各チャンネルが最後に選択された時間を記録し、最後の活動時間が長すぎるチャンネルを切断する必要があります。

そのタイムアウト期間内に、彼が遅い読書中にその男のための出力を止めたい場合もあります 。

読者のための「長め」の正確な定義は練習問題として残されていますが、最初の近似として10分が心に浮かんでいます。

0
追加された
私はyorヘルプに非常に感謝します。ジェネレータ側で次のメッセージを生成する前に、メッセージを書き出すことによって動作します。しかし、他のプロセスでは、バッファがいっぱいであれば、unitlを待ってから再度書き込む余地があります。
追加された 著者 susan,

コンシューマーが遅すぎる場合は、サーバーを保護するためにコンシューマーを切断することが唯一の選択肢となります。悪い消費者があなたの他のクライアントに影響を及ぼすことは望ましくありません。

私は通常、送信バッファのサイズを増やして、接続が閉じられるようにします。これにより、Javaコードでの未書き込みデータの受け渡しの複雑さを避けることができます。実際に行っていることは、バッファを少しずつ拡張しているからです。送信バッファサイズを大きくすると、これは透過的に行われます。送信バッファサイズで再生する必要がない場合もありますが、デフォルトは通常約64 KBです。

0
追加された
@susan ServerSocketでは、受け入れられたソケットによって継承される受信バッファーのサイズを変更できるため、64KB以上に設定することができます。それを受け入れたソケットにしようとすると、> 64kが接続ハンドシェイク中にネゴシエートされるTCP 'ウィンドウスケーリング'オプションを必要とするため、失敗します。同じ理由で、クライアントソケットに64kを超える受信バッファを設定したい場合は、接続する前にそれを行う必要があります。送信バッファは、プロトコルの助けを必要としないため、いつでも設定できます。
追加された 著者 EJP,
@susanの修正:これは失敗しませんが、接続の前にサイズが設定されていない限り、64kを超える部分は使用されません。
追加された 著者 EJP,
送信バッファサイズを変更することを提案するもう1つの理由は、コンシューマーに対する制御が少ないという仮定です。なぜなら、送信しているデータを消費するのに十分な速さで実行していないからです。私が持っている "解決策"は、遅い消費者への警告を記録し、接続を閉じます。送信者バッファを潜在的に増加させる理由は、偽陰性を減らすことです。つまり、実際に何か間違っていない限り起こりません。
追加された 著者 Peter Lawrey,
私は以下を使用してsocketchannel buffersizeを変更します。しかし、なぜserverSocketが受信バッファーサイズだけを設定できるのですか?ソケットソケット()。setReceiveBufferSize(256 * 1024);ソケットソケット()。setSendBufferSize(256 * 1024);
追加された 著者 susan,
どうもありがとうございました。
追加された 著者 susan,
上記の私の問題を解決する解決策はありますか?私は私のコードでは、多くのキューに入れられたデータを書き出すことができません。キーが書き込み可能な場合、ジェネレータはデータを再度書き込む可能性があり、キューに入れられたデータの書き換えが少なくなります。この部分を正しく作るには?ありがとう。
追加された 著者 susan,
私はyorヘルプに非常に感謝します。ジェネレータ側で次のメッセージを生成する前に、メッセージを書き出すことによって動作します。しかし、他のプロセスでは、バッファがいっぱいであれば、unitlを待ってから再度書き込む余地があります。
追加された 著者 susan,