Java NIO Server/Client Chat App - sending data only by closing the socket

浪子不回头ぞ 提交于 2019-12-24 02:17:18

问题


friends! I'm new to Java NIO and I'm currently trying to make a non-blocking chat app. The client connects to the server without problem. The client writes a message or few messages to the server but the server starts reading the messages only when the Socket connection is closed from the client code, so a SocketChannel (or only Socket) must be created and closed in the client code for every message - this doesn't seems to me right. I've tried the client side with simple Java I/O and also with NIO Selector. Same problem - the server starts to read only when the SocketChannel or the Socket is closed from client. Can somebody please tell me the proper way of doing such non blocking connections or show me the error in my logic... Thank You very much!

This is the 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 with 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;
  }
}

And this is the non-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 {
  }
}

回答1:


Your code suffers from the usual raft of NIO mistakes:

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

You are selecting without a sleep. If there are no ready channels this loop will smoke the CPU. Use a timeout, even a short one.

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

You should not register for OP_WRITE unless you've already written something and got a short return value.

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

The channel is already in non-blocking mode. You put it there when you accepted it. You couldn't have selected on it unless it was in non-blocking mode. Remove.

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

The buffer is already clear. You just created it. Remove.

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

Closing the channel cancels the key. You don't need both. Remove the cancel.

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

Remove. Don't leave dead code lying around to confuse future readers.

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

See above.

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

See above.

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

Very good but you need this test before every other one below, e.g. currentKey.isValid() && currentKey.isReadable(), because a prior handler may have closed the channel or cancelled the key. Same applies in the server code.

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

You never handle isReadable() in the client. Don't you expect any input?

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

Here you are blocking the entire client including all its SocketChannels waiting for the user to enter some input. This is very poor design.

    buffer.clear();

You don't need this. You're about to release the buffer as a local variable. You're done with it.

    channel.close();

You're closing the channel after one write? Why?

    key.cancel();

Closing the channel cancels the key. You don't need both. You don't need this. Remove.

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

finishConnect() can return false, in which case you should do nothing further in this method.

    channel.configureBlocking(false);

The channel is already in blocking mode. Otherwise you couldn't have got here. Remove.

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

See above re 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....");
    }

Remove this loop. That's what OP_CONNECT is for. You are keeping a dog and barking yourself. If you want not to proceed out of here until the connection is complete, do it in blocking mode. Instead of just smoking the CPU.

And this is the non-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()) {

The socket is connected. You connected it when you constructed it. It stays that way. isConnected() is not a valid test for peer disconnection.



来源:https://stackoverflow.com/questions/40192279/java-nio-server-client-chat-app-sending-data-only-by-closing-the-socket

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!