Ejemplo multicast con Netty

De ChuWiki

Vamos con un ejemplo multicast en netty. Aunque he mirado el que hay por google, veo que muchas de las opciones que pone no sirven aparentemente para nada, así que el de aquí es con lo mínimo que he visto imprescindible para hacerlo funcionar. Aquí tienes el ejemplo de multicast con netty

El servidor (el que escucha)[editar]

Veamos el código del que se pone a a escucha, lo he llamado servidor, pero no tiene mucho sentido, digo yo. El código es este

        NetworkInterface ni = NetworkInterface.getByInetAddress(InetAddress.getByName("127.0.0.1"));  // (1)

        InetSocketAddress groupAddress = new InetSocketAddress(
                "239.255.27.1", 1234);  //(2)

        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group)  // (3)
            .channelFactory(new ChannelFactory<NioDatagramChannel>() {  // (4)
                @Override
                public NioDatagramChannel newChannel() {
                    return new NioDatagramChannel(InternetProtocolFamily.IPv4);
                }
            })
//            .channel(NioDatagramChannel.class)  // (4)
            .handler(new ChannelInitializer<NioDatagramChannel>() {  // (5)
                @Override
                protected void initChannel(NioDatagramChannel ch) throws Exception {
                    ch.pipeline().addLast(new ServerHandler());
                }
            })
            .option(ChannelOption.SO_REUSEADDR,true);  // (6)

        NioDatagramChannel ch = (NioDatagramChannel)b.bind(groupAddress.getPort()).sync().channel();  // (7)
        ch.joinGroup(groupAddress,ni).sync();  // (8)

        ch.closeFuture().await();
<\syntaxhighlight>

Vamos por partes:

* (1) Necesitamos la interfaz de red por la que vamos a escuchar. Netty no admite ponernos a la escucha de todas las interfaces de red de forma fácil (hay un "workaround" que comento más tarde). Así que cogemos una de ellas por su IP. Como 127.0.0.1 existe en todos los ordenadores (localhost) cogemos esa para que funcione el ejemplo, pero esto obliga a que el que emite lo haga dentro del mismo pc y por esa interfaz de red. Si quieres puedes poner tu IP de red y así escuchas cosas del exterior.
* (2) Una IP de multicast y puerto, para usarla.
* (3) Como ya vimos en [[Ejemplo Sencillo de TCP/IP con netty]] usamos la clase de ayuda Bootstrap de netty para crearnos un canal multicast, al que tenemos que crear un bucle de eventos y pasárselo.
* (4) Con el método channel() podemos pasar el tipo de channel que queremos crear (NioDatagramChannel en nuestro ejemplo). El problema de usar este método y pasar el class, es que se creará con el constructor de NioDatagramChannel por defecto, que cogerá IPv4 o IPv6 según esté configurado por defecto en nuestro ordenador. Si nos vale la opción por defecto, estupendo, pero si no nos vale, es mejor usar en vez de channel() el método channelFactory() al que pasamos una ChanneFactory que en su método newChannel() nos debe devolver una intancia del channel que queremos, en este ejemplo, un NioDatagramChannel configurado para IPv4.
* (5) En el método handler() pasamos una clase ChannelInitializer() y en su método initChannel() metemos un ChannelInboundHandler (ServerHandler) para que nos avisen cuando llegue algún mensaje de red. Es en ServerHandler donde trataremos los mensajes recibidos.
* (6) Opciones para el canal. Por ejemplo, la de REUSEADDR. Podemos añadir todas las options que nos resulten de interés.
* (7) Aunque aquí no tiene sentido, para canales udp, debemos llamar al método bind() de Bootstrap, indicando el puerto multicast al que nos queremos apuntar. Este método bind() nos devuelve una clase ChannelFuture, su método sync() espera a que el Channel esté establecido y su método channel() nos devuelve el channel, que será de tipo NioDatagramChannel.
* (8) Ahora llamamos al joinGroup() para indicar la IP multicast a la que nos queremos unir y es aquí donde debemos indicar la interfaz de red por la que queremos escuchar.

Listo, ya estamos a la escucha. Veamos el código de ServerHandler, donde tratamos los mensajes

