【Netty框架理解】

Netty

Redis,Zookeper,Netty,游戏服务器等其实底层就是I/O通讯程序(C/S架构)
Client与Server之间进行IO通讯。

1、netty的优势、底层逻辑

Netty是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。Netty是基于NIO的,它封装了jdk的NIO,让我们使用起来更加方法灵活。

Netty的特点:

高并发: Netty 是一款基于NIO 开发的网络通 信框架,对比于BIO ,他的并发性能得到了很大提高。
传输快: Netty 的传输依赖于零拷贝特性,尽量减少不必要的内存拷贝,实现了 更高效率的传输。
封装好: Netty 封装了 NIO 操作的很多细节,提供了易于使用调用接口。

Netty的优势:

对NIO进行了大量的优化和封装,使用起来更加方便和高效,同时可扩展性很强,将NIO的连接建立、数据接收都封装到框架里面,以及使用NIO编程的一些异常处理

使用简单:封装了 NIO 的很多细节,使用更简单。
功能强大:预置了多种编解码功能,支持多种主流协议。
定制能力强:可以通过 ChannelHandler 对通信框架进行灵活地扩展。
性能高:通过与其他业界主流的 NIO 框架对比, Netty 的综合性能优。
稳定: Netty 修复了已经发现的所有 NIO 的 bug ,让开发人员可以专注于业务 本身。
社区活跃: Netty 是活跃的开源项目,版本迭代周期短,bug 修复速度快。
Netty 的应用场景
阿里分布式服务框架 Dubbo ,默认使用 Netty 作为基础通信组件,还有 RocketMQ 也是使用 Netty 作为通讯的基础。

Netty的底层逻辑:

Netty底层主要是基于JAVA NIO的非阻塞事件驱动模型以及多路复用器实现的。
在底层,Netty通过以下几个核心组件实现了高性能的网络通信:
Channel(通道):Channel是Netty的核心抽象,它表示一个开放的连接,可以执行读取、写入和关闭等操作。Netty中的Channel提供了异步的I/O操作和事件通知机制。
EventLoop(事件循环):EventLoop是Netty中的事件处理机制,它负责处理和分发事件,以及执行对应的I/O操作。每个Channel都关联了一个EventLoop,它负责处理该Channel上的所有事件。
ChannelPipeline(通道管道):ChannelPipeline是Netty中的处理器链,它由一系列的处理器组成,用于处理、转换或拦截事件和数据。每个Channel都有自己的ChannelPipeline,事件在Pipeline中依次经过处理器进行处理。
ChannelHandler(通道处理器):ChannelHandler是Netty中的处理器,用于执行实际的业务逻辑。它可以处理事件、读取和写入数据,以及修改ChannelPipeline。
ByteBuf(字节缓冲区):ByteBuf是Netty中的字节容器,它提供了高效的字节操作方法,用于读取和写入数据。

Netty的底层实现利用了Java NIO的非阻塞I/O模型,通过Selector轮询事件,将I/O操作异步化并交给EventLoop处理。这种事件驱动的模型使得Netty能够处理大量并发连接,同时提供低延迟和高吞吐量的网络通信

2、netty线程模型

对于服务器端而言有两个线程组,Boss线程组和Worker线程组。其中Boss线程组一般只开启一条线程(除非一个Netty服务同时监听多个端口),Worker线程数默认是CPU核数的两倍。Boss线程主要监听SocketChannel的OP_ACCEPT事件和客户端的连接。

当Boss线程监听到有SocketChannel连接接入时,会把SocketChannel包装成NioSocketChannel,并注册到Worker线程的Selector中,同时监听其OP_WRITE和OP_READ事件。
当Worker线程监听到某个SocketChannel有就绪的读IO事件时,就会进行以下操作:
1、向内存池中分配内存,读取IO数据流
2、将读取后的ByteBuf传递给解码器Handler进行解码,若能解码出完整的请求数据包,就会把请求数据包交给业务逻辑处理Handler
3、经过业务逻辑处理Handler后,在返回响应结果前,交给编码器进行数据加工
4、最终写到缓冲区,并由IO Worker线程将缓冲区的数据输出到网络中并传输给客户端

3、netty实现一个小服务:客户端发消息,服务端可以返回对话

Netty 客户端

package com.example.demo.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;



public class NettyClient {

    static final String HOST = "localhost";
    static final int PORT = 8000;

    public static void main(String[] args) throws InterruptedException {
        //客户端启动
        Bootstrap bootstrap = new Bootstrap();

        NioEventLoopGroup group = new NioEventLoopGroup();
        bootstrap
                // 指定线程模型
                .group(group)
                // 指定IO类型为NIO
                .channel(NioSocketChannel.class)
                // 连接的超时时间,如果超过这个时间,仍未连接到服务端,则表示连接失败
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                // IO处理逻辑
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) {
                        ch.pipeline().addLast(new NettyClientHandler());
                    }
                });
        //3、与服务器建立连接
        bootstrap.connect(HOST,PORT);
    }

}

