2016-10-22 3 views
2

Freunde! Ich bin neu bei Java NIO und versuche gerade, eine nicht blockierende Chat-App zu erstellen. Der Client verbindet sich problemlos mit dem Server. Der Client schreibt eine Nachricht oder wenige Nachrichten an den Server, aber der Server liest die Nachrichten erst, wenn die Socket-Verbindung vom Client-Code geschlossen wird. Daher muss im Client-Code für jede Nachricht ein SocketChannel (oder nur Socket) erstellt und geschlossen werden - Das scheint mir nicht richtig zu sein. Ich habe die Client-Seite mit einfachen Java I/O und auch mit NIO Selector versucht. Das gleiche Problem - der Server beginnt nur zu lesen, wenn der SocketChannel oder der Socket vom Client geschlossen wird. Kann mir bitte jemand sagen, wie man solche nicht blockierenden Verbindungen richtig macht oder mir den Fehler in meiner Logik zeigt ... Vielen Dank!Java NIO Server/Client Chat App - Senden von Daten nur durch Schließen des Sockets

Dies ist der Server-Code:

public class NIOServer implements Runnable { 

@Override 
public void run() { 
    try { 
     runServer(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 

private void runServer() throws IOException { 
    ServerSocketChannel server = ServerSocketChannel.open(); 
    server.socket().bind(new InetSocketAddress(8080)); 
    server.configureBlocking(false); 
    Selector selector = Selector.open(); 
    server.register(selector, SelectionKey.OP_ACCEPT); 

     while(true) { 
      int readyChannels = selector.selectNow(); 
      if(readyChannels==0){ 
       continue; 
      } 
      System.out.println("Ready channels: "+readyChannels); 

      Set<SelectionKey> selectionKeys = selector.selectedKeys(); 
      Iterator<SelectionKey> keyIterator = selectionKeys.iterator(); 

      while(keyIterator.hasNext()) { 
       SelectionKey key = keyIterator.next(); 
       keyIterator.remove(); 

       if(key.isAcceptable()){ 
        ServerSocketChannel acceptableServer = (ServerSocketChannel)key.channel(); 
        SocketChannel client = server.accept(); 
        if(client!=null){ 
         System.out.println("Client accepted!"); 
         client.configureBlocking(false); 
         SelectionKey selectionKey = client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE); 
        } 
       } 
       if (key.isReadable()) { 
        read(key); 
       } 

       /*if(key.isConnectable()){ 
        System.out.println("connectable"); 
       } 
       if(key.isWritable()){ 
        //System.out.println("writable"); 
       }*/ 
      } 

     } 
} 

public void read(SelectionKey key) throws IOException { 
    SocketChannel channel = (SocketChannel)key.channel(); 
    channel.configureBlocking(false); 
    ByteBuffer buffer = ByteBuffer.allocate(100); 
    buffer.clear(); 
    int bytesRead = channel.read(buffer); 

    while(bytesRead>0){ 
     System.out.println("Read bytes: "+ bytesRead); 
     bytesRead=channel.read(buffer); 
     if(bytesRead==-1){ 
      channel.close(); 
      key.cancel(); 
     } 
     buffer.flip(); 
     while(buffer.hasRemaining()){ 
      System.out.print((char)buffer.get()); 
     } 
    } 


    //key.cancel(); 
    //channel.close(); 

} 

}

-Client mit NIO Selector:

public class NIOSelectorClient implements Runnable{ 
private Selector selector; 

@Override 
public void run() { 
    try { 
     startClient(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 

public void startClient() throws IOException { 
    SocketChannel socketChannel= openConnection(); 
    selector = Selector.open(); 
    socketChannel.register(selector,SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE); 
    while(!Thread.interrupted()) { 
     int readyChannels = selector.selectNow(); 
     if(readyChannels==0) { 
      continue; 
     } 

     Set<SelectionKey> keySet = selector.selectedKeys(); 
     Iterator<SelectionKey> keyIterator = keySet.iterator(); 

     while(keyIterator.hasNext()) { 
      SelectionKey currentKey = keyIterator.next(); 
      keyIterator.remove(); 

      if(!currentKey.isValid()) { 
       continue; 
      } 
      if(currentKey.isConnectable()) { 
       System.out.println("I'm connected to the server!"); 
       handleConnectable(currentKey); 
      } 
      if(currentKey.isWritable()){ 
       handleWritable(currentKey); 
      } 
     } 
    } 
} 

private void handleWritable(SelectionKey key) throws IOException { 
    SocketChannel channel = (SocketChannel)key.channel(); 
    ByteBuffer buffer = ByteBuffer.allocate(100); 
    Scanner scanner = new Scanner(System.in); 
    System.out.println("Enter message to server: "); 
    String output = scanner.nextLine(); 
    buffer.put(output.getBytes()); 
    buffer.flip(); 
    //while(buffer.hasRemaining()) { 
     channel.write(buffer); 
    //} 
    System.out.println("Message send"); 
    buffer.clear(); 
    channel.close(); 
    key.cancel(); 
} 

private void handleConnectable(SelectionKey key) throws IOException { 
    SocketChannel channel = (SocketChannel) key.channel(); 
    if(channel.isConnectionPending()) { 
     channel.finishConnect(); 
    } 
    channel.configureBlocking(false); 
    channel.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ); 
} 

private static SocketChannel openConnection() throws IOException { 
    SocketChannel socketChannel = SocketChannel.open(); 
    socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080)); 
    socketChannel.configureBlocking(false); 
    while(!socketChannel.finishConnect()) { 
     System.out.println("waiting connection...."); 
    } 
    return socketChannel; 
} 

}

Und das ist die nicht-NIO cliet:

public class NIOClient { 
public static void main(String[] args) throws IOException { 
    Socket socket = new Socket("127.0.0.1", 8080); 
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); 
    while(socket.isConnected()) { 
     //synchronized (socket) { 
      writeMessage(socket,writer); 
      //readServerMessage(socket); 
     //} 
    } 

} 

public static void writeMessage(Socket socket, BufferedWriter writer) throws IOException { 
    Scanner scanner = new Scanner(System.in); 
    System.out.println("Enter message: "); 
    String output = "Client 1: " + scanner.nextLine(); 
    writer.write(output); 
    writer.flush(); 
    //writer.close(); 
} 

public static void readServerMessage(Socket socket) throws IOException { 

} 

}

+0

Versuchen Sie, TCP_NODELAY zu aktivieren: 'socketChannel.setOption (StandardSocketOptions.TCP_NODELAY, true);' Dies bewirkt, dass TCP alle Daten in seinem Schreibpuffer sendet, sobald es verfügbar ist. – Malt

+0

Danke für Ihren Kommentar! Ich habe gerade versucht, diese Option richtig zu setzen, aber es funktioniert immer noch genauso - ich habe das auch für den einfachen IO-Socket versucht, wie für den NIO SocketChannel ... Hast du noch andere Ideen? :) – bulibas

