2012-02-25 22 views
8

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); 
    } 
} 

Respuesta

12

Si cerró el SocketChannel, lo está cerrando para que pueda notificarlo de la forma que desee.

Si desea recibir una notificación cuando el pares cierra la conexión ,, OP_READ disparará y una lectura devolverá -1.

+0

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

+0

@Jeffrey Debes depurar tu aplicación. – EJP

+0

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

-1

que es desagradable. podrías usar un paquete de aop de nivel de bytes como http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/ (con aop podrás añadir un punto de corte en el método de cierre que hace tu devolución de llamada).

también podría crear un paquete con el mismo nombre que el paquete solar e implementar la interfaz allí.

pero no puedo ver una buena y limpia forma de hacerlo.

+0

Ninguno de estos parece ser una buena práctica de programación, pero los tendré en cuenta. – Jeffrey

+0

estoy de acuerdo. Recuerdo vagamente tener este mismo problema hace años y no encontrar nada mejor. –

Cuestiones relacionadas