Netty简介

什么是Netty?

Netty 是一个高性能、异步事件驱动的 NIO框架,基于 JAVA NIO 提供的 API 实现。它提供了对
TCP、UDP 和文件传输的支持,作为一个异步 NIO 框架,Netty 的所有 IO 操作都是异步非阻塞
的,通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。

1.Netty和Tomcat有什么区别?
最大的区别在于通信协议,
Tomcat是基于Http协议的,他的实质是一个基于http协议的web容器。
但是Netty能通过编程自定义各种协议,因为netty能够通过codec自己来编码/解码字节流,完成类似redis访问的功能,
这就是netty和tomcat最大的不同。
2.Netty优点:
1.并发高: 基于NIO(非阻塞IO)开发的框架,对于BIO(阻塞IO)并发性能得到提高。
**2.传输快:**依赖NIO的一个特性–零拷贝。
**3.封装好:**非代码说不清哈哈哈

public class NettyOioServer {

    public void server(int port) throws Exception {
        final ByteBuf buf = Unpooled.unreleasableBuffer(
                Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8")));
        EventLoopGroup group = new OioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();        //1

            b.group(group)                                    //2
             .channel(OioServerSocketChannel.class)
             .localAddress(new InetSocketAddress(port))
             .childHandler(new ChannelInitializer<SocketChannel>() {//3
                 @Override
                 public void initChannel(SocketChannel ch) 
                     throws Exception {
                     ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {            //4
                         @Override
                         public void channelActive(ChannelHandlerContext ctx) throws Exception {
                             ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);//5
                         }
                     });
                 }
             });
            ChannelFuture f = b.bind().sync();  //6
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();        //7
        }
    }
}
NIO客户端的写法相对比较简单,利用SocketChannel进行IP和端口的绑定,然后调用connect()方法进行连接到服务端。最后利用ByteBuffer装载数据,通过SocketChannel将数据写出去。
/**
 2 * @author liujinkun
 3 * @Title: NioClient
 4 * @Description: NIO客户端
 5 * @date 2019/11/24 2:55 PM
 6 */
 7public class NioClient {
 8
 9    private static ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
10
11    public static void main(String[] args) throws IOException {
12        SocketChannel socketChannel = SocketChannel.open();
13        socketChannel.configureBlocking(false);
14        boolean connect = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
15        // 因为连接是一个异步操作,所以需要在下面一行判断连接有没有完成。如果连接还没有完成,就进行后面的操作,会出现异常
16        if(!connect){
17            // 如果连接未完成,就等待连接完成
18            socketChannel.finishConnect();
19        }
20        // 每个3秒向服务端发送一条消息
21        executorService.scheduleAtFixedRate(() -> {
22            try {
23                String message = socketChannel.getLocalAddress().toString() + " Hello World";
24                // 使用ByteBuffer进行数据发送
25                ByteBuffer byteBuffer = ByteBuffer.wrap(message.getBytes());
26                socketChannel.write(byteBuffer);
27            } catch (IOException e) {
28                e.printStackTrace();
29            }
30
31        }, 0, 3, TimeUnit.SECONDS);
32    }
33}

2.1.1 Netty 高性能
在 IO 编程过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 IO 多路复用技术
进行处理。IO 多路复用技术通过把多个 IO 的阻塞复用到同一个 select 的阻塞上,从而使得系统在
单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O 多路复用的
最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程
的运行,降低了系统的维护工作量,节省了系统资源。与 Socket 类和 ServerSocket 类相对应,NIO 也提供了 SocketChannel 和 ServerSocketChannel两种不同的套接字通道实现。
2.1.1.1 多路复用通讯方式
Netty 架构按照 Reactor 模式设计和实现,它的服务端通信序列图如下:
在这里插入图片描述
客户端通信序列图如下:
在这里插入图片描述
Netty 的 IO 线程 NioEventLoop 由于聚合了多路复用器 Selector,可以同时并发处理成百上千个
客户端 Channel,由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于
频繁 IO 阻塞导致的线程挂起。

3.Netty主要特性

  • 优雅的设计
  • 统一的API接口,支持多种传输类型,例如OIO,NIO
  • 简单而强大的线程模型
  • 丰富的文档
  • 卓越的性能
  • 拥有比原生Java API 更高的性能与更低的延迟
  • 基于池化和复用技术,使资源消耗更低
  • 安全性
  • 完整的SSL/TLS以及StartTLS支持
  • 可用于受限环境,如Applet以及OSGI

