Diferencia entre revisiones de «Ejemplo Sencillo de TCP/IP con netty»

De ChuWiki
Saltar a: navegación, buscar
(El Servidor)
(El cliente)
Línea 54: Línea 54:
 
Para abrir un socket cliente en otro PC que se conecte a nuestro servidor, el código es como este pero algo más sencillo.
 
Para abrir un socket cliente en otro PC que se conecte a nuestro servidor, el código es como este pero algo más sencillo.
  
<syntaxhihglight lang="java">
+
<syntaxhighlight lang="java">
 
int port = 8080;
 
int port = 8080;
  

Revisión del 15:41 1 ago 2018

Netty es una librería Java que nos facilita todo el uso de sockets. Está pensada para ser eficiente y consumir pocos recursos, además de tener ya implementados bastantes protocolos estándar, ahorrándonos el trabajo de hacerlo a nosotros o buscar otras librerías.

Veamos aquí un ejemplo básico de cómo hacer un socket TCP/IP. Haremos un servidor que admitirá varios clientes, cada cliente enviará un texto periodicamente y el servidor se lo reenviará al resto de clientes. Lo haremos con lo básico de Netty, sin utilizar ninguno de los encoder o decoder que netty nos proporiciona.

Aquí tienes el código de ejemplo de socket tcp/ip con netty

El Servidor

En netty el código para abrir un socket servidor TCP/IP es más o menos siempre el mismo, copy-paste y lo explicamos un poco

int port = 8080; // El puerto que queramos

EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup(); // (2)

