欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Netty入门二:Netty概述及Demo

程序员文章站 2022-04-24 11:05:11
...

Netty简述

今天,我们使用通用的应用程序或者类库来实现互相通讯,比如,我们经常使用一个 HTTP 客户端库来从 web 服务器上获取信息,或者通过 web 服务来执行一个远程的调用。

然而,有时候一个通用的协议或他的实现并没有很好的满足需求。比如我们无法使用一个通用的 HTTP 服务器来处理大文件、电子邮件以及近实时消息,比如金融信息和多人游戏数据。我们需要一个高度优化的协议来处理一些特殊的场景。例如你可能想实现一个优化了的 Ajax 的聊天应用、媒体流传输或者是大文件传输器,你甚至可以自己设计和实现一个全新的协议来准确地实现你的需求。

我们知道Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,由于 NIO 原生编程太过于复杂,Netty对其进行了优秀的封装。Netty 大大简化了网络程序的开发过程比如 TCP 和 UDP 的 socket 服务的开发。

通过 Netty 我们可以快速简单地开发网络应用程序,比如服务器(HTTP服务器,FTP服务器,WebSocket服务器,Redis的Proxy服务器等等)和客户端的协议。Netty 大大简化了网络程序的开发过程比如 TCP 和 UDP 的 socket 服务的开发。

Netty和Tomcat有什么区别?

Netty和Tomcat最大的区别就在于通信协议,Tomcat是基于Http协议的,他的实质是一个基于http协议的web容器,但是Netty不一样,他能通过编程自定义各种协议,因为netty能够通过codec自己来编码/解码字节流,完成类似redis访问的功能,这就是netty和tomcat最大的不同。

有人说netty的性能就一定比tomcat性能高,其实不然,tomcat从6.x开始就支持了nio模式,并且后续还有APR模式——一种通过jni调用apache网络库的模式,相比于旧的bio模式,并发性能得到了很大提高,特别是APR模式,而netty是否比tomcat性能更高,则要取决于netty程序作者的技术实力了。

服务器demo

让我们从 handler (处理器)的实现开始,handler 是由 Netty 生成用来处理 I/O 事件的。

 public class EchoServerHandler extends ChannelInboundHandlerAdapter {

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) {
    System.out.println(ctx.channel().remoteAddress() + "->Server :" + msg.toString());
    ctx.write(msg); // (1)
    ctx.flush(); // (2)
    //        final ChannelFuture future = ctx.writeAndFlush(msg);
    //    final ChannelFuture future = ctx.write(msg + "\n");
   
  }

  @Override
  public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    System.out.println("server read complete");
    ctx.flush();
    TimeUnit.MILLISECONDS.sleep(200);
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    // 当出现异常就关闭连接
    cause.printStackTrace();
    ctx.close();
  }
}

  1. EchoServerHandler 继承自 ChannelInboundHandlerAdapter,这个类实现了
    ChannelInboundHandler接口,ChannelInboundHandler
    提供了许多事件处理的接口方法,然后你可以覆盖这些方法。现在仅仅只需要继承 ChannelInboundHandlerAdapter
    类而不是你自己去实现接口方法。

  2. 这里我们覆盖了 chanelRead() 事件处理方法。每当从客户端收到新的数据时,这个方法会在收到消息时被调用。

  3. ChannelHandlerContext 对象提供了许多操作,使你能够触发各种各样的 I/O 事件和操作。这里我们调用了 write(Object) 方法来逐字地把接受到的消息写入。

  4. ctx.write(Object) 方法不会使消息写入到通道上,他被缓冲在了内部,你需要调用 ctx.flush() 方法来把缓冲区中数据强行输出。或者你可以用更简洁的 cxt.writeAndFlush(msg) 以达到同样的目的。

  5. exceptionCaught() 事件处理方法是当出现 Throwable 对象才会被调用,即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时。在大部分情况下,捕获的异常应该被记录下来并且把关联的 channel 给关闭掉。然而这个方法的处理方式会在遇到不同异常的情况下有不同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。

目前为止一切都还不错,我们已经实现了服务器的一半功能,剩下的需要编写一个 main() 方法来启动服务端的 EchoServerHandler。

/** 应答服务器 */
public class EchoServer {

  private int port;

  public EchoServer(int port) {
    this.port = port;
  }

