Bootstrap

【java网络编程】netty框架

一、简介

netty是一个高性能、异步事件驱动的NIO框架,它基于Java Nio提供的API实现,提供了对TCP、UDP和文件传输的支持。

二、Reactor模型

Reactor是一种并发处理客户端请求响应的事件驱动模型。服务端在接收到客户端请求后采用多路复用策略,通过一个非阻塞的线程来异步接收所有的客户端请求。

(1)Reactor单线程模型

一个线程负责建立连接、读、写等操作。如果在业务中处理中出现了耗时操作,就会导致所有请求全部处理延时。

 (2)Reactor多线程模型

只是在单线程的模型情况下,使用线程池管理多个线程,用多线程进行处理业务。

(3)Reactor主从多线程模型

该模型采用一个主Reactor仅处理连接,而多个子Reactor用于处理IO读写。然后交给线程池处理业务。Tomcat就是采用该模式实现。效率高

三、Netty的核心组件

(1)BootStrap/ServerBootStrap

BootStrap用于客户端服务启动。ServerBootStrap用于服务端启动。

(2)NioEventLoop

基于线程队列的方式执行事件操作,具体要执行的事件操作包括连接注册、端口绑定和I/O数据读写等。每个NioEventLoop线程都负责多个Channel的事件处理。

(3)NioEventLoopGroup

对NioEventLoop的生命周期进行管理

(4)Future/ChannelFuture

用于异步通信的实现,基于异步通信方式可以在I/O操作触发后注册一个事件,在I/O操作(数据读写完成或失败)完成后自动触发监听事件并完成后续操作。

(5)Channel

是netty中的网络通信组件,用于执行具体的I/O操作。主要功能包括:网络连接的建立、连接状态的管理、网络连接参数的配置、基于异步NIO的网络数据操作。

(6)Selector

用于I/O多路复用中Channel的管理

(7)ChannelHandlerContext

Channel上下文信息的管理。每个ChannelHandler都对应一个ChannelHandlerContext。

(8)ChannelHandler

I/O事件的拦截和处理。

其中ChannelInBoundHandler用于处理数据接收的I/O操作。

其中ChannelOutBoundHandler用于处理数据发送的I/O操作。

(9)ChannelPipeline

基于拦截器设计模式实现的事件拦截处理和转发。

每个Channel都对应一个ChannelPipeline,在ChannelPipeline中维护了一个由ChannelHandlerContext组成的双向链表,每个ChannelHandlerContext都对应一个ChannelHandker,以完成具体Channel事件的拦截和处理。

四、netty的运行原理

Server工作流程:

1、server端启动时绑定本地某个端口,初始化NioServerSocketChannel.

2、将自己NioServerSocketChannel注册到某个BossNioEventLoopGroup的selector上。

server端包含1个Boss NioEventLoopGroup和1个Worker NioEventLoopGroup

Boss NioEventLoopGroup专门负责接收客户端的连接,Worker NioEventLoopGroup专门负责网络的读写。

NioEventLoopGroup相当于1个事件循环组,这个组里包含多个事件循环NioEventLoop,每个NioEventLoop包含1个selector和1个事件循环线程。

BossNioEventLoopGroup循环执行的任务:

  1. 轮询accept事件;
  2. 处理accept事件,将生成的NioSocketChannel注册到某一个WorkNioEventLoopGroup的Selector上。
  3. 处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用eventloop.execute或schedule执行的任务,或者其它线程提交到该eventloop的任务。

WorkNioEventLoopGroup循环执行的任务:

  1. 轮询read和Write事件
  2. 处理IO事件,在NioSocketChannel可读、可写事件发生时,回调(触发)ChannelHandler进行处理。
  3. 处理任务队列的任务,即 runAllTasks

五、netty怎么解决粘包拆包的

使用解码器解决,解码器一个有4种

 ch.pipeline().addLast(new LengthFieldBasedFrameDecoder());
  1. 固定长度的拆包器 FixedLengthFrameDecoder , 每个应用层数据包的拆分都是固定长度大小
  2. 行拆包器 LineBasedFrameDecoder ,每个应用层数据包,都以换行符作为分隔符,进行分割拆分
  3. 分隔符拆包器 DelimiterBasedFrameDecoder ,每个应用层数据包,都通过自定义分隔符,进行分割拆分
  4. 基于数据包长度的拆包器,LengthFieldBasedFrameDecoder 将应用层数据包的长度,作为接收端应用层数据包的拆分依据。(常用)

六、netty性能为什么这么高

