Netty 学习笔记

Netty

概述

特性:事件驱动、通用 API、0 拷贝

  • NIO 缺点:
    • NIO 的类库和 API 繁杂,使用麻烦。你需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、 ByteBuffer 等.
    • 可靠性不强,开发工作量和难度都非常大
    • NIO 的 Bug。例如 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%
  • Netty 优点:
    • 对各种传输协议提供统一的 API
    • 高度可定制的线程模型——单线程、一个或多个线程池
    • 更好的吞吐量,更低的等待延迟
    • 更少的资源消耗
    • 最小化不必要的内存拷贝

线程模型

单线程模型

image-20201011160630723

线程池模型

image-20201011160752500

Netty 模型

image-20201011161637462

Netty 抽象出两组线程池

  • BossGroup:负责接收客户端连接
  • WorkerGroup:负责网络读写操作

NioEventLoop 表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个 selector, 用于监听绑定在其上的 socket 网络通道。NioEventLoop 内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由 IO 线程 NioEventLoop 负责。

Netty 核心组件

ChannelHandler 及其实现类

ChannelHandler 接口定义了许多事件处理的方法,我们可以通过重写这些方法去实现具 体的业务逻辑。

继承 ChannelInboundHandlerAdapter,重写以下方法

  • channelActive(ChannelHandlerContext ctx):通道就绪事件
  • channelRead(ChannelHandlerContext ctx, Object msg):通道读取数据事件
  • channelReadComplete(ChannelHandlerContext ctx):数据读取完毕事件
  • exceptionCaught(ChannelHandlerContext ctx, Throwable cause):通道发生异常事件

ChannelPipeline

一个 Handler 的集合,它负责处理和拦截 inbound 或者 outbound 的事件和操作,相当于一个贯穿 Netty 的链。

image-20201011162605115

  • addFirst(ChannelHandler… handlers):把一个业务处理类(handler)添加到链中的第一 个位置
  • addLast(ChannelHandler… handlers):把一个业务处理类(handler)添加到链中的最后一个位置

ChannelHandlerContext

事件处理器上下文对象,Pipeline 链中的实际处理节点 。每个处理节点 ChannelHandlerContext 中包含一个具体的事件处理器 ChannelHandler,同时 ChannelHandlerContext 中也绑定了对应的 pipeline 和 Channel 的信息,方便对 ChannelHandler 进行调用。

  • ChannelFuture close():关闭通道
  • ChannelOutboundInvoker flush():刷新
  • ChannelFuture writeAndFlush(Object msg):将数据写到 ChannelPipeline 中当前 ChannelHandler 的下一个 ChannelHandler 开始处理(出站)

ChannelFuture

表示 Channel 中异步 I/O 操作的结果,在 Netty 中所有的 I/O 操作都是异步的,I/O 的调用会直接返回,调用者并不能立刻获得结果,但是可以通过 ChannelFuture 来获取 I/O 操作 的处理状态。

  • Channel channel():返回当前正在进行 IO 操作的通道
  • ChannelFuture sync():等待异步操作执行完毕

EventLoopGroup 和其实现类 NioEventLoopGroup

EventLoopGroup 是一组 EventLoop 的抽象。Netty 为了更好的利用多核 CPU 资源,一般会有多个 EventLoop 同时工作,每个 EventLoop 维护着一个 Selector 实例。

EventLoopGroup 提供 next 接口,可以从组里面按照一定规则获取其中一个 EventLoop 来处理任务。

在 Netty 服务器端编程中,我们一般都需要提供两个 EventLoopGroup。例如:BossEventLoopGroup 和 WorkerEventLoopGroup。

  • NioEventLoopGroup():构造方法
  • Future<?> shutdownGracefully():断开连接,关闭线程

ServerBootstrap 和 Bootstrap

ServerBootstrap 是 Netty 中的服务器端启动助手,通过它可以完成服务器端的各种配置。