  public void run() throws Exception {
    EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    try {
      ServerBootstrap b = new ServerBootstrap(); // (2)
      b.group(bossGroup, workerGroup)
          .channel(NioServerSocketChannel.class) // (3)
          .childHandler(
              new ChannelInitializer<SocketChannel>() { // (4)
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                  ch.pipeline().addLast(new EchoServerHandler());
                }
              })
          .option(ChannelOption.SO_BACKLOG, 128) // (5)
          .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

      // 绑定端口,开始接收进来的连接
      ChannelFuture f = b.bind(port).sync(); // (7)

      System.out.println("Server start listen at " + port);
      // 等待服务器  socket 关闭 。
      // 在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。
      f.channel().closeFuture().sync();
    } finally {
      workerGroup.shutdownGracefully();
      bossGroup.shutdownGracefully();
    }
  }

  public static void main(String[] args) throws Exception {
    int port;
    if (args.length > 0) {
      port = Integer.parseInt(args[0]);
    } else {
      port = 8080;
    }
    new EchoServer(port).run();
  }
}
  1. NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器,Netty 提供了许多不同的 EventLoopGroup 的实现用来处理不同的传输。在这个例子中我们实现了一个服务端的应用,因此会有2个
    NioEventLoopGroup
    会被使用。第一个经常被叫做‘boss’,用来接收进来的连接。第二个经常被叫做‘worker’,用来处理已经被接收的连接,一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。如何知道多少个线程已经被使用,如何映射到已经创建的
    Channel上都需要依赖于 EventLoopGroup 的实现,并且可以通过构造函数来配置他们的关系。

  2. ServerBootstrap 是一个启动 NIO 服务的辅助启动类。你可以在这个服务中直接使用
    Channel,但是这会是一个复杂的处理过程,在很多情况下你并不需要这样做。

  3. 这里我们指定使用 NioServerSocketChannel 类来举例说明一个新的 Channel 如何接收进来的连接。

  4. 这里的事件处理类经常会被用来处理一个最近的已经接收的 Channel。ChannelInitializer
    是一个特殊的处理类,他的目的是帮助使用者配置一个新的
    Channel。也许你想通过增加一些处理类比如DiscardServerHandler 来配置一个新的 Channel
    或者其对应的ChannelPipeline 来实现你的网络程序。当你的程序变的复杂时,可能你会增加更多的处理类到 pipline
    上,然后提取这些匿名类到最顶层的类上。

  5. 你可以设置这里指定的 Channel 实现的配置参数。我们正在写一个TCP/IP 的服务端,因此我们被允许设置 socket
    的参数选项比如tcpNoDelay 和 keepAlive。请参考 ChannelOption 和详细的 ChannelConfig
    实现的接口文档以此可以对ChannelOption 的有一个大概的认识。

  6. 你关注过 option() 和 childOption() 吗?option() 是提供给NioServerSocketChannel
    用来接收进来的连接。childOption() 是提供给由父管道 ServerChannel 接收到的连接,在这个例子中也是
    NioServerSocketChannel。

  7. 我们继续,剩下的就是绑定端口然后启动服务。这里我们在机器上绑定了机器所有网卡上的 8080 端口。当然现在你可以多次调用 bind()
    方法(基于不同绑定地址)。

恭喜!你已经熟练地完成了第一个基于 Netty 的服务端程序。

客户端demo

**
 * Sends one message when a connection is open and echoes back any received data to the server.
 * Simply put, the echo client initiates the ping-pong traffic between the echo client and server by
 * sending the first message to the server.
 */
public final class EchoClient {

  static final String HOST = System.getProperty("host", "127.0.0.1");
  static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));

  public static void main(String[] args) throws Exception {

    // Configure the client.
    EventLoopGroup group = new NioEventLoopGroup();
    try {
      Bootstrap b = new Bootstrap();
      b.group(group)
          .channel(NioSocketChannel.class)
          .option(ChannelOption.TCP_NODELAY, true)
          .handler(
              new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                  ChannelPipeline p = ch.pipeline();
                  p.addLast(new EchoClientHandler());
                }
              });

      // Start the client.
      ChannelFuture f = b.connect(HOST, PORT).sync();

      // Wait until the connection is closed.
      f.channel().closeFuture().sync();
    } finally {
      // Shut down the event loop to terminate all threads.
      group.shutdownGracefully();
    }
  }
}

  1. BootStrap 和 ServerBootstrap 类似,不过他是对非服务端的 channel 而言,比如客户端或者无连接传输模式的 channel。

  2. 如果你只指定了一个 EventLoopGroup,那他就会即作为一个 boss group ,也会作为一个 workder group,尽管客户端不需要使用到 boss worker 。

  3. 代替NioServerSocketChannel的是NioSocketChannel,这个类在客户端channel 被创建时使用。

  4. 不像在使用 ServerBootstrap 时需要用 childOption() 方法,因为客户端的 SocketChannel 没有父亲。

  5. 我们用 connect() 方法代替了 bind() 方法。

public class EchoClientHandler extends ChannelInboundHandlerAdapter {

  private final String firstMessage;

  /** Creates a client-side handler. */
  public EchoClientHandler() {
    //         firstMessage = Unpooled.buffer(EchoClient.SIZE);
    //         for (int i = 0; i < firstMessage.capacity(); i ++) {
    //              firstMessage.writeByte((byte) i);
    //         }
    ////         firstMessage.writeByte('\n');
    firstMessage = "hello\n";
  }

  @Override
  public void channelActive(ChannelHandlerContext ctx) {
    ctx.writeAndFlush(firstMessage);
    System.out.println("channel active.");
  }

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) {
    System.out.println("channel read from server: " + msg);
    ctx.write(msg + "\n");
  }

  @Override
  public void channelReadComplete(ChannelHandlerContext ctx) {
    ctx.flush();
    System.out.println("channel read complete");
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    // Close the connection when an exception is raised.
    cause.printStackTrace();
    ctx.close();
  }
}

参考文章:
https://waylau.com/netty-4-user-guide/

相关标签: Netty