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

我想在SocketChannel调用close方法时收到通知。 我的第一个想法是创建一个包装器,它在调用implCloseSelectableChannel方法时通知监听器(因为close方法本身在AbstractInterruptibleChannel声明为final )。 这个解决方案有效,但是当我尝试用Selector注册它时,由于Selector中的以下检查,我会得到一个IllegalSelectorException

 /* */ 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 getOption(SocketOption 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  SocketChannel setOption(SocketOption 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 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); } } 

如果您关闭了SocketChannel ,则表示您正在关闭它,因此您可以按照自己喜欢的方式通知自己。

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

这很讨厌。 你可以使用像http://www.csg.ci.iu-tokyo.ac.jp/~chiba/javassist/这样的字节级aop包(用aop你可以在close方法上添加一个cutpoint做你的回调)。

您还可以创建一个与sun包同名的包,并在那里实现接口。

但我看不出一个好的,干净的方式来做到这一点。