Bootstrap 是 Netty 中的客户端启动助手,通过它可以完成客户端的各种配置。

  • ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup):该方法用于服务器端,用来设置两个 EventLoop
  • B group(EventLoopGroup group):该方法用于客户端,用来设置一个 EventLoop
  • B channel(Class<? extends C> channelClass):该方法用来设置一个服务器端的通道实现
  • <T> B option(ChannelOption<T> option, T value):用来给 ServerChannel 添加配置
  • <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value):用来给接收到的通道添加配置
  • ServerBootstrap childHandler(ChannelHandler childHandler):该方法用来设置业务处理类(自定义的 handler)
  • ChannelFuture bind(int inetPort): 该方法用于服务器端,用来设置占用的端口号
  • ChannelFuture connect(String inetHost, int inetPort):该方法用于客户端,用来连接服务器端

代码案例

Maven:

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

NettyServer:

public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        // 1.两个线程池,默认线程数为 CPU 核心数乘 2
        //   bossGroup:接收客户端传过来的请求
        //   workerGroup:处理读写请求
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        // 2.创建服务启动辅助类: 组装一些必要的组件
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // 设置组
        serverBootstrap.group(bossGroup, workerGroup)
                // channel 方法指定服务器监听的通道类型
                .channel(NioServerSocketChannel.class)
                // 设置 channel handler,每一个客户端连接后,给定一个监听器进行处理
                .childHandler(new MyChannelInitializer());

        // bind 监听端口
        ChannelFuture f = serverBootstrap.bind(8000).sync();
        System.out.println("tcp server start success..");
        // 会阻塞等待直到服务器的 channel 关闭
        f.channel().closeFuture().sync();
    }

    static class MyChannelInitializer extends ChannelInitializer<NioSocketChannel> {
        @Override
        protected void initChannel(NioSocketChannel ch) throws Exception {
            // 传输通道
            ChannelPipeline pipeline = ch.pipeline();
            // 在通道上添加对通道的处理器,该处理器可能还是一个监听器
            pipeline.addLast(new StringEncoder())
                    .addLast(new StringDecoder())
                    // 监听器队列上添加我们自己的处理方式..
                    .addLast(new SimpleChannelInboundHandler<String>() {
                        @Override
                        protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) {
                            System.out.println(s);
                        }
                    });
        }
    }
}
  1. bossGroup 和 workerGroup 是两个线程池,它们默认线程数为 CPU 核心数乘以 2,bossGroup 用于接收客户端传过来的请求,接收到请求后将后续操作交由 workerGroup 处理。

  2. ServerBootstrap#channel 方法:用于指定服务器端监听套接字通道 NioServerSocketChannel,其内部管理了一个 Java NIO 中的ServerSocketChannel 实例。

  3. ServerBootstrap#channelHandler 方法:用于设置业务职责链,责任链是我们下面要编写的,责任链具体是什么,它其实就是由一个个的 ChannelHandler 串联而成,形成的链式结构。正是这一个个的 ChannelHandler 帮我们完成了要处理的事情。

    ChannelInitializer 继承 ChannelInboundHandlerAdapter,用于初始化 Channel 的 ChannelPipeline。通过 initChannel 方法参数 ch 得到 ChannelPipeline 的一个实例。

  4. Bootstrap#bind 方法:将服务绑定到 8080 端口上,bind 方法内部会执行端口绑定等一系列操作,使得前面的配置生效。sync 方法用于阻塞当前 Thread,一直到端口绑定操作完成。

  5. 最后是应用程序将会阻塞等待直到服务器的 Channel 关闭。

NettyClient:

public class NettyClient { 
    public static void main(String[] args) throws InterruptedException { 
        // 客户端的启动辅助类
		Bootstrap bootstrap = new Bootstrap();
        // 线程池的实例 
        NioEventLoopGroup group = new NioEventLoopGroup(); 
        // 添加到组中 
        bootstrap.group(group) 
            // channel 方法指定通道类型 
            .channel(NioSocketChannel.class) 
            // 通道初始化了 
            .handler(new ChannelInitializer<Channel>() { 
                @Override 
                protected void initChannel(Channel ch) { 
                    ch.pipeline().addLast(new StringEncoder());
				} 
            });
        Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();

        while (true) { 
            channel.writeAndFlush(new Date() + ": hello world!"); 
            Thread.sleep(2000);
		} 
    } 
}

基于 Netty 自定义 RPC