3.1 Netty
Netty对NIO的API进行了封装,通过以下手段让性能又得到了一定程度的提升

  1. 使用多路复用技术,提高处理连接的并发性
  2. 零拷贝:
  3. Netty的接收和发送数据采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝
  4. Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象进行一次操作
  5. Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题
  6. 内存池:为了减少堆外直接内存的分配和回收产生的资源损耗问题,Netty提供了基于内存池的缓冲区重用机制
  7. 使用主从Reactor多线程模型,提高并发性
  8. 采用了串行无锁化设计,在IO线程内部进行串行操作,避免多线程竞争导致的性能下降
  9. 默认使用Protobuf的序列化框架
  10. 灵活的TCP参数配置

3.2 Callback
回调一般是在完成某个特定的操作后对相关方法进行调用。
Netty 在内部使用回调来处理事件;当一个回调被触发时,相关的事件可以被一个 interfaceChannelHandler 的实现处理,例如Channel激活时会调用ChannelActive方法,样例代码如下:

public class ConnectHandler extends ChannelInboundHandlerAdapter {
@Override
    public void channelActive(ChannelHandlerContext ctx)throws Exception {
        System.out.println("Client " + ctx.channel().remoteAddress() + connected");

    }
}

3.3Future
Future一般用在当执行异步操作时需要获取未来的某个时候才能获取到的结果。
Netty提供了它自己的实现——ChannelFuture,用于在执行异步操作的时候使用。
下面的例子中的connect()方法会直接返回,后续的成功或失败将由其注册的FutureListener来处理。

        try {
            // 使用异步的方式连接Server,不管成功失败,都是执行下面System.out的语句,最后的连接结果由FutureListener进行处理
            ChannelFuture future = bootstrap.connect();
            System.out.println("Finished connect operation");
            future.addListener((ChannelFutureListener) future1 -> {
                if (future1.isSuccess()){
                    ByteBuf buffer = Unpooled.copiedBuffer(
                            "Hello", Charset.defaultCharset());
                    ChannelFuture wf = future1.channel()
                            .writeAndFlush(buffer);
                    System.out.println("Connect successful!");
                }else{
                    System.out.println("Connect failed!");
                    Throwable cause = future1.cause();
                    cause.printStackTrace();
                }
            });
            System.out.println("Finished connect operation2");
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

最后的打印结果如下:

Finished connect operation
Finished connect operation2
Connect failed!
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: localhost/127.0.0.1:8888
    at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
    at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)
    at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:325)
    at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect
    ...............................................
    Caused by: java.net.ConnectException: Connection refused: no further information
    ... 11 more

3.4 Event
Netty 使用不同的事件来通知状态的改变或者是操作的状态。事件可能包括:

  • 连接已被激活或者连接失活
  • 数据读取;
  • 用户事件;
  • 错误事件。
  • 打开或者关闭到远程节点的连接;
  • 将数据写到或者冲刷到套接字。

每个事件都可以被分发给 ChannelHandler 类中的某个用户实现的方法。这是将事件驱动范式直接转换为应用程序逻辑处理比较理想的位置。
下图展示了事件是怎么被处理的:
在这里插入图片描述
4.Netty全过程图解
在这里插入图片描述

Channel: 数据传输流,与channel相关的概念有以下四个

在这里插入图片描述

  • Channel,表示一个连接,可以理解为每一个请求,就是一个Channel。

  • ChannelHandler,核心处理业务就在这里,用于处理业务请求。

  • ChannelHandlerContext,用于传输业务数据。

  • ChannelPipline,用于保存处理过程需要用到的ChannelHandler和ChannelHandlerContext。

  • ByteBuf
    ByteBuf是netty的Server与Client之间通信的数据传输载体(Netty的数据容器),它提供了一个byte数组(byte[])的抽象视图,既解决了JDK API的局限性,又为网络应用程序的开发者提供了更好的API。
    ByteBuf是一个存储字节的容器,最大特点就是使用方便,它既有自己的读索引和写索引,方便你对整段字节缓存进行读写,也支持get/set,方便你对其中每一个字节进行读写,它的数据结构如下图:
    在这里插入图片描述
    ByteBuF优点:
    容量可以按需增长
    读写模式切换不需要调用flip()
    读写使用了不同的索引
    支持方法的链式调用
    支持引用计数
    支持池化
    可以被用户自定义的缓冲区类型扩展
    通过内置的复合缓冲区类型实现透明的零拷贝