Antwort

1

Ihr Code leidet unter der üblichen Reihe von NIO Fehler:

public class NIOServer implements Runnable { 

private void runServer() throws IOException { 
    ServerSocketChannel server = ServerSocketChannel.open(); 
    server.socket().bind(new InetSocketAddress(8080)); 
    server.configureBlocking(false); 
    Selector selector = Selector.open(); 
    server.register(selector, SelectionKey.OP_ACCEPT); 

     while(true) { 
      int readyChannels = selector.selectNow(); 

Sie sind ohne Schlaf auswählen. Wenn es keine fertigen Kanäle gibt, wird diese Schleife die CPU rauchen. Verwenden Sie ein Timeout, auch ein kurzes.

Sie sollten sich nicht für OP_WRITE registrieren, es sei denn, Sie haben bereits etwas geschrieben und einen kurzen Rückgabewert erhalten.

public void read(SelectionKey key) throws IOException { 
    SocketChannel channel = (SocketChannel)key.channel(); 
    channel.configureBlocking(false); 

Der Kanal ist bereits im nicht blockierenden Modus. Du hast es dort hingelegt, als du es akzeptiert hast. Sie konnten nicht ausgewählt haben, es sei denn, es war im nicht blockierenden Modus. Löschen.

ByteBuffer buffer = ByteBuffer.allocate(100); 
    buffer.clear(); 

Der Puffer ist bereits frei. Du hast es gerade erstellt. Löschen.

int bytesRead = channel.read(buffer); 

    while(bytesRead>0){ 
     System.out.println("Read bytes: "+ bytesRead); 
     bytesRead=channel.read(buffer); 
     if(bytesRead==-1){ 
      channel.close(); 
      key.cancel(); 

Das Schließen des Kanals löscht den Schlüssel. Du brauchst beides nicht. Entfernen Sie den Abbruch.

//key.cancel(); 
    //channel.close(); 

Entfernen. Lassen Sie keinen toten Code herumliegen, um zukünftige Leser zu verwirren.

-Client mit NIO Selector:

public class NIOSelectorClient implements Runnable{ 
private Selector selector; 

public void startClient() throws IOException { 
    SocketChannel socketChannel= openConnection(); 
    selector = Selector.open(); 
    socketChannel.register(selector,SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE); 

Siehe oben.

while(!Thread.interrupted()) { 
     int readyChannels = selector.selectNow(); 

Siehe oben.

  if(!currentKey.isValid()) { 
       continue; 
      } 

Sehr gut, aber Sie müssen diesen Test vor jedem anderen unten, z. currentKey.isValid() && currentKey.isReadable(), weil ein vorheriger Handler den Kanal möglicherweise geschlossen oder den Schlüssel abgebrochen hat. Gleiches gilt für den Servercode.

  if(currentKey.isConnectable()) { 
       System.out.println("I'm connected to the server!"); 
       handleConnectable(currentKey); 
      } 
      if(currentKey.isWritable()){ 
       handleWritable(currentKey); 
      } 

Sie behandeln niemals isReadable() im Client. Erwarten Sie keine Eingabe?

private void handleWritable(SelectionKey key) throws IOException { 
    SocketChannel channel = (SocketChannel)key.channel(); 
    ByteBuffer buffer = ByteBuffer.allocate(100); 
    Scanner scanner = new Scanner(System.in); 
    System.out.println("Enter message to server: "); 
    String output = scanner.nextLine(); 

Hier blockieren Sie den gesamten Client inklusive aller SocketChannels für den Benutzer warten, eine Eingabe einzugeben. Das ist sehr schlechtes Design.

buffer.clear(); 

Sie brauchen dies nicht. Sie werden den Puffer als lokale Variable freigeben. Du bist damit fertig.

channel.close(); 

Sie schließen den Kanal nach einem schreiben? Warum?

key.cancel(); 

Das Schließen des Kanals löscht den Schlüssel. Du brauchst beides nicht. Du brauchst das nicht. Löschen.

private void handleConnectable(SelectionKey key) throws IOException { 
    SocketChannel channel = (SocketChannel) key.channel(); 
    if(channel.isConnectionPending()) { 
     channel.finishConnect(); 

finishConnect() kann false zurückkehren, in dem Fall, dass Sie nichts weiter in dieser Methode tun sollten.

channel.configureBlocking(false); 

Der Kanal befindet sich bereits im Sperrmodus. Sonst hättest du nicht hier sein können. Löschen.

channel.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ); 
} 

Siehe oben zu OP_WRITE.

private static SocketChannel openConnection() throws IOException { 
    SocketChannel socketChannel = SocketChannel.open(); 
    socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080)); 
    socketChannel.configureBlocking(false); 
    while(!socketChannel.finishConnect()) { 
     System.out.println("waiting connection...."); 
    } 

Diese Schleife entfernen. Dafür ist OP_CONNECT da. Sie halten einen Hund und bellen sich. Wenn Sie nicht bis zur Beendigung der Verbindung fortfahren möchten, führen Sie dies im Blockiermodus aus. Anstatt nur die CPU zu rauchen.

Und das ist die nicht-NIO cliet:

public class NIOClient { 
public static void main(String[] args) throws IOException { 
    Socket socket = new Socket("127.0.0.1", 8080); 
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); 
    while(socket.isConnected()) { 

Die Steckdose angeschlossen ist. Du hast es verbunden, als du es gebaut hast. Es bleibt so. isConnected() ist kein gültiger Test für die Peer-Trennung.