2016-10-22 61 views
2

朋友发送数据!我是Java NIO的新手,目前我正在尝试制作一个无阻塞的聊天应用程序。客户端连接到服务器没有问题。客户端向服务器写入消息或少量消息,但服务器只有在客户端代码关闭Socket连接时才开始读取消息,因此必须为客户端代码创建并关闭每个消息的SocketChannel(或仅Socket)。 - 这在我看来不正确。我用简单的Java I/O和NIO Selector尝试了客户端。同样的问题 - 服务器只有在客户端关闭SocketChannel或Socket时才开始读取。有人可以告诉我做这种非阻塞连接的正确方法或告诉我在我的逻辑错误...非常感谢你!Java NIO服务器/客户端聊天应用程序 - 仅通过关闭套接字

这是服务器代码:

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

} 

}

客户端与NIO选择:

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

}

这是非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

尝试启用TCP_NODELAY:'socketChannel.setOption(StandardSocketOptions.TCP_NODELAY,true);' 这将导致TCP在其写缓冲区可用时立即发送任何数据。 – Malt

+0

感谢您的评论!我刚刚尝试设置这个选项是真的,但它仍然以同样的方式 - 我已经尝试了这个以及简单的IO套接字,至于NIO的SocketChannel ...你有任何其他想法? :) – bulibas

回答

1

你的代码的NIO错误通常筏遭遇:

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

您选择不睡眠。如果没有现成的通道,该回路将吸入CPU。使用超时,即使是短暂的。

     SelectionKey selectionKey = client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE); 

除非您已经写了一些内容并获得了较短的返回值,否则您不应该注册OP_WRITE。

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

关闭通道取消密钥。你不需要两个。取消取消。

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

删除。不要留下无用的代码来混淆未来的读者。

客户端与NIO选择:

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

见上面。

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

参见上文。

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

非常好,但您需要在下面的每一个之前的这个测试,例如, currentKey.isValid() && currentKey.isReadable(),因为之前的处理程序可能已经关闭了通道或取消了密钥。同样适用于服务器代码。

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

你永远不会在客户端处理isReadable()。你不期望有任何输入吗?

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

这里你挡住了整个客户端包括其所有SocketChannels等待用户输入一些输入。这是非常糟糕的设计。

buffer.clear(); 

你不需要这个。您即将释放缓冲区作为局部变量。你已经完成了。

channel.close(); 

您在一次写入后关闭通道?为什么?

key.cancel(); 

关闭通道取消密钥。你不需要两个。你不需要这个。去掉。

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

finishConnect()可以返回false,在这种情况下,你应该进一步什么也不做此方法。

channel.configureBlocking(false); 

通道已处于阻塞模式。否则,你不可能来到这里。去掉。

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

请参阅上面的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...."); 
    } 

删除此循环。这就是OP_CONNECT的用途。你养狗,吠叫自己。如果您想在连接完成之前不从此处离开,请在阻止模式下执行此操作。而不是只吸一点CPU。

这是非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()) { 

套接字连接。您在构建它时连接了它。它保持那样。 isConnected()不是用于对等连接断开的有效测试。

相关问题