ByteBuF 工作机制: ByteBuf维护了两个不同的索引,一个用于读取,一个用于写入。readerIndex和writerIndex的初始值都是0,当从ByteBuf中读取数据时,它的readerIndex将会被递增(它不会超过writerIndex),当向ByteBuf写入数据时,它的writerIndex会递增。

  • 名称以readXXX或者writeXXX开头的ByteBuf方法,会推进对应的索引,而以setXXX或getXXX开头的操作不会。

  • 在读取之后,0~readerIndex的就被视为discard的,调用discardReadBytes方法,可以释放这部分空间,它的作用类似ByteBuffer的compact()方法。

  • readerIndex和writerIndex之间的数据是可读取的,等价于ByteBuffer的position和limit之间的数据。writerIndex和capacity之间的空间是可写的,等价于ByteBuffer的limit和capacity之间的可用空间。

  • ByteBuf提供了两个指针变量来支持顺序读写操作readerIndex用来支持读操作, writerIndex用来支持写操作。下图展示了ByteBuf是如何被两个索引分成三个区域的:
    在这里插入图片描述

他有三种使用模式:
Heap Buffer 堆缓冲区
堆缓冲区是ByteBuf最常用的模式,他将数据存储在堆空间。
Direct Buffer 直接缓冲区
直接缓冲区是ByteBuf的另外一种常用模式,他的内存分配都不发生在堆,jdk1.4引入的nio的ByteBuffer类允许jvm通过本地方法调用分配内存,这样做有两个好处
通过免去中间交换的内存拷贝, 提升IO处理速度; 直接缓冲区的内容可以驻留在垃圾回收扫描的堆区以外。
DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限制下, 使用 Heap 之外的内存, GC对此”无能为力”,也就意味着规避了在高负载下频繁的GC过程对应用线程的中断影响.
Composite Buffer 复合缓冲区
复合缓冲区相当于多个不同ByteBuf的视图,这是netty提供的,jdk不提供这样的功能。

Codec
Netty中的编码/解码器,通过他你能完成字节与pojo、pojo与pojo的相互转换,从而达到自定义协议的目的。
在Netty里面最有名的就是HttpRequestDecoder和HttpResponseEncoder了

认识Http请求
在这里插入图片描述
1.HTTP Request 第一部分是包含的头信息
2.HttpContent 里面包含的是数据,可以后续有多个 HttpContent 部分
3.LastHttpContent 标记是 HTTP request 的结束,同时可能包含头的尾部信息
4.完整的 HTTP request,由1,2,3组成

在这里插入图片描述
HTTP response 第一部分是包含的头信息
HttpContent 里面包含的是数据,可以后续有多个 HttpContent 部分
LastHttpContent 标记是 HTTP response 的结束,同时可能包含头的尾部信息
完整的 HTTP response,由1,2,3组成

从request的介绍我们可以看出,一次http请求并不是通过一次对话完成的,它中间可能有很多次的连接。
而Netty每一次对话都会建立一个channel,并且一个ChannelnboundHandler一般是不会同时去处理多个Channel的

springboot整合netty


注:常见IO
OIO,每个线程只能处理一个channel(同步的,该线程和该channel绑定);
BIO,同步阻塞IO,阻塞整个步骤,如果连接少,他的延迟是最低的,因为一个线程只处理一个连接,适用于少连接且延迟低的场景,比如说数据库连接。
NIO,同步非阻塞IO,每个线程可以处理多个channel(异步),线程发起IO请求,立即返回;内核在做好IO操作的准备之后,通过调用注册的回调函数通知线程做IO操作,线程开始阻塞,直到操作完成。阻塞业务处理但不阻塞数据接收,适用于高并发且处理简单的场景,比如聊天软件。(java NIO特性于JDK1.4中引入)
多路复用IO,他的两个步骤处理是分开的,也就是说,一个连接可能他的数据接收是线程a完成的,数据处理是线程b完成的,他比BIO能处理更多请求。
信号驱动IO,这种IO模型主要用在嵌入式开发,不参与讨论。
异步IO,他的数据请求和数据处理都是异步的,数据请求一次返回一次,适用于长连接的业务场景。
AIO,线程发起IO请求,立即返回;内存做好IO操作的准备之后,做IO操作,直到操作完成或者失败,通过调用注册的回调函数通知线程做IO操作完成或者失败。

参考链接:
https://blog.csdn.net/usagoole/article/details/88024517
https://www.jianshu.com/p/b9f3f6a16911
https://blog.csdn.net/lmdsoft/article/details/105618052

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值