You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java NIO中serverSocketChannel.accept()返回null的问题排查

Java NIO服务端accept()返回null及重复打印的问题分析

我编写了一个简单的Java NIO客户端服务端程序,代码如下:

package org.lb;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;

public class Test {
  public static void main(String[] args) throws IOException {
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(8080));
    serverSocketChannel.configureBlocking(false);

    Selector selector = Selector.open();
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    while (true) {
      selector.select();
      Set<SelectionKey> selectionKeySet = selector.selectedKeys();
      for (SelectionKey selectionKey : selectionKeySet) {
        if (selectionKey.isAcceptable()) {
          SocketChannel socketChannel = serverSocketChannel.accept();
          if (socketChannel == null) {
            System.out.println("No connection...");
          } else {
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
            System.out.println("Connection done!");
          }
        }

        if (selectionKey.isReadable()) {
          System.out.println("isReadable");
          SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
          socketChannel.close();
        }
      }
    }
  }
}

我仅在selectionKey.isAcceptable()返回true时调用serverSocketChannel.accept(),原以为该方法应始终返回有效的SocketChannel实例,但有时会返回null。使用Postman、浏览器或curl发送请求时均出现此情况:

Postman单次请求输出:

Connection done!
No connection...
isReadable
Connection done!
No connection...
isReadable

curl单次请求输出:

Connection done!
isReadable
No connection...

我预期“Connection done!”仅打印一次,但实际打印了两次,同时还出现“No connection...”,这是为什么?


问题原因

  1. 未移除已处理的SelectionKey
    Selector的selectedKeys集合不会自动清除已处理的键。每次调用select()后,之前触发过的事件键会保留在集合中,导致下一次循环时重复处理同一个OP_ACCEPT事件。第一次处理完accept()后,该键仍在集合里,下次循环再次进入isAcceptable()分支,此时没有新连接可处理,accept()就会返回null。

  2. HTTP客户端的连接特性
    浏览器、Postman等工具可能会建立多个连接(比如预连接、请求结束后的复用尝试),curl在某些场景下也会触发额外的连接行为,这会导致多次OP_ACCEPT事件触发,但后续的accept()没有新连接可获取,因此返回null。

解决方案

核心修复点是处理完每个SelectionKey后必须从集合中移除,避免重复处理:

修改代码中的循环部分,使用迭代器并手动移除已处理的键:

while (true) {
  selector.select();
  Set<SelectionKey> selectionKeySet = selector.selectedKeys();
  // 使用迭代器遍历,方便移除已处理的键
  for (Iterator<SelectionKey> iterator = selectionKeySet.iterator(); iterator.hasNext();) {
    SelectionKey selectionKey = iterator.next();
    // 关键步骤:移除已处理的键,防止重复处理
    iterator.remove();

    if (selectionKey.isAcceptable()) {
      SocketChannel socketChannel = serverSocketChannel.accept();
      if (socketChannel == null) {
        System.out.println("No connection...");
      } else {
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("Connection done!");
      }
    }

    if (selectionKey.isReadable()) {
      System.out.println("isReadable");
      SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
      socketChannel.close();
    }
  }
}

另外需要明确:在非阻塞模式下,即使OP_ACCEPT事件被触发,accept()返回null是正常行为(当所有待处理连接都已被处理完毕时),你的null判断逻辑是合理的,重复处理事件才是导致多次打印的根本原因。

内容的提问来源于stack exchange,提问作者Rahul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 03:52:06