2

NIO ライブラリを使用して、Java で単純な ChatServer を作成するのに苦労しています。誰かが私を助けることができるだろうか。私は SocketChannel と Selector を使用して、単一のスレッドで複数のクライアントを処理しています。問題は、新しい接続を受け入れてそのデータを取得することはできますが、データを送り返そうとすると、SocketChannel が機能しないことです。メソッド write() では、渡すデータと同じサイズの整数を返しますが、クライアントはそのデータを受け取りません。奇妙なことに、サーバー アプリケーションを閉じると、クライアントがデータを受け取ります。ソケットチャネルがバッファを維持しているようで、アプリケーションを閉じたときにのみフラッシュされます。

役立つ情報をさらに提供するために、いくつかの詳細を以下に示します。このコードでイベントを処理しています:

private void run() throws IOException {

    ServerSocketChannel ssc = ServerSocketChannel.open();

    // Set it to non-blocking, so we can use select
    ssc.configureBlocking( false );

    // Get the Socket connected to this channel, and bind it
    // to the listening port
    this.serverSocket = ssc.socket();
    InetSocketAddress isa = new InetSocketAddress( this.port );
    serverSocket.bind( isa );

    // Create a new Selector for selecting
    this.masterSelector = Selector.open();

    // Register the ServerSocketChannel, so we can
    // listen for incoming connections
    ssc.register( masterSelector, SelectionKey.OP_ACCEPT );

    while (true) {
        // See if we've had any activity -- either
        // an incoming connection, or incoming data on an
        // existing connection
        int num = masterSelector.select();

        // If we don't have any activity, loop around and wait
        // again
        if (num == 0) {
            continue;
        }

        // Get the keys corresponding to the activity
        // that has been detected, and process them
        // one by one
        Set keys = masterSelector.selectedKeys();
        Iterator it = keys.iterator();
        while (it.hasNext()) {
            // Get a key representing one of bits of I/O
            // activity
            SelectionKey key = (SelectionKey)it.next();

            // What kind of activity is it?
            if ((key.readyOps() & SelectionKey.OP_ACCEPT) ==
                SelectionKey.OP_ACCEPT) {

                // Aceita a conexão
                Socket s = serverSocket.accept();

                System.out.println( "LOG: Conexao TCP aceita de " + s.getInetAddress() + ":" + s.getPort() );

                // Make sure to make it non-blocking, so we can
                // use a selector on it.
                SocketChannel sc = s.getChannel();
                sc.configureBlocking( false );

                // Registra a conexao no seletor, apenas para leitura
                sc.register( masterSelector, SelectionKey.OP_READ );

            } else if ( key.isReadable() ) {
                SocketChannel sc = null;

                // It's incoming data on a connection, so
                // process it
                sc = (SocketChannel)key.channel();

                // Verifica se a conexão corresponde a um cliente já existente

                if((clientsMap.getClient(key)) != null){
                    boolean closedConnection = !processIncomingClientData(key);
                    if(closedConnection){
                        int id = clientsMap.getClient(key);
                        closeClient(id);
                    }
                } else {
                    boolean clientAccepted = processIncomingDataFromNewClient(key);
                    if(!clientAccepted){
                        // Se o cliente não foi aceito, sua conexão é simplesmente fechada
                        sc.socket().close();
                        sc.close();
                        key.cancel();
                    }
                }

            }
        }

        // We remove the selected keys, because we've dealt
        // with them.
        keys.clear();
    }
}

このコードは、チャットに接続したい新しいクライアントを処理するだけです。したがって、クライアントはサーバーへの TCP 接続を確立し、それが受け入れられると、単純なテキスト プロトコルに従ってデータをサーバーに送信し、ID を通知してサーバーへの登録を要求します。メソッド processIncomingDataFromNewClient(key) でこれを処理します。また、ハッシュテーブルに似たデータ構造で、クライアントとその接続のマップを保持しています。接続からクライアント ID を回復し、クライアント ID から接続を回復する必要があるためです。これは、clientsMap.getClient(key) で表示できます。しかし、問題自体はメソッド processIncomingDataFromNewClient(key) にあります。そこでは、クライアントから送信されたデータを読み取って検証し、問題がなければ、クライアントにメッセージを送り返し、チャット サーバーに接続されていることを伝えます。同様のコードを次に示します。

