Netty入门

Posted by Kaka Blog on December 21, 2018

简介

Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架

NIO与BIO

NIO通信方式: img

BIO通信方式: img

当一个连接建立之后,他有两个步骤要做,第一步是接收完客户端发过来的全部数据,第二步是服务端处理完请求业务之后返回response给客户端。NIO和BIO的区别主要是在第一步。

  • BIO(Blocking I/O,阻塞IO) 在BIO中,等待客户端发数据这个过程是阻塞的,这样就造成了一个线程只能处理一个请求的情况,而机器能支持的最大线程数是有限的,这就是为什么BIO不能支持高并发的原因。如果连接少,他的延迟是最低的,因为一个线程只处理一个连接,适用于少连接且延迟低的场景,比如说数据库连接。

  • NIO(Nonblocking I/O,非阻塞IO) 而NIO中,当一个Socket建立好之后,Thread并不会阻塞去接受这个Socket,而是将这个请求交给Selector,Selector会不断的去遍历所有的Socket,一旦有一个Socket建立完成,他会通知Thread,然后Thread处理完数据再返回给客户端——这个过程是阻塞的,这样就能让一个Thread处理更多的请求了。适用于高并发且处理简单的场景,比如聊天软件。

快速上手

1.添加依赖

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.24.Final</version>
    <scope>compile</scope>
</dependency>

2.新建DiscardServerHandler

实现消息的接收,并打印到控制台。

public class DiscardServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        try {
            while (byteBuf.isReadable()) {
                System.out.print((char)byteBuf.readByte());
                System.out.flush();
            }
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

3.新建DiscardServer

public class DiscardServer {
    private int port;

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

    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new DiscardServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            ChannelFuture f = bootstrap.bind(this.port).sync();
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 6666;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        }
        new DiscardServer(port).run();
    }
}
  • NioEventLoopGroup可以理解为一个线程池,内部维护了一组线程,每个线程负责处理多个Channel上的事件,而一个Channel只对应于一个线程。
  • ServerBootstrap服务器端启动器,group(EventLoopGroup...)方法用于指定一个或两个Reactor,本例中指定为两个。channel(Channel)方法本质用来指定一个Channel工厂,本例中该工厂生产服务端用于accept客户端连接的Channel,将默认使用Channel的无参构造方法。childHandler()用于指定subReactor中的处理器。ChannelInitializer它是一个特殊的Handler,功能是初始化多个Handler。option(Key, Value)用于指定TCP相关的参数以及一些Netty自定义的参数。bind(int)方法将服务端Channel绑定到本地端口

4.测试

telnet 127.0.0.1 6666,在telnet窗口输入,控制台会对应输出内容。

总结

Netty实现通讯的步骤:

1、创建两个NIO线程组,一个专门用来网络事件处理(接受客户端连接),另一个则进行网络通讯读写。

2、创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传入数据的缓存大小等。

3、创建一个实际处理数据的类ChannelInitializer,进行初始化的准备工作,比如设置传入数据的字符集,格式,实现实际处理数据的接口。

4、绑定端口,执行同步阻塞方法等待服务器启动即可。

参考

Netty入门教程——认识Netty

User guide for 4.x

自顶向下深入分析Netty(三)–Bootstrap