(1)网络模型(I/O多路复用模型)

多路复用IO,采用一个线程处理连接请求,多个线程处理IO请求,他比BIO能处理更多请求,数据请求和数据处理都是异步,底层采用了linux的select、poll、epoll

(2)数据零拷贝

netty的数据接受和发送均采用对外直接内存进行socket读写,堆外内存可以直接操作系统内存,不需要来回的进行字节缓冲区的二次复制。同时netty提供了组合buffer对象,可以避免传统通过内存复制的方式合并buffer时带来的性能损耗。

netty文件传输采用transfeTo方法操作,完全零拷贝。

(3)内存重用机制

使用堆外直接内存,重用缓冲区,不需要jvm回收内存

(4)无锁设计

netty内部采用串行无锁化设计对I/O操作,避免多线程竞争CPU和资源锁定导致的性能下降。

(5)高性能的序列化框架

使用google的protoBuf实现数据的序列化

(6)使用FastThreadLocal类

使用FastThreadLocal类替代ThreadLocal类

七、netty使用

1、引入依赖


        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.0.36.Final</version>
        </dependency>

2、编写服务端代码(固定写法)

public class Server {  
  
    private int port;  
  
    public Server(int port) {  
        this.port = port;  
    }  
  
    public void run() {  
        EventLoopGroup bossGroup = new NioEventLoopGroup(); //用于处理服务器端接收客户端连接  
        EventLoopGroup workerGroup = new NioEventLoopGroup(); //进行网络通信(读写)  
        try {  
            ServerBootstrap bootstrap = new ServerBootstrap(); //辅助工具类,用于服务器通道的一系列配置  
            bootstrap.group(bossGroup, workerGroup) //绑定两个线程组  
                    .channel(NioServerSocketChannel.class) //指定NIO的模式  
                    .childHandler(new ChannelInitializer<SocketChannel>() { //配置具体的数据处理方式  
                        @Override  
                        protected void initChannel(SocketChannel socketChannel) throws Exception {  
                            socketChannel.pipeline().addLast(new ServerHandler());  
                        }  
                    })  
                    /** 
                     * 对于ChannelOption.SO_BACKLOG的解释: 
                     * 服务器端TCP内核维护有两个队列,我们称之为A、B队列。客户端向服务器端connect时,会发送带有SYN标志的包(第一次握手),服务器端 
                     * 接收到客户端发送的SYN时,向客户端发送SYN ACK确认(第二次握手),此时TCP内核模块把客户端连接加入到A队列中,然后服务器接收到 
                     * 客户端发送的ACK时(第三次握手),TCP内核模块把客户端连接从A队列移动到B队列,连接完成,应用程序的accept会返回。也就是说accept 
                     * 从B队列中取出完成了三次握手的连接。 
                     * A队列和B队列的长度之和就是backlog。当A、B队列的长度之和大于ChannelOption.SO_BACKLOG时,新的连接将会被TCP内核拒绝。 
                     * 所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户端无法连接。要注意的是,backlog对程序支持的 
                     * 连接数并无影响,backlog影响的只是还没有被accept取出的连接 
                     */  
                    .option(ChannelOption.SO_BACKLOG, 128) //设置TCP缓冲区  
                    .option(ChannelOption.SO_SNDBUF, 32 * 1024) //设置发送数据缓冲大小  
                    .option(ChannelOption.SO_RCVBUF, 32 * 1024) //设置接受数据缓冲大小  
                    .childOption(ChannelOption.SO_KEEPALIVE, true); //保持连接  
            ChannelFuture future = bootstrap.bind(port).sync();  
            future.channel().closeFuture().sync();  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            workerGroup.shutdownGracefully();  
            bossGroup.shutdownGracefully();  
        }  
    }  
  
    public static void main(String[] args) {  
        new Server(8379).run();  
    }  
}  

 3、编写Handler处理类


public class ServerHandler  extends ChannelHandlerAdapter {  
  
    @Override  
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
      
            //do something msg  
            ByteBuf buf = (ByteBuf)msg;  
            byte[] data = new byte[buf.readableBytes()];  
            buf.readBytes(data);  
            String request = new String(data, "utf-8");  
            System.out.println("Server: " + request);  
            //写给客户端  
            String response = "我是反馈的信息";  
            ctx.writeAndFlush(Unpooled.copiedBuffer("888".getBytes()));  
            //.addListener(ChannelFutureListener.CLOSE);  
              
  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        cause.printStackTrace();  
        ctx.close();  
    }  
  
}  

;