2009-07-09 6 views
2

Ich leide sehr, um einen einfachen ChatServer in Java zu erstellen, der die NIO-Bibliotheken verwendet. Frage mich, ob mir jemand helfen könnte. Ich mache das mit SocketChannel und Selector, um mehrere Clients in einem einzigen Thread zu behandeln. Das Problem ist: Ich bin in der Lage, neue Verbindungen zu akzeptieren und seine Daten zu erhalten, aber wenn ich versuche, Daten zurückzusenden, funktioniert der SocketChannel einfach nicht. In der Methode write() gibt es eine Ganzzahl zurück, die die gleiche Größe der Daten hat, die ich an sie übergebe, aber der Client empfängt diese Daten niemals. Seltsamerweise, wenn ich die Server-Anwendung schließe, empfängt der Client die Daten. Es ist wie der Socket-Kanal einen Puffer verwaltet, und es wird nur geleert, wenn ich die Anwendung schließe.SocketChannel in Java sendet Daten, aber es gelangt nicht zur Zielanwendung

Hier sind einige weitere Details, um Ihnen mehr Informationen zu helfen. Ich bin der Umgang mit den Ereignissen in diesem Stück Code:

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

Dieses Stück Code ist einfach Griffe neue Clients, die mit dem IRC verbinden wollen. Ein Client stellt also eine TCP-Verbindung zum Server her, und sobald er akzeptiert wird, sendet er Daten an den Server nach einem einfachen Textprotokoll, informiert seine ID und bittet um Registrierung beim Server. Ich handle das in der Methode processIncomingDataFromNewClient (key). Ich verwalte auch eine Karte von Clients und deren Verbindungen in einer Datenstruktur ähnlich einer Hashtabelle. Ich mache das, weil ich eine Client-ID von einer Verbindung und eine Verbindung von einer Client-ID wiederherstellen muss. Dies kann angezeigt werden in: clientsMap.getClient (key). Aber das Problem selbst liegt in der Methode processIncomingDataFromNewClient (Schlüssel). Dort lese ich einfach die Daten, die der Client an mich gesendet hat, validiere sie und wenn es in Ordnung ist, sende ich eine Nachricht zurück an den Client, um zu sagen, dass sie mit dem Chat-Server verbunden ist. Hier ist ein ähnliches Stück Code:

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

Und schließlich die Methode nachrichts (SelectionKey key) sieht wie folgt aus:

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

Also, was passiert ist, dass der Server, wenn ein senden Nachricht, druckt etwas wie folgt aus:

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

So, sagt er, dass er die Daten gesendet, aber der Chat-Client hält blockiert, in der recv() -Methode wartet. So kommen die Daten nie dazu. Wenn ich die Server-Anwendung schließe, werden jedoch alle Daten im Client angezeigt. Ich wundere mich warum.

Es ist wichtig zu sagen, dass der Kunde in C ist und der Server JAVA, und ich bin mit beide in der gleichen Maschine, ein Ubuntu-Gast in VirtualBox unter Windows. Ich führe auch beide unter Windows-Host und unter Linux-Hosts, und immer wieder das gleiche seltsame Problem.

Es tut mir leid für die große Länge dieser Frage, aber ich habe bereits viele Plätze für eine Antwort gesucht, fand viele Tutorials und Fragen, einschließlich hier bei StackOverflow, aber fand keine vernünftige Erklärung. Ich bin wirklich nicht zu mögen diese Java NIO, und ich sah, darüber zu beschweren zu viele Leute. Ich denke, dass, wenn ich das in C getan hätte, wäre es viel einfacher gewesen :-D

Also, wenn mir jemand helfen könnte und auch diese behavor zu diskutieren, wäre es toll!:-)

alle Vielen Dank im Voraus,

Petersons

+0

Nachdem Sie(), flush() geschrieben haben? – akarnokd

+0

Danke, aber es gibt keine solche Methode, die ich kenne. Ich habe die API für etwas ähnliches gesucht, aber nichts gefunden. In der These sollte die Write-Methode die Bytes sofort senden. –

+0

@akarnokd Das ist richtig. Es gibt keine Stream-Pufferung in NIO und keine 'flush()' Methoden. – EJP

Antwort

1

versuchen

System.out.println("LOG: " + bytesRead + " bytes lidos de " + connectionOrigin); 
buf.flip(); 
String msg = new String(buf.array(),0,bytesRead); 
+1

Ich tat, und es hat nicht funktioniert. Außerdem unterscheidet sich der Puffer, mit dem die Nachricht gesendet wird, von dem, der zum Empfangen von Nachrichten verwendet wurde. Aber danke für die Antwort. –

-1

auf dem Block akzeptieren, nach der nicht-blockierenden, versuchen Sie die nodelay so einzustellen, dass das Betriebssystem nicht warten bis der eigene Puffer voll ist, um die Daten zu senden

socket.setTcpNoDelay(true); 
+0

Das ist nicht, was 'tcpNoDelay' tut, und es löst dieses Problem nicht. – EJP

Verwandte Themen