private boolean processIncomingDataFromNewClient(SelectionKey key){
    SocketChannel sc = (SocketChannel) key.channel();
    String connectionOrigin = sc.socket().getInetAddress() + ":" + sc.socket().getPort();

    int id = 0; //id of the client

    buf.clear();
    int bytesRead = 0;
    try {
        bytesRead = sc.read(buf);
        if(bytesRead<=0){
            System.out.println("Conexão fechada pelo: " + connectionOrigin);
            return false;
        }
        System.out.println("LOG: " + bytesRead + " bytes lidos de " + connectionOrigin);

        String msg = new String(buf.array(),0,bytesRead);

        // Do validations with the client sent me here
        // gets the client id

            }catch (Exception e) {
                e.printStackTrace();
                System.out.println("LOG: Oops. Cliente não conhece o protocolo. Fechando a conexão: " + connectionOrigin);
                System.out.println("LOG: Primeiros 10 caracteres enviados pelo cliente: " + msg); 
                return false;
            }
        }

    } catch (IOException e) {
        System.out.println("LOG: Erro ao ler dados da conexao: " + connectionOrigin);
        System.out.println("LOG: "+ e.getLocalizedMessage());
        System.out.println("LOG: Fechando a conexão...");

        return false;
    }

    // If it gets to here, the protocol is ok and we can add the client
    boolean inserted = clientsMap.addClient(key, id);
    if(!inserted){
        System.out.println("LOG: Não foi possível adicionar o cliente. Ou ele já está conectado ou já têm clientes demais. Id: " + id);
        System.out.println("LOG: Fechando a conexão: " + connectionOrigin);
        return false;
    }
    System.out.println("LOG: Novo cliente conectado! Enviando mesnsagem de confirmação. Id: " + id + " Conexao: " + connectionOrigin);

    /* Here is the error */
    sendMessage(id, "Servidor pet: connection accepted");

    System.out.println("LOG: Novo cliente conectado! Id: " + id + " Conexao: " + connectionOrigin);
    return true;
}

最後に、メソッド sendMessage(SelectionKey key) は次のようになります。

private void sendMessage(int destId, String msg) {
    Charset charset = Charset.forName("ISO-8859-1");
    CharBuffer charBuffer = CharBuffer.wrap(msg, 0, msg.length());
    ByteBuffer bf = charset.encode(charBuffer);

    //bf.flip();

    int bytesSent = 0;
    SelectionKey key = clientsMap.getClient(destId);

    SocketChannel sc = (SocketChannel) key.channel();

    try {
        /
        int total_bytes_sent = 0;
        while(total_bytes_sent < msg.length()){
            bytesSent = sc.write(bf);
            total_bytes_sent += bytesSent;

        }

        System.out.println("LOG: Bytes enviados para o cliente " + destId + ": "+ total_bytes_sent + " Tamanho da mensagem: " + msg.length());
    } catch (IOException e) {
        System.out.println("LOG: Erro ao mandar mensagem para: " + destId);
        System.out.println("LOG: " + e.getLocalizedMessage());
    }
}

したがって、何が起こっているかというと、サーバーはメッセージを送信すると、次のようなものを出力します。

LOG: Bytes sent to the client: 28 Size of the message: 28

つまり、データを送信したことを示していますが、チャット クライアントはブロックし続け、recv() メソッドで待機しています。したがって、データは決してそこに到達しません。ただし、サーバー アプリケーションを閉じると、すべてのデータがクライアントに表示されます。なぜだろう。

クライアントは C であり、サーバーは JAVA であると言うことが重要です。私は両方を同じマシンで実行しており、Windows の下の virtualbox で Ubuntu ゲストを実行しています。WindowsホストとLinuxホストの両方で実行しても、同じ奇妙な問題が発生し続けます。

この質問が長くなって申し訳ありませんが、すでに多くの場所で回答を検索し、ここStackOverflowを含む多くのチュートリアルと質問を見つけましたが、合理的な説明が見つかりませんでした. 私はこの Java NIO が本当に好きではなく、多くの人が不満を言っているのを見ました。もし私がCでそれをやっていたら、それはずっと簡単だっただろうと私は考えています:-D

ですから、誰かが私を助けて、この振る舞いについて話し合うことができれば、それは素晴らしいことです! :-)

みなさん、よろしくお願いします。

ピーターソン

4

2 に答える 2

1

試す

System.out.println("LOG: " + bytesRead + " bytes lidos de " + connectionOrigin);
buf.flip();
String msg = new String(buf.array(),0,bytesRead);
于 2009-07-09T20:42:52.023 に答える
-1

受け入れブロックでは、ノンブロッキングの後、ノードレイを設定して、OS がデータを送信するために自身のバッファーがいっぱいになるまで待機しないようにします。

    socket.setTcpNoDelay(true);
于 2012-08-06T20:58:31.970 に答える