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

2024-04-22

朋友们!我是 Java NIO 的新手,目前正在尝试制作一个非阻塞聊天应用程序。客户端连接到服务器没有问题。客户端向服务器写入一条消息或几条消息,但服务器仅在客户端代码关闭 Socket 连接时才开始读取消息,因此必须在客户端代码中为每条消息创建并关闭 SocketChannel(或仅 Socket) - 这对我来说似乎不对。我已经尝试过使用简单的 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 {
  }
}

您的代码会遭受常见的大量 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 客户端:

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • Jackson JSON + Java 泛型

    我正在尝试将以下 JSON 反序列化 映射到List
  • “源兼容性”和“目标兼容性”有什么区别?

    之间有什么关系 区别sourceCompatibility and targetCompatibility 当它们设置为不同的值时会发生什么 根据工具链和兼容性 https docs gradle org current userguide
  • 使用 proguard 混淆文件名

    我正在使用 proguard 和 Android Studio 混淆我的 apk 当我反编译我的apk时 我可以看到很多文件 例如aaa java aab java ETC 但我项目中的所有文件都有原始名称 有没有办法混淆我的项目的文件名
  • 无法使用 datastax java 驱动程序通过 UDT 密钥从 cassandra 检索

    我正在尝试使用用户定义的类型作为分区键将对象存储在 cassandra 中 我正在使用 datastax java 驱动程序进行对象映射 虽然我能够插入到数据库中 但无法检索该对象 如果我更改分区键以使用非 udt 例如文本 我就能够保存和
  • Java中Gson、JsonElement、String比较

    好吧 我想知道这可能非常简单和愚蠢 但在与这种情况作斗争一段时间后 我不知道发生了什么 我正在使用 Gson 来处理一些 JSON 元素 在我的代码中的某个位置 我将 JsonObject 的 JsonElements 之一作为字符串获取
  • java中如何知道一条sql语句是否执行了?

    我想知道这个删除语句是否真的删除了一些东西 下面的代码总是执行 else 是否删除了某些内容 执行此操作的正确方法是什么 public Deleter String pname String pword try PreparedStatem
  • Java AES 256 加密

    我有下面的 java 代码来加密使用 64 个字符密钥的字符串 我的问题是这会是 AES 256 加密吗 String keyString C0BAE23DF8B51807B3E17D21925FADF273A70181E1D81B8EDE
  • ThreeTen 向后移植与 JSR-310 的比较

    由于某些原因 我们现在无法使用 java 8 我们仍然停留在 java 7 上 不过 我想使用新的JSR 310 date time APIs现在 使用官方向后移植 ThreeTen http www threeten org threet
  • 使用 JDBC 连接到 PostgreSql 的本地实例

    我在 Linux 机器上有一个正在运行的 PostgreSql 本地实例 当我使用psql来自 shell 的命令我成功登录 没有任何问题 我需要通过 JDBC 连接到 PostgreSql 但我不知道我到底应该传递什么url参数为Driv
  • 我们如何使用 thymeleaf 绑定对象列表的列表

    我有一个表单 用户可以在其中添加任意数量的内容表对象这也可以包含他想要的列对象 就像在 SQL 中构建表一样 我尝试了下面的代码 但没有任何效果 并且当我尝试绑定两个列表时 表单不再出现 控制器 ModelAttribute page pu
  • Azure Java SDK:ServiceException:ForbiddenError:

    尝试了基本位置检索器代码 如下所示 String uri https management core windows net String subscriptionId XXXXXXXX 5fad XXXXXX 9dfa XXXXXX St
  • titledBorder 标题中的图标

    您好 是否可以在 titledBorder 的标题中放置一个图标 例如以下代码 import java awt GridLayout import javax swing JFrame import javax swing JLabel i
  • 即使禁用安全性,OAuth 令牌 API 也无法在 Elastic Search 中工作

    我是 Elastic search 新手 使用 Elastic search 版本 7 7 1 我想通过以下方式生成 OAuth 令牌弹性搜索文档 https www elastic co guide en elasticsearch re
  • 用于请求带有临时缓存的远程 Observable 的 RxJava 模式

    用例是这样的 我想暂时缓存最新发出的昂贵的Observable响应 但在它过期后 返回到昂贵的源Observable并再次缓存它 等等 一个非常基本的网络缓存场景 但我真的很难让它工作 private Observable
  • Android ScrollView,检查当前是否滚动

    有没有办法检查标准 ScrollView 当前是否正在滚动 方向是向上还是向下并不重要 我只需要检查它当前是否正在滚动 ScrollView当前形式不提供用于检测滚动事件的回调 有两种解决方法可用 1 Use a ListView并实施On
  • Java 中清除嵌套 Map 的好方法

    public class MyCache AbstractMap
  • Selenium 单击在 Internet Explorer 11 上不起作用

    我尝试在 Internet Explorer 上单击 selenium 但它不起作用 我努力了element click moveToElement element click build perform javascript没事了 事实上
  • 什么是 Java2D 处理程序线程?

    我创建了一个使用 Hibernate 的示例 java 应用程序 当我进行线程转储时 我观察到一个名为 Java2D Disposer 的奇怪线程 有人能告诉我该线程的功能吗 AWT 系统中的某些实体需要最终确定以释放资源 最突出的例子是j
  • 关闭扫描仪是否会影响性能

    我正在解决一个竞争问题 在问题中 我正在使用扫描仪获取用户输入 这是 2 个代码段 一个关闭扫描器 一个不关闭扫描器 关闭扫描仪 import java util Scanner public class JImSelection publ
  • GAE 无法部署到 App Engine

    我正在尝试从 Eclipse 发布 Web 应用程序 我在 GAE 上创建了四个项目 可以通过登录我的帐户并查看控制台来查看它们 我已经改变了appengine web xml到项目的应用程序 ID 如果我将其更改为 GAE 上第一个创建的

随机推荐