netty源码分析(一):netty简介

142 阅读7分钟

一.什么是Netty

查看官网netty.io/  可以看到netty的简介

Netty is an asynchronous event-driven network application framework

for rapid development of maintainable high performance protocol servers & clients.

Netty——是一个异步事件驱动的网络应用程序框架。 Netty 的主要目的是构建基于 NIO(或者是 NIO.2)的高性能协议服务器/客户端

二.为什么使用netty

如果使用原生jdk的话,

1)NIO 的类库和 API 繁杂,使用麻烦:你需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。

2)需要具备其他的额外技能做铺垫:例如熟悉 Java 多线程编程,因为 NIO 编程涉及到 Reactor 模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的 NIO 程序。

3)可靠性能力补齐,开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等等。NIO 编程的特点是功能开发相对容易,但是可靠性能力补齐工作量和难度都非常大。

4)JDK NIO 的 Bug:例如臭名昭著的 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。

三.使用JDK的NIO完成socket通信

原生的nio主要是三大件,分别是Selector,Channel和ByteBuffer,原理图

image.png

service端开启一个线程运行selector, 等待client端的接入,当监听到client接入时,在TCP三次握手后,会建立对应的通道(既channel),然后把channel注册到这个Selector上,并注册关注的事件是"读/写事件",当client需要发送数据时,把数据封装成ByteBuffer,写入channel中,selector感应到channel中的"读/写事件"后,就会处理请求,处理后把需要返回的数据同样封装成ByteBuffer写进channel,传递给client端,代码如下

1)service端

//创建Selector
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket socket = serverSocketChannel.socket();
socket.bind(new InetSocketAddress(9999));

/**
 * 把channel注册到selector'上,注册为"连接"
 */
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
    int numbers = selector.select();
    System.out.println("numbers:" + numbers);
    Set<SelectionKey> keys = selector.selectedKeys();
    System.out.println("SelectionKey:" + keys);

    Iterator<SelectionKey> iterator = keys.iterator();
    while (iterator.hasNext()) {
        SelectionKey selectionKey = iterator.next();
        /** 获得连接*/
        if (selectionKey.isAcceptable()) {

            ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
            SocketChannel socketChannel = channel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
            iterator.remove();/**必须移除*/
            System.out.println("获得客户端连接:" + socketChannel);
        }
        if (selectionKey.isReadable()) {
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            int byteRead = 0;
            final ByteBuffer byteBuffer = ByteBuffer.allocate(5);
            while (true) {
                final int read = socketChannel.read(byteBuffer);
                if (read == 0) {
                    break;
                }
                byteBuffer.flip();
                socketChannel.write(byteBuffer);
                byteRead += read;
            }
            System.out.println("读取:" + byteRead + ",来源于:" + socketChannel);
            iterator.remove();/**必须移除*/
        }
    }

}

2.clinet端代码

 
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
final Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 6666));
System.out.println("客户端发送连接请求"+ LocalDateTime.now());

while (true) {
    selector.select();
    final Set<SelectionKey> selectionKeys = selector.selectedKeys();
    System.out.println("客户端监听到事件"+ LocalDateTime.now());
    for (SelectionKey selectionKey : selectionKeys) {
        if (selectionKey.isConnectable()) {
            //连接
            final SocketChannel client = (SocketChannel) selectionKey.channel();
            if (client.isConnectionPending()) {
                client.finishConnect();
                final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                byteBuffer.put(("client connect:" + LocalDateTime.now()).getBytes());
                byteBuffer.flip();
                client.write(byteBuffer);
                ExecutorService executorService = newSingleThreadExecutor(defaultThreadFactory());
                executorService.submit(() -> {
                    while (true) {
                        final InputStreamReader inputStreamReader = new InputStreamReader(System.in);
                        final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                        try {
                            final String readLine = bufferedReader.readLine();
                            byteBuffer.clear();
                            byteBuffer.put(readLine.getBytes());
                            byteBuffer.flip();
                            client.write(byteBuffer);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                    }

                });
            }

            client.register(selector, SelectionKey.OP_READ);

        } else if (selectionKey.isReadable()) {
            final SocketChannel client = (SocketChannel) selectionKey.channel();
            final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            final int cout = client.read(byteBuffer);
            if (cout > 0) {
                final String receivemsg = new String(byteBuffer.array(), 0, cout);
                System.out.println("收到的数据:" + receivemsg);
            }
        }
    }
    selectionKeys.clear();
}
 

如上是JDK原生的NIO网络编程,可以看到需要自己数量掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。对新手很不友好,所以再次基础上,netty实现了对jdk的nio的封装,并加入了自己的一些优化,让开发着更容易上手使用

四.注意

netty仅仅是封装JDK原生的NIO这一块,并不是自己开发,本质上还是使用JDK的NIO这一套

五.Netty初使用

1-1.服务端-启动类

public class MyServer {
    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //bossGroup.setIoRatio(20); 设置io比例为20%,即非io是io的四倍,执行非io的时间也是io的时间的四倍

        EventLoopGroup workGroup = new NioEventLoopGroup(1);

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // 源码解析
        serverBootstrap
                .group(bossGroup, workGroup)
                .option(ChannelOption.SO_BACKLOG, 1)
                .channel(NioServerSocketChannel.class)
                .handler(new MyServerHandler())
                  /**
                 * todo 这里需要我们自定义编写处理类,处理netty中的数据
                */
                .childHandler(new MyTcpServerInitializer());

        try {
            ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }


    }
}

