How to be notified when a SocketChannel is closed?
Asked Answered
M

2

9

I want to be notified when a SocketChannel has its close method called. My first thought was to create a wrapper which notifies a listener when the implCloseSelectableChannel method is called (since the close method itself is declared final in AbstractInterruptibleChannel). This solution works, but when I tried to register it with a Selector I would get an IllegalSelectorException because of the following check in SelectorImpl:

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

Now I can't override the register method to delegate to the wrapped SocketChannel because it's declared final in AbstractSelectableChannel and I can't implement SelChImpl because it has default visibility in the sun.nio.ch package. The only way I can see to proceed from here would be to make my own SelectorProvider and Selector, but that seems like overkill for something so simple.

Is there an easier way to be notified when a SocketChannel has been closed or do I need to rethink my program design?

SocketChannelWrapper example:

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);
    }
}
Mcgarry answered 25/2, 2012 at 16:52 Comment(0)
C
17

If the SocketChannel is closed by you, you are closing it, so you can notify yourself any way you like.

If you want to be notified when the peer closes the connection,, OP_READ will fire and a read will return -1.

Caesarean answered 27/2, 2012 at 8:58 Comment(6)
The problem is that if my program is to work right, I need whoever is closing the SocketChannel to notify me. If they don't, things start breaking. Call me lazy / forgetful but the easiest way to accomplish this would be to just have the callback right in the SocketChannel. I may need to rethink my design if this continues to be an issue.Mcgarry
@Mcgarry You need to debug your application.Caesarean
I don't have bugs in my application, but if I forget to notify myself an error pops up. So far I have figured out the problem, had a "doh" moment and moved on, but one of these days I'm going to forget to notify myself and I won't be able to figure out why. I wanted to see if I could modify how SocketChannel worked to save me some trouble.Mcgarry
@Mcgarry I have miles of NIO code without notifications of closes. If you close the channel, its key is cancelled and it is deregistered from the Selector next time around. All I have is if (!key.isValid()) continue; in the select loop. If you need more than this I suggest there is something wrong with your design.Caesarean
not if the peer does not do a proper shutdown...in that case you are not notifed and nothing fires.Inflection
@DeanHiller Define 'proper shutdown'. If you mean that the connection gets reset one way or the other, I agree. If you mean that the peer closes his socket without shutting it down first, I do not. NB The question is about closing.Caesarean
P
-1

that's nasty. you could maybe use a byte level aop package like http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/ (with aop you can should be able to add a cutpoint on the close method that does your callback).

you could also create a package with the same name as the sun package and implement the inteface in there.

but i can't see a good, clean way to do this.

Pennypennyaliner answered 25/2, 2012 at 16:58 Comment(2)
Neither of those seem like very good programming practices, but I'll keep them in mind.Mcgarry
i agree. i vaguely remember having this same problem years ago and not finding anything better.Pennypennyaliner

© 2022 - 2024 — McMap. All rights reserved.