Quiero ser notificado cuando un SocketChannel
tiene su método close
llamado. Mi primer pensamiento fue crear un contenedor que notifica a un oyente cuando se llama al método implCloseSelectableChannel
(ya que el método close
se declara final
en AbstractInterruptibleChannel
). Esta solución funciona, pero cuando traté de registrarlo con un Selector
que obtendría un IllegalSelectorException
por lo siguiente cheque en SelectorImpl
:¿Cómo ser notificado cuando se cierra un SocketChannel?
/* */ protected final SelectionKey register(AbstractSelectableChannel paramAbstractSelectableChannel, int paramInt, Object paramObject)
/* */ {
/* 128 */ if (!(paramAbstractSelectableChannel instanceof SelChImpl))
/* 129 */ throw new IllegalSelectorException();
Ahora no puede reemplazar el método register
delegar en el SocketChannel
envuelta porque está declarado final
en AbstractSelectableChannel
y no puedo implementar SelChImpl
porque tiene visibilidad predeterminada en el paquete sun.nio.ch
. La única manera que puedo ver para proceder desde aquí sería hacer mi propio SelectorProvider
y Selector
, pero eso parece exagerado por algo tan simple.
¿Hay alguna manera más fácil de recibir notificaciones cuando se ha cerrado SocketChannel
o tengo que volver a pensar en el diseño de mi programa?
SocketChannelWrapper
ejemplo:
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);
}
}
El problema es que si mi programa funciona correctamente, necesito que quien cierre el 'SocketChannel' me lo notifique. Si no lo hacen, las cosas comienzan a romperse. Llámame flojo/olvidadizo, pero la forma más fácil de lograr esto sería simplemente tener la devolución de llamada directamente en el 'SocketChannel'. Es posible que deba replantear mi diseño si esto sigue siendo un problema. – Jeffrey
@Jeffrey Debes depurar tu aplicación. – EJP
No tengo errores en mi aplicación, pero si me olvido de avisarme, aparece un error. Hasta ahora he resuelto el problema, tuve un momento de "doh" y seguí adelante, pero uno de estos días me olvidaré de avisarme a mí mismo y no podré descubrir por qué. Quería ver si podía modificar el funcionamiento de 'SocketChannel' para ahorrarme algunos problemas. – Jeffrey