3

NIOを使用して複数の同時接続を処理するマルチスレッド ゲームサーバー アプリケーションを作成しました。残念ながら、このサーバーは、最初のユーザーが接続するとすぐに、そのユーザーが実際にデータを送受信していない場合でも、1 つのコアで完全な CPU 負荷を発生させます。

以下は、私のネットワーク処理スレッドのコードです (読みやすくするために重要な部分に省略されています)。このクラスClientHandlerは、ゲーム メカニクスのネットワーク抽象化を行う独自のクラスです。以下の例の他のすべてのクラスはjava.nio.

ご覧のとおり、while(true)ループを使用しています。それについての私の理論は、キーが書き込み可能な場合、selector.select()すぐに返されclientHandler.writeToChannel()て呼び出されるというものです。しかし、ハンドラーが何も書き込まずに戻ると、キーは書き込み可能なままになります。次に、 select がすぐに再度呼び出され、すぐに戻ります。だから私は忙しいスピンを得ました。

clientHandlers によって送信されるデータがない限りスリープ状態になるようにネットワーク処理ループを設計する方法はありますか? 私のユースケースでは低レイテンシーが重要であることに注意してください。そのため、ハンドラーにデータがない場合、任意のミリ秒だけスリープさせることはできません。

ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
server.socket().bind(new InetSocketAddress(port));
Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
// wait for connections

while(true)
{
     // Wait for next set of client connections
    selector.select();
    Set<SelectionKey> keys = selector.selectedKeys();
    Iterator<SelectionKey> i = keys.iterator();
    while (i.hasNext()) {
        SelectionKey key = i.next();
        i.remove();

        if (key.isAcceptable()) {
            SocketChannel clientChannel = server.accept();
            clientChannel.configureBlocking(false);
            clientChannel.socket().setTcpNoDelay(true);
            clientChannel.socket().setTrafficClass(IPTOS_LOWDELAY);
            SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
            ClientHandler clientHanlder = new ClientHandler(clientChannel);
            clientKey.attach(clientHandler);
        }
        if (key.isReadable()) {
            // get connection handler for this key and tell it to process data 
            ClientHandler clientHandler = (ClientHandler) key.attachment();
            clientHandler.readFromChannel();
        }
        if (key.isWritable()) {
            // get connection handler and tell it to send any data it has cached 
            ClientHandler clientHandler = (ClientHandler) key.attachment();
            clientHandler.writeToChannel();
        }
        if (!key.isValid()) {
            ClientHandler clientHandler = (ClientHandler) key.attachment();
            clientHandler.disconnect();
        }
    }
}
4

2 に答える 2

8
SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);

問題はここにあります。SocketChannels は、ソケット送信バッファーがいっぱいでない限り、ほとんど常に書き込み可能です。したがって、通常は登録しOP_WRITE:ないでください。そうしないと、セレクターループがスピンします。次の場合にのみ登録する必要があります。

  1. 書きたいことがあり、
  2. 事前確率write()がゼロを返しました。
于 2013-02-14T23:59:47.177 に答える
6

読み取りと書き込みが同じセレクターで行われなければならない理由がわかりません。スレッドで 1 つのセレクターを読み取り/受け入れ操作に使用すると、新しいデータが到着するまで常にブロックされます。

次に、書き込み用に別のスレッドとセレクターを使用します。メッセージが書き込み可能なチャネルに送信される前に、メッセージを保存するためにキャッシュを使用していると述べています。実際には、チャネルが書き込み不可になるのは、カーネルのバッファーがいっぱいになった場合のみです。したがって、書き込み不可になることはほとんどありません。これを実装する良い方法は、メッセージが与えられ、スリープしている専用のライター スレッドを用意することです。interrupt()新しいメッセージを送信する必要があるときに ed にするかtake()、ブロッキング キューで を使用することができます。新しいメッセージが到着するたびに、ブロックを解除し、select()書き込み可能なすべてのキーに対して a を実行し、保留中のメッセージを送信します。チャネルが書き込み可能でないため、まれにメッセージをキャッシュに残す必要があります。

于 2013-02-14T18:26:37.543 に答える