【问题标题】:NIO Selector Thread, handling channels as expected, but how do I ensure channels are correctly closed after use?NIO Selector Thread,按预期处理通道,但是如何确保通道在使用后正确关闭?
【发布时间】:2015-07-30 22:50:02
【问题描述】:

所以我的 ServerRunnable 类中有以下代码:

public class FirmwareServerRunnable implements Runnable {

    private static Logger log = Logger.getLogger(FirmwareServerRunnable.class
            .getName());
    private LinkedTransferQueue<CommunicationState> communicationQueue;
    private int serverPort = 48485;

    public FirmwareServerRunnable(int port,
            LinkedTransferQueue<CommunicationState> communicationQueue) {
        serverPort = port;
        this.communicationQueue = communicationQueue;
    }

    private boolean running;
    private ServerSocketChannel serverSocketChannel;

    @Override
    public void run() {

        try {

            Selector selector = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            ServerSocket serverSocket = serverSocketChannel.socket();
            serverSocket.bind(new InetSocketAddress(serverPort));

            log.info("Selector Thread: FirmwareServer Runnable- Listening for connections on port: "
                    + serverSocket.getLocalPort());

            running = true;

            @SuppressWarnings("unused")
            SelectionKey serverAcceptKey = serverSocketChannel.register(
                    selector, SelectionKey.OP_ACCEPT);

            while (running) {

                selector.select();
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

                while (keyIterator.hasNext()) {

                    SelectionKey key = (SelectionKey) keyIterator.next();

                    if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {

                        acceptConnection(selector, key);
                        keyIterator.remove();

                    } else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
                        CommunicationState commsState = (CommunicationState) key
                                .attachment();
                        if (commsState.getCurrentState() == CommunicationState.STATE_READ) {
                            readFromSocketChannel(key);
                            keyIterator.remove();
                        }
                    } else if ((key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {

                        CommunicationState commsState = (CommunicationState) key
                                .attachment();
                        if (commsState.getCurrentState() == CommunicationState.STATE_WRITE) {


                            writeToSocketChannel(key);
                            keyIterator.remove();
                        }
                    }

                }

            }

        } catch (IOException e) {
            log.error(
                    "Firmware Selector Thread: An IOException occurred",
                    e);     
        }

    }

我的acceptConnection() 方法接受一个连接并向其添加一个CommunicationState 对象(一个状态机),其中包含ByteBuffer、当前通道状态、客户端当前处于通信过程中的位置等内容。 .
该服务器在进程中间切换通信方法。最初它使用 JSON 消息与客户端通信,但当它到达某个点时,它开始使用 USART protocol commands 用新固件刷新客户端。

该过程完成后,客户端将断开连接并重新启动。这使我的频道处于未知状态。我不确定频道是否已关闭或不在我身边。 我怎样才能检查这个?我认为selector.selectedKeys() 只返回准备好操作的键是正确的吗?如果是这种情况,我如何检查未正确关闭的连接?我可以在这个 ServerRunnable while(running){} 循环中做到这一点吗?

我一直在考虑的一个选项是将密钥本身的引用附加到 CommunicationState 机器,然后在该过程完成后我可以获得对通道的引用并将其关闭。但出于某种原因,我对这个解决方案感到不安,我觉得它不合适。

如果是包含关闭的频道密钥的情况,我可以使用key.isValid() 确认需要永久删除该密钥吗?

如果您对这个过程有任何想法,我将不胜感激,我一定忽略了一些事情。

编辑:快速测试似乎表明通道键不包含在所选键集中,除非它们已准备好用于三个定义的操作之一 我的测试很糟糕。

【问题讨论】:

  • 重新编辑,当然是这样。 selected-keys 集是为其注册的操作选择的键集。
  • 是的,我知道,我在这里抓住了稻草! :-) 回到有条不紊的方法,就是这样。
  • 我将再次尝试单步执行代码。我有一种情况,该过程正在完成,客户端成功重新启动,但我机器上的处理器保持在 30% - 40% 标记之间,就好像它在继续循环一样,这是我能说的最好的。我的 readFromSocketChannel() 方法似乎没有被调用。我已经记录输出读取的字节,并说明通道何时关闭,但这些都没有被打印。
  • 好的,我找到了问题,我的状态机处于错误的状态,所以它无法调用readFromSocketChannel() 方法。感谢您的耐心等待,您的帮助很大!
  • 经过一些调试后发现状态是正确的。由于我不明白-1 将在byteBuffer 本身中返回,而不是在int read = socketChannel.read(byteBuffer); 返回的值中,导致该错误发生

标签: java multithreading server nio


【解决方案1】:

已被对等方关闭的连接将导致选择器将您的通道视为可读,当您从中读取时,您将得到-1,因此您应该关闭通道,这将取消其选择键.

编辑

如果是包含关闭的频道密钥的情况,我可以使用 key.isValid() 来确认需要永久删除密钥吗?

如果你关闭了频道,它的键会被取消,所以你下次不会在 selected-keys 集中看到它。如果peer关闭了连接,见上文。

【讨论】:

  • 好的,谢谢,我的readFromSocketChannel() 方法可以解决这个问题。这引发了其他问题。我显然有一个循环在客户端断开连接后无法正常退出。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-19
  • 2016-07-19
  • 2020-03-19
  • 2018-01-29
  • 1970-01-01
  • 2011-05-19
  • 2023-04-02
相关资源
最近更新 更多