try {
   ServerBootstrap b = new ServerBootstrap(); // (3)
   b.group(bossGroup, workerGroup)  // (4)
      .channel(NioServerSocketChannel.class) // (5)
      .childHandler(new ChannelInitializer<SocketChannel>() { // (6)
          @Override
          public void initChannel(SocketChannel ch) throws Exception { // (7)
             ch.pipeline().addLast(serverHandler); // (8)
          }
      })
      .option(ChannelOption.SO_BACKLOG, 128)          // (9)
      .childOption(ChannelOption.SO_KEEPALIVE, true); // (10)
       
   ChannelFuture f = b.bind(port).sync(); // (11)

   f.channel().closeFuture().sync(); // (12)

Veamos todo esto

  • (1) Netty está orientado a eventos. Se producen eventos y se propagan cuando se abre una conexión, cuando se acepta un cliente, cuando se cierra una conexión, cuando llega un mensaje, etc, etc, etc. Todos estos eventos se tratan en un bucle de eventos en una serie de hilos que maneja netty. Netty tiene varias clases que implementan estos bucles de eventos con hilos, entre ellas NioEventLoopGroup. Así que lo primero que hacemos es crear una de estas para los eventos del socket servidor. Cada vez que se acepte una conexión de un cliente o se cierre, será este bucle de eventos en el encargado de detectarlo y avisar a todos los que tengan interés en ello.
  • (2) Idem a (1), pero es el bucle de eventos para los eventos de clientes ya conectados, cuando se envían o reciben mensajes de ellos, también cuando se desconecta un cliente concreto, etc. Resumiendo, (1) es para los eventos del socket servidor y (2) para los eventos en cada uno de los socket con los clientes.
  • (3) ServerBootstrap es una clase que nos abre un socket servidor de forma fácil. Podríamos hacerlo usando clases de más bajo nivel de netty (ServerSocketChannel) y así tendriamos más control y libertad para crearlo como queramos, pero en general no es necesario. Basta con instanciar esta clase.
  • (4) Le pasamos los dos bucles de eventos que creamos antes, el del servidor para que acepte conexiones y el de los eventos de cada uno de los clientes ya conectados.
  • (5) Estamos haciendo un Socket Servidor que acepte conexiones de clientes. Netty tiene varias clases que permiten hacer esto, aquí decimos la que queremos usar, NioServerSocketChannel. Esta usa la función select de java.nio. Hay otras implementaciones que usan entradas/salidas bloqueantes, o que usan epoll de linux y que sólo funcionan en linux. Para un java moderno, NioServerSocketChannel es una buena elección.
  • (6) Llamamos al método childHandler pasándole un ChannelInitializer. Cada vez que un cliente se conecte, se llamará a (7) ChannelInitializer.initChannel() pasándole el SocketChannel con el cliente que se acaba de conectar, es decir, la conexión con dicho cliente. Aquí es donde tenemos que hacer cosas de nuestra cosecha para tratar los mensajes que nos envíe el cliente o para enviarle mensajes al cliente.
  • (8) Cada vez que sucede algo con el cliente (llega un mensaje, le enviamos un mensaje, se conecta, se desconecta, etc), netty provoca eventos a los que nos avisa si nos suscribimos. en la línea (8) es donde lo hacemos. En la llamada a ch.pipeline().addLast() pasamos una clase nuestra, serverHandler, que implementa la interfaz de netty correspondiente para recibir todos los eventos que nos interesen en la conexión con el cliente. Veremos esto con más detalle más adelante.
  • (9) Con el método option() podemos pasar todas las opciones que queramos típicas de un socket servidor TCP/IP. El método option() afecta al socket servidor.
  • (10) con el método childOption() podemos pasar todas las opciones que queramos típicas de un socket servidor TCP/IP. El método childOption() afecta a los socket que se abran con el cliente, no al socket servidor.
  • (11) Hacemos el típico bind() del socket servidor, indicando el puerto que queremos. La llamada a bind() vuelve inmediatamente, con la llamada a sync() el código se queda aquí bloqueado hasta que el servidor está en marcha.
  • (12) f.channel() nos devuelve el canal (conexión) que hay en el socket servidor. closeFuture() nos devuelve una "cosa" (un future) que es algo que sucederá pero todavía no ha sucedido y ese algo, en este caso, es que se cierre el socket servidor. Si llamamos al método sync() de esa cosa futura, el método sync() se quedará bloqueado hasta que esa cosa futura (el cierre del socket) sea realidad. Como nadie cierra el socket (en este ejemplo), este sync() se quedará aquí para siempre. Si en algún sitio llamaramos a f.channel().close(), entonces se cerraría el socket y el sync() acabaría saliendo.

El cliente

Para abrir un socket cliente en otro PC que se conecte a nuestro servidor, el código es como este pero algo más sencillo.

int port = 8080;

EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
   Bootstrap b = new Bootstrap(); // (1)
   b.group(workerGroup)
      .channel(NioSocketChannel.class) // (2)
      .handler(new ChannelInitializer<SocketChannel>() { // (3)
          @Override
          public void initChannel(SocketChannel ch) throws Exception {
             ch.pipeline().addLast(clientHandler);  // (4)
          }
      })
     .option(ChannelOption.SO_KEEPALIVE, true); // (5)

     // connect.
     ChannelFuture f = b.connect("localhost", port).sync(); // (6)

     // Wait until the server socket is closed.
     // In this example, this does not happen, but you can do that to gracefully
     // shut down your server.
     f.channel().closeFuture().sync();

Es algo más sencillo, comentamos sólo las diferencias

  • (1) Como es un cliente, usamos la clase de utilidad Bootstrap en vez de ServerBootstrap. Como vimos antes, se podría hacer usando clases de más bajo nivel de Netty, pero esta nos ayuda y es más fácil.
  • (2) Como es cliente, usamos la clase NioSocketChannel en vez de NioServerSocketChannel. Al igual que en el caso del servidor, netty nos ofrece varias implementaciones usando diferentes técnicas, pero NioSocketChannel es una opción adecuada para una versión de java moderna con la api nio.
  • (3) Aquí usamos el método handler() para pasarle el ChannelInitializer. En el servidor usabamos childHandler() porque eran handler para los sockets con los clientes y no para el socker servidor en sí mismo.
  • (4) Como en el caso anterior, aquí es donde debemos pasar un handler encargado de enviar o tratar mensajes del servidor. clientHandler es una instancia de una clase de nuestro código que veremos más adelante.
  • (5) Con sucesivas llamadas a option() podemos pasar todas las opciones relativas a socket tcp/ip que queramos.
  • (6) Llamada a connect() para establecer la conexión. Debemos indicar IP del servidor (localhost en el ejemplo) y puerto de escucha del servidor (8080 en el ejemplo). La llamada a connect() vuelve inmediatamente, con sync() esperamos a que la conexión esté realmente establecida.