网络编程-Netty-02 Netty核心功能及线程模型

文章目录

      • 1、Netty解决的痛点
      • 2、Netty的使用场景
      • 3、客户端、服务端demo
      • 4、Netty线程模型理解
        • 4.1Doug lea《Scalable IO in Java》中的可扩展的网络IO线程模型
        • 4.2 Netty的线程模型(网络版)
      • 5、Netty模块组件
        • 5.1 Bootstrap、ServerBootstrap
        • 5.2 NioEventLoopGroup
        • 5.2 NioEventLoop
        • 5.3 Channel
        • 5.4 ChannelHandler
        • 5.5 Future、ChannelFuture
      • 6、Netty服务端源码流程
      • 7、Netty实现聊天室功能

1、Netty解决的痛点

  • NIO的类库和API复杂,需要对Selector、ServerSocketChannel、SocketChannel、ByteBuffer等做操作。
  • 针对客户端断线重连、网络闪断、线条处理、半包读写、网络拥塞、异常等分别处理。
  • Netty对JDK自带的API做了封装,能够解决上述问题。
  • Netty拥有高性能、高吞吐、低时延等优点。

2、Netty的使用场景

  • 分布式系统中,各节点间远程服务调用需要高性能的RPC框架。Netty作为优秀的异步高性能通信框架,往往作为基础通信组件。
    • 阿里分布式框架Dobbo进行节点通信,使用Netty进行进程间通信。
    • RocketMQ使用Netty作为基础通信组件
    • 华为分布式服务框架servicecomb的Edge Service的是基于Netty的vertx作为基础通信组件
  • 游戏行业,通过Netty作为高性能基础通信组件,提供TCP/UDP和http协议栈
  • 大数据领域,hadoop高性能通信和序列化Avro的RPC框架,默认采用Netty进行跨界点通信。

3、客户端、服务端demo

先跑demo后,了解基本功能,后看源码。

    
    <dependency>
      <groupId>io.nettygroupId>
      <artifactId>netty-allartifactId>
      <version>4.1.73.Finalversion>
    dependency>