Netty 服务端

package com.example.demo.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;


public class NettyServer {

    public static void main(String[] args) {
        // 1、引导服务端的启动
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // 用于监听端口,接收新连接的线程组
        NioEventLoopGroup boss = new NioEventLoopGroup();
        // 表示处理每一个连接的数据读写的线程组
        NioEventLoopGroup worker = new NioEventLoopGroup();
        //2、初始化
        serverBootstrap.group(boss, worker)
                // 指定IO模型为NIO
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                // 定义后面每一个连接的数据读写
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) {
                        ch.pipeline().addLast(new NettyServerHandler());
                    }
                });
        //3、绑定端口
        serverBootstrap.bind(8000);

    }

}

自定义ClientHandler

package com.example.demo.netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.nio.charset.StandardCharsets;
import java.util.Date;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    /**
     * 当客户端连接服务器完成就会触发该方法
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx){
        System.out.println(new Date() + ": 客户端发送数据给服务端");
        ctx.channel().writeAndFlush(getByteBuf(ctx));
    }

    /**
     * 读取服务端发送的数据
     * @param ctx 上下文对象,含有通道管道
     * @param msg 客户端发送的数据
     * @throws Exception
     */
    @Override
    // 不管服务端还是客户端,收到数据后都会调用channelRead()方法
    public void channelRead(ChannelHandlerContext ctx, Object msg)  {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(new Date() + ": 客户端收到服务端的数据 -> " + byteBuf.toString(StandardCharsets.UTF_8));
    }


    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        ByteBuf buffer = ctx.alloc().buffer();
        byte[] bytes = "hello world".getBytes(StandardCharsets.UTF_8);
        buffer.writeBytes(bytes);
        return buffer;
    }
}

自定义ServerHandler

package com.example.demo.netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * 自定义Handler需要继承netty规定好的某个HandlerAdapter(规范)
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    /**
     * 当客户端连接服务器完成就会触发该方法
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx){
        System.out.println("服务端与客户端连接通道建立完成");
    }

    /**
     * 读取客户端发送的数据
     * @param ctx 上下文对象,含有通道管道
     * @param msg 客户端发送的数据
     * @throws Exception
     */
    @Override
    // 不管服务端还是客户端,收到数据后都会调用channelRead()方法
    public void channelRead(ChannelHandlerContext ctx, Object msg)  {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(new Date() + ": 服务端收到客户端数据 -> " + byteBuf.toString(StandardCharsets.UTF_8));
        // 服务端将读到的数据返回客户端
        System.out.println(new Date() + ": 服务端发送数据给客户端");
        ctx.channel().writeAndFlush(getByteBuf(ctx));
    }


    /**
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx){

    }

    //写数据
    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        byte[] bytes = "hello world from server!".getBytes(StandardCharsets.UTF_8);
        ByteBuf buffer = ctx.alloc().buffer();
        buffer.writeBytes(bytes);
        System.out.println(buffer.toString(StandardCharsets.UTF_8));
        return buffer;
    }
}

NIO、BIO、AIO

● BIO 就是传统的IO,同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理
● NIO 是同步非阻塞的,服务器端用一个线程处理多个连接(多个客户端),客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到连接有IO请求就进行处理,适合大量并发请求的场景
● AIO 就是NIO的升级版,通过操作系统的回调通知去让线程处理事件,异步非阻塞,适合超大并发请求的场景
● IO多路复用是一种同步IO模型(NIO),允许单线程去同时监听多个文件描述符,一旦文件描述符就绪就会通知程序去处理
常见的有select、poll、epoll
○ select
■ 基于数组实现,每次调用都进行遍历,最大连接数有上限
○ poll
■ 通过链表实现,每次调用都进行遍历,最大连接数无上限
○ epoll
■ 通过哈希表实现,通过事件通知,每当有IO事件就绪,系统注册的回调函数就会被调用,最大连接数无上限

BIO (阻塞IO)

● BIO 就是传统的IO,同步阻塞,服务器实现模式为(一个客户端)一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理
在这里插入图片描述

NIO (非阻塞IO)

● NIO 是同步非阻塞的,服务器端用一个线程处理多个连接(多个客户端),客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到连接有IO请求就进行处理,适合大量并发请求的场景
在这里插入图片描述
简单NIO
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
缺点:
1、每次建立连接都会遍历整个channelList
2、非阻塞的IO,程序会空转,占用CPU资源

NIO多路复用(selector 多路复用器 阻塞 释放cpu资源)

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值