网络应用框架Netty快速入门

Posted Java面试通关手册

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络应用框架Netty快速入门相关的知识,希望对你有一定的参考价值。

一 初遇Netty

Netty是什么?

Netty 是一个提供 asynchronous event-driven (异步事件驱动)的网络应用框架,是一个用以快速开发高性能、可扩展协议的服务器和客户端。

Netty能做什么?

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

Netty为什么好?

Netty是建立在NIO基础之上,Netty在NIO之上又提供了更高层次的抽象,使用它你可以更容易利用Java NIO提高服务端和客户端的性能。

Netty的特性:

1. 设计

 
   
   
 
  1. 1.1 统一的API,适用于不同的协议(阻塞和非阻塞)

  2. 1.2 基于可扩展和灵活的事件驱动模型

  3. 1.3高度可定制的线程模型 - 单线程,一个或多个线程池,如SEDA

  4. 1.4真正的无连接数据报套接字支持(自3.1以来)

2. 性能

 
   
   
 
  1. 2.1更好的吞吐量,低延迟

  2. 2.2更省资源

  3. 2.3尽量减少不必要的内存拷贝

3. 安全

 
   
   
 
  1. 完整的SSL / TLSStartTLS协议的支持

4. 易用性

 
   
   
 
  1. 4.1 官方有详细的使用指南

  2. 4.2 对环境要求很低

NIO和IO的区别是什么?

1. 一个面向字节一个面向缓冲;

IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

2. NIO是非阻塞IO,IO是阻塞IO

阻塞意味着当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入,该线程在此期间不能再干任何事情了。而非阻塞不会这样。

二 Netty使用

环境要求:

  • JDK 7+

  • Maven 3.2.x

  • Netty 4.x

Maven依赖:

 
   
   
 
  1.        <dependency>

  2.            <groupId>io.netty</groupId>

  3.            <artifactId>netty-all</artifactId>

  4.            <version>4.0.32.Final</version>

  5.        </dependency>

以下Netty examples来源: 官方文档

2.1 写个抛弃服务器

DiscardServerHandler.java

 
   
   
 
  1. import io.netty.buffer.ByteBuf;

  2. import io.netty.channel.ChannelHandlerContext;

  3. import io.netty.channel.ChannelInboundHandlerAdapter;

  4. /**

  5. * handler 是由 Netty 生成用来处理 I/O 事件的。

  6. */

  7. public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)

  8.    /**

  9.     * 这里我们覆盖了 chanelRead() 事件处理方法。

  10.     * 每当从客户端收到新的数据时,这个方法会在收到消息时被调用。

  11.     *((ByteBuf) msg).release():丢弃数据

  12.     */

  13.    @Override

  14.    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)

  15.        // 默默地丢弃收到的数据

  16.        ((ByteBuf) msg).release(); // (3)

  17.    }

  18.    @Override

  19.    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)

  20.        // 当出现异常就关闭连接

  21.        cause.printStackTrace();

  22.        ctx.close();

  23.    }

  24. }

目前我们已经实现了 DISCARD 服务器的一半功能,剩下的需要编写一个 main() 方法来启动服务端的 DiscardServerHandler。

