一、应用场景

目前,部分的硬件厂商对接硬件要求需要基于TCP协议发送报文的形式,以及可能部分系统做集成时都需要基于TCP协议,建立Socket通道,发送实时消息,相信很多接触过Java的,都使用过Socket编程,包括BIO/NIO/AIO,但是这些编程起来较为复杂,Netty很好的帮我们简化了这些配置的流程,让我们更方便的使用。

二、Netty的使用

我们先不谈原理,先来看看Netty是如何使用的。

jar包:

<dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>5.0.0.Alpha2</version>
    </dependency>

Socket需要一个服务端和客户端

2.1 服务端

服务端的创建

public static void main(String[] args) throws Exception {
    // 创建两个线程组bossGroup和workerGroup,含有的子线程NioEventLoop的个数默认为cpu核数的两倍
    // bossGroup只是处理连接请求,真正的和客户端业务处理,会交给workerGroup完成
    // 这里面的int值参数代表了要创建的线程数量
    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    EventLoopGroup workerGroup = new NioEventLoopGroup(8);
    try {
        // 创建服务器端的启动对象
        ServerBootstrap bootstrap = new ServerBootstrap();
        //使用链式编程来配置参数
        bootstrap.group(bossGroup,workerGroup) //设置两个线程组
            //使用NioServerSocketChannel做为服务器的通道实现
            .channel(NioServerSocketChannel.class)
            // 初始化服务器连接队列大小,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。
            // 多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
            .option(ChannelOption.SO_BACKLOG,1024)
            //创建通道初始化对象,设置初始化参数
            .childHandler(new ChannelInitializer<SocketChannel>(){
                
                @Override
                protected void initChannel(SocketChannel ch)throws Exception {
                    //对workerGroup的SocketChannel设置处理器
                    ch.pipeline().addLast(new NettyServerHandler());
                }
            });
        sout("netty server start...")
            // 绑定一个端口并且同步,生成一个ChannelFuture异步对象,通过isDone()等方法可以判断异步事件的执行情况
            // 启动服务器(并绑定端口),bind是异步操作,sync方法是异步操作完毕
            ChannelFuture cf = bootstrap.bind(9000).sync();
        //给
    }
}

服务端收发消息的处理类 

这里注意这个ChannelHandlerContext参数,如果要发送消息 ,需要通过这个参数,如果外部的方法中需要发送消息,可以将这个参数赋给一个全局的变量。

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    // 客户端连接
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
    }

    // 客户端发送信息
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        super.channelRead(ctx, msg);
    }

    // 数据读取完毕时的处理方法
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ByteBuf buf = Unpooled.copiedBuffer("HelloClient".getBytes(CharsetUtil.UTF_8));
        ctx.writeAndFlush(buf);
    }
}

2.2 客户端

客户端的创建

public static void main(String[] args)throws Exception {
// 客户端需要一个事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try {
// 创建客户端启动对象
// 注意客户端使用的对象不是ServerBootstra而是Bootstrap
Bootstrap bootstrap = new Bootstrap();
// 设置相关参数
bootstrap.group(group)// 设置线程组
    .channel(NioSocketChannel.class)
    .handler(new ChannelInitializer<SocketChannel>() {
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            // 加入处理器事件
            socketChannel.pipeline().addLast(new NettyClientHandler());
        }
    });
    sout("netty client strat..");
        //启动客户端去连接服务器端
        ChannelFuture cf = bootstrap.connect("127.0.0.1",9000).sync();
    //对通道关闭进行监听
    cf.channel().closeFuture.sync();
} finally  {
    group.shutdownGracefully();
}
}

客户端处理类

public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx){
        ByteBug buf = Unpooled.copiedBuffer("HelloServer".getBytes(CharsetUtil.UTF_8));
        ctx.writeAndFlush(buf);
    }
    
    @Override
    public void  channelRead(ChannelHandlerContext ctx,Object msg){
        ByteBuf buf = (ByteBuf)msg;
        sout("收到服务端发送的消息" + bug.toString(CHarsetUtil.UTF_8);
    }
}

基本的使用就是这样了,下面我们来看一下Netty的底层

三、Netty底层

3.1 通信模型

我们先来看一张Netty底层的通信模型,这张图是从网上找到的,画的很不错

netty分布式架构 netty集群部署_tcp/ip

 这里有一个多路复用器Selector的概念

如果想详细了解这个概念,这里有一篇博客专门讲这个:多路复用器Selector

这里大概讲一下Selector的作用,在Netty中,它主要起到了监听的作用,用来监听的一个IO操作有没有发生数据的交互,如果发生了,它可以判断这个操作是读操作,还是连接操作,如果是连接操作,服务端的BossGroup首先将这个连接放入WorkerGroup中的队列中去,然后进行我们配置的处理类里面的过程,如果是读操作,就会被WorkerGroup中的Selector监听到,进行我们编写的读操作的逻辑,Netty实现的是NIO的操作,所以线程之间不会发生阻塞。

3.2 Netty的零拷贝

再来说说Netty的零拷贝

首先要明确一点,没有真正意义上的零拷贝,我们这里说的零拷贝,是指Java虚拟机没有对数据进行再次的拷贝,而是直接处理。

传统Socket的操作:

操作系统在进行Socket通信操作时,接收到的数据会分配到操作系统的直接内存当中,所谓的直接内存,就是宿主机的内存,不是java虚拟机管理的内存。而我们的Java程序想要操作Socket传来的数据,需要将直接内存中的数据拷贝到java虚拟机中来进行操作。

Netty的实现 :

与传统Socket操作不同的地方是,Netty在操作Socket传来的数据时,不会将这些数据拷贝到Java的虚拟机中来,而是直接给出这些数据的引用,指向这些数据的地址,进行数据操作时,直接操作的是直接内存,所以还是有拷贝的,只不过没有拷贝到虚拟中来。