NIO选择器线程,按预期处理通道,但如何确保通道在使用后正确关闭?

4

我 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协议命令向客户端闪存新固件。

一旦过程完成,客户端断开连接并重新启动。这将使我的通道处于未知状态。我不确定通道是否已在我的一侧关闭。如何检查这一点?我是否正确地认为selector.selectedKeys()只返回准备好进行操作的键?如果是这样,如何检查没有正确关闭的连接?我可以在这个ServerRunnable while(running){}循环中做吗?

我正在考虑的一个选项是将引用附加到CommunicationState机器本身,然后一旦过程完成,我就可以获取通道的引用并在那里关闭它。但出于某种原因,我对这个解决方案感到不安,感觉不对劲。

如果关闭的通道键也被包括在内,我可以使用key.isValid()来确认需要永久删除该键吗?

我很感激您对此过程的任何想法,我一定忽略了某些东西。

编辑:一个快速测试似乎表明通道键不包括在所选键集中,除非它们准备好进行三个定义操作之一 我的测试有误。


关于您的编辑,当然是这样的。所选键集是已为其注册操作而选择的键集。 - user207421
是的,我知道,我在这里抓住稻草了! :-) 回到一种有条理的方法吧。 - mal
我将尝试再次逐步执行代码。我遇到了这样一种情况,即进程已经完成,客户端成功重启,但是我的机器上的处理器仍停留在30%-40%之间,好像它仍在循环,而据我所知,确实如此。我的readFromSocketChannel()方法似乎没有被调用。我有记录输出读取的字节,并在通道关闭时输出日志,但两者都没有被打印出来。 - mal
好的,我找到问题了,我的状态机处于错误的状态,所以它无法调用readFromSocketChannel()方法。非常感谢你的耐心和帮助! - mal
经过一些调试,发现状态是正确的。错误是由于我没有理解-1将在byteBuffer本身中返回,而不是在int read = socketChannel.read(byteBuffer);返回的值中返回。 - mal
1个回答

2
一个被对等方关闭的连接将导致选择器将其通道视为可读取,当您从中读取时,您将获得-1,此时您应该关闭通道,取消其选择键。
编辑: 如果已经关闭的通道键也包含在内,我可以使用key.isValid()来确认该键需要永久删除吗?
如果您关闭了通道,则其键已被取消,因此您下次不会在选定的键集中看到它。如果是对等方关闭了连接,请参见上文。

好的,谢谢,我的 readFromSocketChannel() 方法已经处理了那个。这引发了其他问题。很明显我在某个地方有一个循环,在客户端断开连接后没有正确退出。 - mal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接