<syntaxhighlight lang="java">
public class ServerHandler extends ChannelInboundHandlerAdapter{

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        DatagramPacket packet = (DatagramPacket)msg;
        System.out.println(packet.content().toString(Charset.defaultCharset()));
        ReferenceCountUtil.release(msg);
    }
}

Como en el ejemplo de tcp/ip mencionado antes, sobreescribimos el método channelRead() que es donde nos avisarán cuando llegue un mensaje, que recibiremos como parámetro Object msg. Este msg es de tipo io.netty.channel.socket.DatagramPacket (del paquete de netty, java tiene otra clase similar, no confundir). El método content() nos dará los bytes que nos han enviado desde el otro lado. En nuestro ejemplo, nos darán un String, así que convertimos los bytes (ByteBuf de netty) a String y los sacamos por pantalla.

Como no necesitamos más el msg recibido y no se lo hemos pasado a nadie, nos toca liberarlo con ReferenceCountUtil.release().

El cliente (el que envía)[editar]

Llamamos cliente al que envía los mensajes, por el mismo motivo que llamamos servidor al que los recibe, es decir, ninguno. El código para el cliente es como el siguiente

        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group)
            .channelFactory(new ChannelFactory<NioDatagramChannel>() {
                @Override
                public NioDatagramChannel newChannel() {
                    return new NioDatagramChannel(InternetProtocolFamily.IPv4);
                }
            })
//                .channel(NioDatagramChannel.class)
                .handler(new ChannelInitializer<NioDatagramChannel>() {
                    @Override
                    protected void initChannel(NioDatagramChannel ch) throws Exception {
                        ch.pipeline().addLast(new ClientHandler());
                    }
                })
                .option(ChannelOption.SO_REUSEADDR,true);



        NioDatagramChannel ch = (NioDatagramChannel)b.bind(
                "127.0.0.1",1234).sync().channel();


        new Thread(){
            public void run() {
                while (true){
                    try {
                        Thread.sleep(1000);
                        myHandler.sendMessage("Hola desde netty");
                        System.out.println("Envio Hola");
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        ch.closeFuture().await();

La parte de creación de Bootstrap con todos sus intringulis es igual que en el servidor, ya está comentado. Vamos con lo que es diferente :

  • la llamada a bind() debe tener la IP de la interfaz por la que queremos transmitir los mensajes. Si no ponemos la IP ("127.0.0.1" es este caso), cogerá alguna al azar de las configuradas en el PC (la que sea de defecto).
  • No es necesaria la llamada a joinGroup() si solo vamos a transmitir y no ponernos a la escucha.

En el handler hemos pasado un ClientHandler, que hereda de ChannelInboundHandlerAdapter. Veamos el código

public class ClientHandler extends ChannelInboundHandlerAdapter{
    private ChannelHandlerContext ctx;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        this.ctx=ctx;
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        this.ctx=null;
    }

    public void sendMessage(String message){
        if (null==ctx) return;
        ByteBuf buff = ctx.alloc().buffer();
        buff.writeCharSequence(message,Charset.defaultCharset());
        DatagramPacket packet = new DatagramPacket(buff, new InetSocketAddress("239.255.27.1",1234));

        ctx.writeAndFlush(packet);
    }
}

Como hicimos en el ejemplo de TCP/IP, sobreescribimos channelActive() y channelInactive() para enterarnos cuando se establece/pierde el canal y nos guardamos el ChannelHandlerContext para poder enviar mensajes con él cuando nos venga bien.

Añadimos un método sendMessage() con el String que queremos enviar. Dentro de él debemos crear un io.netty.channel.socket.DatagramPacket (no confundir con el de java.net) pasando un ByteBuf con nuestro texto (en forma de bytes) y la ip/puerto multicast al que queremos enviar. La llamada a ctx.writeAndFlush() hará el envío.

pipelines[editar]

Por supuesto, como vimos en Pipeline con netty podemos añadir varios handler encadenados, de forma que bien al recibir, bien al enviar, lo que envía un handler es lo que recibe el siguiente y en el caso de mensajes más complejos que este, podemos partir el tratamiendo del mensaje en clases separadas, cada una especialista en hacer una parte del tratamiento.