繁体   English   中英

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

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

朋友们! 我是Java NIO的新手,目前正在尝试制作一个非阻塞的聊天应用程序。 客户端毫无问题地连接到服务器。 客户端向服务器写入一条消息或几条消息,但是服务器仅在从客户端代码关闭Socket连接时才开始读取消息,因此必须为每个消息在客户端代码中创建并关闭SocketChannel(或仅Socket) -在我看来,这似乎不对。 我已经用简单的Java I / O以及NIO Selector尝试了客户端。 同样的问题-仅当从客户端关闭SocketChannel或Socket时,服务器才开始读取。 有人可以告诉我进行这种非阻塞连接的正确方法,还是告诉我我的逻辑错误...非常感谢!

这是服务器代码:

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的条件:

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

您的代码经常遇到大量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);
}

参见上文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....");
    }

删除此循环。 这就是OP_CONNECT的目的。 您正在养狗,吠叫自己。 如果您不希望在连接完成之前不离开这里,请以阻止模式进行操作。 不仅仅是抽CPU。

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

插座已连接。 在构造它时就已连接它。 它保持这种方式。 isConnected()不是对等断开连接的有效测试。

暂无
暂无

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

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