当前位置: 首页 > 知识库问答 >
问题:

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

鄂琛
2023-03-14

朋友!我是Java NIO新手,目前正在尝试制作一个非阻塞聊天应用程序。客户端与服务器的连接没有问题。客户端向服务器写入一条或几条消息,但服务器仅在从客户端代码关闭套接字连接时才开始读取消息,因此必须在客户端代码中为每条消息创建并关闭一个SocketChannel(或仅套接字)——在我看来,这并不正确。我用简单的Java I/O和NIO选择器尝试了客户端。同样的问题——当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 {
  }
}

共有1个答案

子车俊哲
2023-03-14

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

            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客户:

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

套接字已连接。您在构建套接字时连接了它。它保持这种状态。isConnect()不是对等断开连接的有效测试。

 类似资料:
  • 我已经实现了一个通过套接字进行通信的全局聊天。客户端写入一条消息,发送到服务器,然后服务器将消息发回给所有客户端。每个客户端都由一个名为ClientThread的类表示,因此每个客户端都是一个线程。每次新客户端连接时,我都会创建一个新的ClientThread实例,并将所有这些实例存储在列表中。现在我想实现一个私人聊天,这样每个客户端就可以私下与另一个客户端交谈(或者2,3或更多的群聊)。 我还想

  • 服务器和客户端使用我自己的协议进行通信,看起来像XMPP。我应该实现聊天应用。因此,当一个用户编写String时,它应该立即通过服务器发送给其他客户端。我在服务器上有sendToAll方法。但用户只有在按下回车键时才能看到其他用户的消息。用户如何在不按回车键的情况下接收消息? 这是我的客户: 以及带有ServerThread的服务器。 服务器线程。

  • 问题内容: 服务器和客户端使用我自己的协议(类似于XMPP)进行通信。我应该实现聊天应用程序。因此,当一个用户写String时,应该立即将其通过服务器发送给其他客户端。我在服务器上有sendToAll方法。但是用户只有在按Enter时才能看到其他用户的消息。 用户如何在不按Enter键的情况下接收消息? 这是我的客户: 和带有ServerThread的服务器。 ServerThread。 问题答案

  • 我正在开发一个非常简单的Java客户机/服务器系统(只是为了让我的脚沾满套接字)。由于某种原因,我一直收到“套接字已关闭”错误。。。这是我的密码。。 服务器文件 客户端文件 我在客户端的第41行得到了错误,然后在第46行得到了NullPointerException。。 提前感谢您的帮助。我只是想在这里学习。

  • 我是java的新手,我制作了一个聊天应用程序,客户端和服务器可以通过它发送和接收消息,现在我试图从客户端向服务器发送一个文件,但在服务器接收到文件后,客户端和服务器都无法发送消息,下面是代码: 客户端: 服务器端:

  • 问题内容: 我正在通过TCP创建一个Java客户端/服务器应用程序,其中有两个套接字: 一种是交换消息。 二是用于文件传输。 我已经在服务器中创建了两个ServerSockets,以便 通过接受ServerSockets 创建套接字1和2 。 首先,客户端通过第一个套接字发送一些字节, 以便它可以告诉服务器它需要哪个文件。 然后,服务器通过第二个套接字将文件发送给客户端。 客户端收到文件后,尝试将