客户端:

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        //客户端事件循环组 默认线程数为 cpu-core*2
        NioEventLoopGroup group = new NioEventLoopGroup(1);
        try {
            Bootstrap bootstrap = new Bootstrap();
            //设置bootstrap参数
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            System.out.println("netty client start...");
            ChannelFuture cf = bootstrap.connect("127.0.0.1", 9090);
            //启动客户端连接服务器
            cf.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully();
        }
    }
}

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    /**
     * 建连完成
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ByteBuf buf = Unpooled.copiedBuffer("HelloServer".getBytes(CharsetUtil.UTF_8));
        ctx.writeAndFlush(buf);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("收到服务端的消息:" + buf.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("数据流读取完毕");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("捕获异常"+cause);
    }
}

服务端:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        //创建管理连接的线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(2);
        //创建处理业务逻辑的线程组
        EventLoopGroup workerGroup = new NioEventLoopGroup(10);
        try {
            //创建服务器端的启动对象
            ServerBootstrap bootstrap = new ServerBootstrap();
            //配置启动参数(两个线程组)
            bootstrap.group(bossGroup,workerGroup)
                    //服务器通道的实现
                    .channel(NioServerSocketChannel.class)
                    //将瞬时无法处理的请求加入队列
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("netty server start...");
            //启动服务器(绑定端口号) bind是异步操作 sync是等待异步绑定结果的同步操作
            ChannelFuture cf = bootstrap.bind(9090).sync();
            //等待服务器监听端口关闭,closeFuture是异步操作
            //通过sync同步等待通道关闭处理完毕,这里会阻塞等待通道关闭完成,内部调用的是Object.wait()方法
            cf.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 当客户端与服务端完成连接时出发
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端连接通道建立完成");
    }

    /**
     * 读取客户端发送的数据
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("收到客户端的消息:" + buf.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("数据读取完毕");
        ByteBuf buf = Unpooled.copiedBuffer("HelloClient".getBytes(CharsetUtil.UTF_8));
        ctx.writeAndFlush(buf);
    }

    /**
     * 异常捕获
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("捕获异常"+cause);
    }
}

4、Netty线程模型理解

4.1Doug lea《Scalable IO in Java》中的可扩展的网络IO线程模型
  • Classic Service Designs 经典服务设计

    特点

    • 单线程处理请求
    • 每个请求经过读消息 —> 解码 —> 处理请求 —> 编码 —> 响应客户端数据
    • 阻塞式响应

    网络编程-Netty-02 Netty核心功能及线程模型_第1张图片

  • Basic Reactor Design 响应式事件驱动模型

    • 特点
    • 异步非阻塞响应(linux系统下基于epoll文件描述器中断响应)
    • 由acceptor对客户端连接进行响应
    • 再由dispatch对读写事件进行处理

    网络编程-Netty-02 Netty核心功能及线程模型_第2张图片

  • Using Multiple Reactors 多线程主从响应式事件驱动模型

    • 特点
    • mainReactor主负责连接
    • 连接后生成的channel注册到subReactor,再由subReactor分配worker threads线程,对编解码和处理
      网络编程-Netty-02 Netty核心功能及线程模型_第3张图片
4.2 Netty的线程模型(网络版)

网传的netty线程模型非常形象,易于理解从客户端到服务端的线程执行过程,及服务端两组线程池锁起到的作用。

网络编程-Netty-02 Netty核心功能及线程模型_第4张图片

5、Netty模块组件

5.1 Bootstrap、ServerBootstrap
  • Bootstrap 客户端启动引导类,集成其他模块

  • ServerBootstrap 服务端启动引导类,集成其他模块功能

    以服务端的引导类为例,集成如一下其他模块

    ServerBootstrap bootstrap = new ServerBootstrap();
     bootstrap.group(bossGroup,workerGroup)
              .channel(NioServerSocketChannel.class)//集成通道类
              .option(ChannelOption.SO_BACKLOG,1024)//阻塞队列长度
              .childHandler(new ChannelInitializer<SocketChannel>() {//pipeline管道
                  @Override
                  protected void initChannel(SocketChannel ch) throws Exception {
                      ChannelPipeline pipeline = ch.pipeline();
                      pipeline.addLast("decoder",new StringDecoder());
                      pipeline.addLast("encoder",new StringEncoder());
                      pipeline.addLast(new NettyChatServerHandler());
                  }
               });
    
5.2 NioEventLoopGroup
  • NioEventLoopGroup 用于管理NioEventLoop生命周期的服务组,相当于线程池。

    //当不定义线程池中线程数量
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                    "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));//获取机器中核心数量*2
    nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads//指定线程池中线程数量,或默认使用double*coreSize
    children = new EventExecutor[nThreads];//创建线程池
    new ThreadPerTaskExecutor(newDefaultThreadFactory());//线程工厂
    
    • BossGroup 负责管理客户端连接的事件的线程组,监听accept()事件。线程组的每个对象是NioEventLoop
    • WorkerGroup负责管理注册到本线程channel事件的线程组,线程组的每个对象是NioEventLoop
5.2 NioEventLoop
  • 绑定线程,支持异步提交任务,线程启动时,执行NioEventLoop 的 run方法

    • IO任务,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由processSelectedKeys 方法触发。
    • 非IO任务,添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发。
  • 任务队列

  • Selector

    多路复用器,一个Selector可以监听多个连接的Channel事件。

    selectionKey.OP_READ/OP_WRITE事件

5.3 Channel

Netty的网络通讯组件,能够执行网络IO操作,为用户提供:

  • 网络连接通道的状态,已打开、已连接等
  • 网络连接的配置参数(接收缓冲区大小)
  • 提供异步网络IO操作,建立连接、读写、端口绑定等。当使用异步调用将立即返回ChannelFuture,不关心结果。通过注册监听器到 ChannelFuture 上,可以在I/O 操作成功、失败或取消时回调通知调用方
  • 支持关联IO操作对应的处理程序

NioSocketChannel,异步的客户端 TCP Socket 连接。

2 NioServerSocketChannel,异步的服务器端 TCP Socket 连接。

3 NioDatagramChannel,异步的 UDP 连接。

4 NioSctpChannel,异步的客户端 Sctp 连接。

5 NioSctpServerChannel,异步的 Sctp 服务器端连接。

6 这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO

5.4 ChannelHandler

ChannelHandler是接口,处理IO事件或拦截IO操作,并将其转发到ChannelPipline,进行业务处理链中执行下一处理程序。

ChannelPipline中存在2种Handler,Inbound、Outbound分别处理入方向和出方向的任务。

实现方法可见于:

//普通抽象类处理器
ChannelInboundHandler //用于处理入站 I/O 事件。 
ChannelOutboundHandler //用于处理出站 I/O 操作。
//适配器类
ChannelInboundHandlerAdapter  //用于处理入站 I/O 事件。 
ChannelOutboundHandlerAdapter //用于处理出站 I/O 操作。
  • ChannelHandlerContext
  • ChannelPipline
5.5 Future、ChannelFuture
  • 异步任务的执行结果是直接返回的,可以通过监听事件得到实际执行结果
  • 同步调用(.sync()方法)可以拿到结果

6、Netty服务端源码流程

网络编程-Netty-02 Netty核心功能及线程模型_第5张图片

7、Netty实现聊天室功能

  • 客户端

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.Scanner;

public class NettyChatClient {
    public static void main(String[] args) throws InterruptedException {
        //客户端事件循环组 默认线程数为 cpu-core*2
        NioEventLoopGroup group = new NioEventLoopGroup(1);
        try {
            Bootstrap bootstrap = new Bootstrap();
            //设置bootstrap参数
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new NettyChatClientHandler());
                        }
                    });
            ChannelFuture cf = bootstrap.connect("127.0.0.1", 9090).sync();
            Channel channel = cf.channel();
            System.out.println("=========="+ channel.localAddress()+"==========");
            //客户端输入内容
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()){
                String msg = scanner.nextLine();
                //通过channel发送到服务器
                channel.writeAndFlush(msg);
            }
            //启动客户端连接服务器
            cf.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully();
        }
    }
}
//业务逻辑之客户端pipeline管道

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class NettyChatClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(msg.trim());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("捕获异常"+cause);
    }
}

  • 服务端
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class NettyChatServer {
    public static void main(String[] args) throws InterruptedException {
        //设置2组线程,分别用于接收请求和处理业务
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(2);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(8);
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    //阻塞队列
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("decoder",new StringDecoder());
                            pipeline.addLast("encoder",new StringEncoder());
                            pipeline.addLast(new NettyChatServerHandler());
                        }
                    });
            System.out.println("聊天室已开启...");
            //启动服务器(绑定端口号) bind是异步操作 sync是等待异步绑定结果的同步操作
            ChannelFuture cf = bootstrap.bind(9090).sync();
            //等待服务器监听端口关闭,closeFuture是异步操作
            //通过sync同步等待通道关闭处理完毕,这里会阻塞等待通道关闭完成,内部调用的是Object.wait()方法
            cf.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

//业务逻辑之服务端pipeline管道
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

public class NettyChatServerHandler extends SimpleChannelInboundHandler<String> {
    private List<Channel> channelList;//netty会创建多例NettyChatServerHandler对象 ---> 需要调整为单利

    //GlobalEventExecutor.INSTANCE 是全局的事件执行器,是一个单例
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    //时间戳
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public NettyChatServerHandler(){
        this.channelList = new ArrayList<>();
    }
    /**
     * 当客户端与服务端完成连接时出发
     *
     * @param ctx
     * @throws Exception
     */

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.writeAndFlush("[客户端]"+channel.remoteAddress() + "上线了"+ sdf.format(new java.util.Date()) + "\n");
        channelGroup.add(channel);
        System.out.println(ctx.pipeline().channel().remoteAddress() + "上线了");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.forEach(ch -> {
            if (channel != ch){
                ch.writeAndFlush("[客户端] "+channel.remoteAddress()+ "发送了消息:"+msg);
            }else {
                ch.writeAndFlush("[自己] 发送了消息:"+msg);
            }
        });
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        channelGroup.writeAndFlush("[客户端] "+ ctx.channel().remoteAddress() + "退出群聊");
    }

    /**
     * 异常捕获
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("捕获异常"+cause);
    }
}

附件:
http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf [doug lea]

你可能感兴趣的:(网络编程,网络,rpc,java)