1-2服务端-自定义ChannelInitializer--MyTcpServerInitializer

 
public class MyTcpServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();

        System.out.println("netty 执行MyTcpServerInitializer的initChannel方法,socketChannel:" + socketChannel.toString());
        //心跳
  //   pipeline.addLast("idleStateHandler", new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
        pipeline.addLast("decode", new MyDecode());
        pipeline.addLast("encode", new MyEncode());
        //在这里添加具体的handler
        pipeline.addLast(new MyTcpServerHandler());
    }

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

1-3.添加具体的handler处理类

public class MyTcpServerHandler extends SimpleChannelInboundHandler<Person> {


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        super.channelRead(ctx, msg);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Person person) throws Exception {
        System.out.println("服务器端接收的数据" + person);
        ctx.writeAndFlush(person);
        /**
         * 给客户端回数据
         */
        final ByteBuf byteBuf = Unpooled.copiedBuffer(UUID.randomUUID().toString(), CharsetUtil.UTF_8);
         ctx.writeAndFlush(byteBuf);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
 
    }
}

2-1. 客户端-启动类

public class TestTcpClient {
    public static void main(String[] args) {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap
                    .group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY,true)
                     /**
                      * todo 这里需要我们自定义编写处理类,处理netty中的数据
                     */
                    .handler(new MyTcpClientInitializer());
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8899);
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

2-2.客户端--自定义ChannelInitializer--MyTcpClientInitializer

public class MyTcpClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //添加具体handler处理类
        pipeline.addLast(new MyTcpClinetHandler());
    }
}

2-3.客户端--自定义handler

public class MyTcpClinetHandler extends SimpleChannelInboundHandler<ByteBuf> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //todo 发送数据
        final ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
        byteBuf.writeShort(4);
        byteBuf.writeBytes(("pqs" + 1).getBytes(StandardCharsets.UTF_8));
        ctx.writeAndFlush(byteBuf);

    }
 

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        System.out.println(System.currentTimeMillis() + "==>channelRead0");
        short length = msg.readShort();
        byte[] buffer = new byte[msg.readableBytes()];
        msg.readBytes(buffer);
        String message = new String(buffer, CharsetUtil.UTF_8);
        System.out.println("client 接收消息" + message);

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println(System.currentTimeMillis() + "==>channelReadComplete");
        super.channelReadComplete(ctx);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println(System.currentTimeMillis() + "==>channelRegistered");
        super.channelRegistered(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println(System.currentTimeMillis() + "==>channelUnregistered");
        super.channelUnregistered(ctx);
    }
}

六.Netty核心模块

通过对 "Netty初使用"中可以看到,netty主要有以下极大部分

  • BootStrap
  • BossGroup/WorkGroup
  • Channel
  • ChannelHandler
  • ChannelPipline
  • EventLoop
  • EventLoopGroup 这几大核心部分,可以用下图说明

image.png

BootStrap

Netty 应用程序通过设置 bootstrap(引导)类的开始,该类提供了一个 用于应用程序网络层配置的容器。有两种类型的引导:一种用于客户端(简单地称为 Bootstrap),而另一种 (ServerBootstrap)用于服务器。 引导类作为netty承上启下作用,能添加 BossGroup/WorkGroup,设置Channel,添加ChannelHandler,并绑定端口/连接服务端,进而启动netty进程

channel

基本的 I/O 操作(bind()、connect()、read()和 write())依赖于底层网络传输所提 供的原语。在基于 Java 的网络编程中,其基本的构造是 class Socket。Netty 的 Channel 接 口所提供的 API,大大地降低了直接使用 Socket 类的复杂性。此外,Channel 也是拥有许多 预定义的、专门化实现的广泛类层次结构的channel类。

ChannelHandler

ChannelHandler 支持很多协议,并且提供用于数据处理的容器。我们已经知道 ChannelHandler 由特定事件触发。 ChannelHandler 可用于几乎所有的动作,包括将一个对象转为字节(或相反),执行过程中抛出的异常处理。

常用的一个接口是 ChannelInboundHandler,这个类型接收到入站事件(包括接收到的数据)可以处理应用程序逻辑。当你需要提供响应时,你也可以从 ChannelInboundHandler 冲刷数据。一句话,业务逻辑经常存活于一个或者多个 ChannelInboundHandler。

ChannelPipline

ChannelPipeline 提供了一个容器给 ChannelHandler 链并提供了一个API 用于管理沿着链入站和出站事件的流动。每个 Channel 都有自己的ChannelPipeline,当 Channel 创建时自动创建的。 ChannelHandler 是如何安装在 ChannelPipeline? 主要是实现了ChannelHandler 的抽象 ChannelInitializer。ChannelInitializer子类 通过 ServerBootstrap 进行注册。当它的方法 initChannel() 被调用时,这个对象将安装自定义的 ChannelHandler 集到 pipeline。当这个操作完成时,ChannelInitializer 子类则 从 ChannelPipeline 自动删除自身。

EventLoop

image.png

EventLoop是netty处理数据的核心,每一个EventLoop里都有一个Selector和taskQueue, 通过启动一个线程,不停的做三件事

  • 1.获取select中的的事件
  • 2.处理这些事件
  • 3.处理taskQueue中的任务

EventLoopGroup

是EventLoop的集合,每个EventLoopGroup中包含一个或多个EventLoop

七.总结

本篇文章主要简要介绍Netty是什么?怎么用?并通过传统NIO编程和Netty编程,熟悉Netty的基本用法,并列举Netty中常用组件,在接下来文章中会深入源码,逐步深入解析各个模块的原理.