当SocketChannel关闭时如何收到通知?

9

我希望在SocketChannelclose方法被调用时收到通知。我的第一个想法是创建一个包装器,在implCloseSelectableChannel方法被调用时通知监听器(因为close方法本身在AbstractInterruptibleChannel中被声明为final)。这个解决方案有效,但当我尝试将其与Selector注册时,会出现IllegalSelectorException,因为在SelectorImpl中有以下检查:

/*     */   protected final SelectionKey register(AbstractSelectableChannel paramAbstractSelectableChannel, int paramInt, Object paramObject)
/*     */   {
/* 128 */     if (!(paramAbstractSelectableChannel instanceof SelChImpl))
/* 129 */       throw new IllegalSelectorException();

现在我无法重写register方法以委托给包装的SocketChannel,因为它在AbstractSelectableChannel中被声明为final,而我又无法实现SelChImpl,因为它在sun.nio.ch包中具有默认可见性。我唯一能想到的继续进行的方式是制作自己的SelectorProviderSelector,但这似乎对于如此简单的事情来说过于繁琐。
是否有更简单的方法来通知我SocketChannel何时已关闭,或者我需要重新考虑我的程序设计? SocketChannelWrapper示例:
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class SocketChannelWrapper extends SocketChannel {
    private static interface CloseListener {
        public void socketChannelClosed(SocketChannel channel);
    }

    private final SocketChannel socket;
    private final CloseListener listener;

    public SocketChannelWrapper(SocketChannel socket, CloseListener l) {
        super(socket.provider());
        this.socket = socket;
        listener = l;
    }

    @Override
    public SocketAddress getLocalAddress() throws IOException {
        return socket.getLocalAddress();
    }

    @Override
    public <T> T getOption(SocketOption<T> name) throws IOException {
        return socket.getOption(name);
    }

    @Override
    public Set<SocketOption<?>> supportedOptions() {
        return socket.supportedOptions();
    }

    @Override
    public SocketChannel bind(SocketAddress local) throws IOException {
        return socket.bind(local);
    }

    @Override
    public <T> SocketChannel setOption(SocketOption<T> name, T value)
            throws IOException {
        return socket.setOption(name, value);
    }

    @Override
    public SocketChannel shutdownInput() throws IOException {
        return socket.shutdownInput();
    }

    @Override
    public SocketChannel shutdownOutput() throws IOException {
        return socket.shutdownOutput();
    }

    @Override
    public Socket socket() {
        return socket.socket();
    }

    @Override
    public boolean isConnected() {
        return socket.isConnected();
    }

    @Override
    public boolean isConnectionPending() {
        return socket.isConnectionPending();
    }

    @Override
    public boolean connect(SocketAddress remote) throws IOException {
        return socket.connect(remote);
    }

    @Override
    public boolean finishConnect() throws IOException {
        return socket.finishConnect();
    }

    @Override
    public SocketAddress getRemoteAddress() throws IOException {
        return socket.getRemoteAddress();
    }

    @Override
    public int read(ByteBuffer dst) throws IOException {
        return socket.read(dst);
    }

    @Override
    public long read(ByteBuffer[] dsts, int offset, int length)
            throws IOException {
        return socket.read(dsts, offset, length);
    }

    @Override
    public int write(ByteBuffer src) throws IOException {
        return socket.write(src);
    }

    @Override
    public long write(ByteBuffer[] srcs, int offset, int length)
            throws IOException {
        return socket.write(srcs, offset, length);
    }

    @Override
    protected void implCloseSelectableChannel() throws IOException {
        socket.close();
        listener.socketChannelClosed(this);
    }

    @Override
    protected void implConfigureBlocking(boolean block) throws IOException {
        socket.configureBlocking(block);
    }

    public static void main(String[] args) throws UnknownHostException,
            IOException {
        final Selector selector = Selector.open();
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        selector.select();
                        Iterator<SelectionKey> itr = selector.selectedKeys()
                                .iterator();
                        while (itr.hasNext()) {
                            SelectionKey key = itr.next();
                            itr.remove();

                            if (key.isValid()) {
                                if (key.isAcceptable()) {
                                    ((ServerSocketChannel) key.channel())
                                            .accept();
                                }
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.setDaemon(true);

        ServerSocketChannel server = ServerSocketChannel.open().bind(
                new InetSocketAddress(1234));
        server.configureBlocking(false);

        server.register(selector, SelectionKey.OP_ACCEPT);
        t.start();

        SocketChannel socket = new SocketChannelWrapper(
                SocketChannel.open(new InetSocketAddress(InetAddress
                        .getLocalHost(), 1234)), new CloseListener() {
                    @Override
                    public void socketChannelClosed(SocketChannel channel) {
                        System.out.println("Socket closed!");
                    }
                });
        socket.configureBlocking(false);
        // socket.close(); //prints out "Socket closed!"
        socket.register(selector, SelectionKey.OP_READ);
    }
}
2个回答

17

如果您关闭了 SocketChannel,那么它就是由您关闭的,因此您可以以任何您喜欢的方式通知自己。

如果您希望在 对等点 关闭 连接 时收到通知,OP_READ 将发生并且读取将返回 -1。


1
问题在于,如果我的程序要正常工作,我需要关闭SocketChannel的人通知我。如果他们不这样做,事情就会开始出错。虽然有点懒惰/健忘,但最简单的方法是在SocketChannel中直接设置回调函数。如果这个问题继续存在,我可能需要重新考虑我的设计。 - Jeffrey
1
我的应用程序没有漏洞,但如果我忘记通知自己,就会弹出错误。到目前为止,我已经找出了问题,有过“噢”时刻,并继续前进,但总有一天我会忘记通知自己,我就无法弄清楚原因了。我想看看是否可以修改SocketChannel的工作方式,以免给自己带来麻烦。 - Jeffrey
1
@Jeffrey,我有很多没有关闭通知的NIO代码。如果您关闭了通道,则其键将被取消并在下一次选择器中注销。在选择循环中,我只有if (!key.isValid()) continue;。如果您需要更多内容,我建议您的设计可能存在问题。 - user207421
如果对等方没有进行适当的关闭,那么您将不会收到通知,也不会触发任何操作。 - Dean Hiller
@pstanton 这是一个问题吗?不是。 - user207421
显示剩余2条评论

-1

这很糟糕。您可以尝试使用字节级AOP包,例如 http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/(使用AOP,您应该能够在close方法上添加切入点以执行回调)。

您也可以创建与sun包同名的包并在其中实现接口。

但我看不到一个好的、干净的解决方法。


1
这些都不是很好的编程实践,但我会记在心里。 - Jeffrey
我同意。我依稀记得多年前遇到过同样的问题,但没有找到更好的解决方法。 - andrew cooke

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