DiscardServer.java

 
   
   
 
  1. import io.netty.bootstrap.ServerBootstrap;

  2. import io.netty.channel.ChannelFuture;

  3. import io.netty.channel.ChannelInitializer;

  4. import io.netty.channel.ChannelOption;

  5. import io.netty.channel.EventLoopGroup;

  6. import io.netty.channel.nio.NioEventLoopGroup;

  7. import io.netty.channel.socket.SocketChannel;

  8. import io.netty.channel.socket.nio.NioserverSocketChannel;

  9. /**

  10. * 启动服务端的 DiscardServerHandler

  11. */

  12. public class DiscardServer {

  13.    private int port;

  14.    public DiscardServer(int port) {

  15.        this.port = port;

  16.    }

  17.    public void run() throws Exception {

  18.        //在这个例子中我们实现了一个服务端的应用,因此会有2个 NioEventLoopGroup 会被使用。

  19.        //第一个经常被叫做‘boss’,用来接收进来的连接。

  20.        //第二个经常被叫做‘worker’,用来处理已经被接收的连接,一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。

  21.        EventLoopGroup bossGroup = new NioEventLoopGroup();

  22.        EventLoopGroup workerGroup = new NioEventLoopGroup();

  23.        try {

  24.            //启动 NIO 服务的辅助启动类

  25.            ServerBootstrap serverBootstrap = new ServerBootstrap();

  26.            //用于处理ServerChannel和Channel的所有事件和IO。

  27.            serverBootstrap.group(bossGroup, workerGroup)

  28.             .channel(NioServerSocketChannel.class) // (3)

  29.             .childHandler(new ChannelInitializer<SocketChannel>() { // (4)

  30.                 @Override

  31.                 public void initChannel(SocketChannel ch) throws Exception {

  32.                     ch.pipeline().addLast(new DiscardServerHandler());

  33.                 }

  34.             })

  35.             .option(ChannelOption.SO_BACKLOG, 128)          // (5)

  36.             .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

  37.            // 绑定端口,开始接收进来的连接

  38.            ChannelFuture f = serverBootstrap.bind(port).sync(); // (7)

  39.            // 等待服务器  socket 关闭 。

  40.            // 在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。

  41.            f.channel().closeFuture().sync();

  42.        } finally {

  43.            workerGroup.shutdownGracefully();

  44.            bossGroup.shutdownGracefully();

  45.        }

  46.    }

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

  48.        int port;

  49.        if (args.length > 0) {

  50.            port = Integer.parseInt(args[0]);

  51.        } else {

  52.            port = 8080;

  53.        }

  54.        new DiscardServer(port).run();

  55.    }

  56. }

2.2 查看收到的数据

我们刚刚已经编写出我们第一个服务端,我们需要测试一下他是否真的可以运行。最简单的测试方法是用 telnet 命令。例如,你可以在命令行上输入telnet localhost 8080 或者其他类型参数。

然而我们能说这个服务端是正常运行了吗?事实上我们也不知道,因为他是一个 discard 服务,你根本不可能得到任何的响应。为了证明他仍然是在正常工作的,让我们修改服务端的程序来打印出他到底接收到了什么。

我们已经知道 channelRead() 方法是在数据被接收的时候调用。让我们放一些代码到 DiscardServerHandler 类的 channelRead() 方法。

修改DiscardServerHandler类的channelRead(ChannelHandlerContext ctx, Object msg)方法如下:

 
   
   
 
  1. @Override

  2. public void channelRead(ChannelHandlerContext ctx, Object msg) {

  3.    ByteBuf in = (ByteBuf) msg;

  4.    try {

  5.        while (in.isReadable()) { // (1)

  6.            System.out.print((char) in.readByte());

  7.            System.out.flush();

  8.        }

  9.    } finally {

  10.        ReferenceCountUtil.release(msg); // (2)

  11.    }

  12. }

再次验证,cmd下输入:telnet localhost 8080。你将会看到服务端打印出了他所接收到的消息。

如下:

你在dos界面输入的消息会被显示出来

2.3 写个应答服务器

到目前为止,我们虽然接收到了数据,但没有做任何的响应。然而一个服务端通常会对一个请求作出响应。让我们学习怎样在 ECHO 协议的实现下编写一个响应消息给客户端,这个协议针对任何接收的数据都会返回一个响应。

和 discard server 唯一不同的是把在此之前我们实现的 channelRead() 方法,返回所有的数据替代打印接收数据到控制台上的逻辑。因此,需要把 channelRead() 方法修改如下:

 
   
   
 
  1.    @Override

  2.    public void channelRead(ChannelHandlerContext ctx, Object msg) {

  3.        ctx.write(msg);

  4.        ctx.flush();

  5.    }

再次验证,cmd下输入:telnet localhost 8080。你会看到服务端会发回一个你已经发送的消息。如下:

下一篇我们会学习如何用Netty实现聊天功能。


以上是关于网络应用框架Netty快速入门的主要内容,如果未能解决你的问题,请参考以下文章

网络开发的最强大框架:Netty快速入门

Netty|01 入门学习

Netty入门看这一篇就够了

Netty入门

Netty入门——概述

Netty入门——概述