步骤

  1. 创建一个公共的接口项目以及创建接口及方法,用于消费者和提供者之间的约定。
  2. 创建一个提供者,该类需要监听消费者的请求,并按照约定返回数据。
  3. 创建一个消费者,该类需要透明的调用自己不存在的方法,内部需要使用 Netty 请求提供者返回数据

公共接口

public interface UserService { 
    String sayHello(String word);
}

提供者

UserServiceImpl:

public class UserServiceImpl implements UserService {
	@Override 
    public String sayHello(String word) { 
        System.out.println("调用成功--参数:" + word); 
        return "调用成功--参数:" + word;
	}

    public static void startServer(String hostName, int port) { 
        try { 
            NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(); 
            ServerBootstrap bootstrap = new ServerBootstrap();

            bootstrap.group(eventLoopGroup)
                .channel(NioServerSocketChannel.class) 
                .childHandler(new ChannelInitializer<SocketChannel>() { 
                    @Override 
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline p = ch.pipeline();
                        p.addLast(new StringDecoder());
                        p.addLast(new StringEncoder());
                        p.addLast(new UserServerHandler());
                    } 
                }); 
            bootstrap.bind(hostName, port).sync();
        } catch (InterruptedException e) { 
            e.printStackTrace();
        } 
    }
    
    public static void main(String[] args) {
        UserServiceImpl.startServer("localhost", 8990);
	}
}

UserServerHandler:

public class UserServerHandler extends ChannelInboundHandlerAdapter {
	@Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 如果符合约定,则调用本地方法,返回数据 
        if (msg.toString().startsWith("UserService")) {
            String result = new UserServiceImpl()
                .sayHello(msg.toString().substring(msg.toString().lastIndexOf("#") + 1));
            ctx.writeAndFlush(result);
        }
    }
}

消费者

客户端调用代理方法,返回一个实现了 HelloService 接口的代理对象,调用代理对象的方法,返回结果。

RpcConsumer:代理相关的类

public class RpcConsumer {
    private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    
    private static UserClientHandler client;

    /**
     * 创建一个代理对象
     */
    public Object createProxy(final Class<?> serviceClass, final String providerName) {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[]{serviceClass}, (proxy, method, args) -> {
                    if (client == null) {
                        initClient();
					}
                    // 设置参数
                    client.setPara(providerName + args[0]);
                    return executor.submit(client).get();
				}); 
    }

    /** 
     * 初始化客户端
     */
    private static void initClient() {
        client = new UserClientHandler();
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group)
            .channel(NioSocketChannel.class)
            .option(ChannelOption.TCP_NODELAY, true)
            .handler(new ChannelInitializer<SocketChannel>() {
                @Override 
                public void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline p = ch.pipeline();
                    p.addLast(new StringDecoder());
                    p.addLast(new StringEncoder());
                    p.addLast(client);
				}
            });

        try {
            b.connect("localhost", 8990).sync();
		} catch (InterruptedException e) {
            e.printStackTrace();
		}
    }
    
}

UserClientHandler:

public class UserClientHandler extends ChannelInboundHandlerAdapter implements Callable {

    // 事件处理器上下文(存储 handler 信息,写操作)
    private ChannelHandlerContext context;
    private String result;
    private String para;

    // 客户端和服务器连接时,该方法就自定执行
    @Override 
    public void channelActive(ChannelHandlerContext ctx) { 
        context = ctx;
	}

    /** 
     * 收到服务端数据,唤醒等待线程 
     */
	@Override 
    public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) { 
        result = msg.toString(); 
        notify();
	}

    /** 
     * 写出数据,开始等待唤醒 
     */
	@Override public synchronized Object call() throws InterruptedException { 	
        context.writeAndFlush(para); 
        wait(); 
        return result;
	}

    public void setPara(String para) { 
        this.para = para;
	} 
}

ClientBootstrap:

public class ClientBootstrap { 
    public static final String providerName = "UserService#sayHello#";
    public static void main(String[] args) throws InterruptedException {
		// 创建一个代理对象 
        UserService service = (UserService) consumer.createProxy(UserService.class, providerName); 
        for (;;) { 
            Thread.sleep(1000); 
            System.out.println(service.sayHello("are you ok ?"));
		} 
    } 
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值