简体   繁体   English

Java NIO服务器/客户端聊天应用程序-仅通过关闭套接字来发送数据

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

friends! 朋友们! I'm new to Java NIO and I'm currently trying to make a non-blocking chat app. 我是Java NIO的新手,目前正在尝试制作一个非阻塞的聊天应用程序。 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. 客户端向服务器写入一条消息或几条消息,但是服务器仅在从客户端代码关闭Socket连接时才开始读取消息,因此必须为每个消息在客户端代码中创建并关闭SocketChannel(或仅Socket) -在我看来,这似乎不对。 I've tried the client side with simple Java I/O and also with NIO Selector. 我已经用简单的Java I / O以及NIO Selector尝试了客户端。 Same problem - the server starts to read only when the SocketChannel or the Socket is closed from client. 同样的问题-仅当从客户端关闭SocketChannel或Socket时,服务器才开始读取。 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: 带有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;
  }
}

And this is the non-NIO cliet: 这是非NIO的条件:

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

Your code suffers from the usual raft of NIO mistakes: 您的代码经常遇到大量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();

You are selecting without a sleep. 您正在选择不睡觉。 If there are no ready channels this loop will smoke the CPU. 如果没有就绪通道,则此循环将占用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. 除非您已经写过东西并获得了简短的返回值,否则您不应该注册OP_WRITE。

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: 带有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);

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, eg currentKey.isValid() && currentKey.isReadable() , because a prior handler may have closed the channel or cancelled the key. 很好,但是您需要在下面的其他所有测试之前进行此测试,例如currentKey.isValid() && currentKey.isReadable() ,因为先前的处理程序可能已关闭通道或取消了键。 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. 您永远不会在客户端中处理isReadable() 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. 在这里,您正在阻止整个客户端,包括其所有的SocketChannels等待用户输入一些输入。 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. finishConnect()可以返回false ,在这种情况下,您不应该对此方法做任何进一步的操作。

    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. 参见上文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. 这就是OP_CONNECT的目的。 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. 不仅仅是抽CPU。

And this is the non-NIO cliet: 这是非NIO的条件:

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. isConnected()不是对等断开连接的有效测试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM