netty入门及介绍

Posted 叶长风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了netty入门及介绍相关的知识,希望对你有一定的参考价值。

netty入门及介绍


  • 1、前言

  • 2、服务端程序

  • 3、客户端程序

  • 4、部分netty知识详解

  • 5、总结

1.前言


前段时间研究了一段时间的rpc框架,dubbo、zero ice等等都熟悉了一下,然后发现许多rpc框架底层都是采用netty进行通信,由此对netty有了一些兴趣,然后花了三个多月的时间学习了一下netty,一些博文专栏中的文章、官网的例子,都去写了一遍,不能说是很熟悉netty,但是多多少少还是有了一些了解,然后决定再开一个neety专栏记载我学的这些东西,使用的netty的版本是4.1.12.Final,没有使用netty5,原因也是很简单,netty5被官方放弃了,因为netty5引入了新的特性增加了编码的复杂度,但是却没有显著性的提高性能,因此不再推广和维护netty5,继续维护和更新netty4,上图是netty的一些比较有优势的特性,这个在后续的文章中会逐一讲述。

2.服务端程序


在这里要提的是最简单的程序并不是hello world,而是丢弃服务,这里引用一下丢弃服务的定义。

简单来说就是不做任何事情。服务无论接收到任何数据都会丢弃数据并且不进行响应,十分简单,以下就是具体的程序:

DiscardServerHandler:

package cn.com.discard;

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

/**
 * Created by xiaxuan on 17/7/6.
 */
public class DiscardServerHandler extends ChannelInboundHandlerAdapter 

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception 
        ((ByteBuf)msg).release();
    

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception 
        cause.printStackTrace();
        ctx.close();
    

这个DiscardServerHandler就是继承ChannelInboundHandlerAdapter这个类后,覆盖父类方法channelRead和exceptionCaught,channelRead中做的事情是将获得的消息进行丢弃,不进行任何操作,而exceptionCaught方法简而易懂,在发生错误的时候打印错误日志并关闭上下文。

DiscardServer:

package cn.com.discard;

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;

/**
 * Created by xiaxuan on 17/7/6.
 */
public class DiscardServer 

    private int port;

    public DiscardServer(int port) 
        this.port = port;
    

    public void run() 
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try 
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    //option()是提供给NioServerSocketChannel用来接收进来的连接,也就是boss线程
                    .option(ChannelOption.SO_BACKLOG, 128)
                    //childOption()是提供给由父管道ServerChannel接收到的连接,也就是worker线程,这个例子中
                    //也是NioServerSocketChannel
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() 
                        protected void initChannel(SocketChannel ch) throws Exception 
                            ch.pipeline().addLast(new DiscardServerHandler());
                        
                    );

            ChannelFuture f = bootstrap.bind(port).sync();
            f.channel().closeFuture().sync();
         catch (InterruptedException e) 
            e.printStackTrace();
         finally 
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        
    

    public static void main(String[] args) 
        int port;
        if (args.length > 0) 
            port = Integer.valueOf(args[0]);
         else 
            port = 8080;
        
        new DiscardServer(port).run();
    

DiscardServer是启动类,这里的NioEventLoopGroup是netty用来处理的i/o操作的多线程事件循环器,在这里有两个NioEventLoopGroup对象,一个是boss,一个是worker,boss用来接收连接,worker用来处理连接中的数据。
ServerBootstrap是启动的辅助类,这个启动类我们调用了channel、option、childOption、childHandler等等方法。上面代码中有一定注释,这里只讲 ChannelOption.SO_BACKLOGChannelOption.SO_BACKLOG 这两个属性是什么意思。

ChannelOption.SO_BACKLOG : BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50ChannelOption.SO_KEEPALIVE: 是否启用心跳保活机制。在双方TCP套接字建立连接后(即都进入ESTABLISHED状态)并且在两个小时左右上层没有任何数据传输的情况下,这套机制才会被激活。

我们在childHandler中有一行代码为:

ch.pipeline().addLast(new DiscardServerHandler());

这行的代码就是将DiscardServerHandler加入到pipline中对数据进行处理。
以上就是服务端程序。

3.客户端程序


客户端程序也是同样的简单。

DiscardClientHandler:

package cn.com.discard;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * Created by xiaxuan on 17/7/6.
 */
public class DiscardClientHandler extends SimpleChannelInboundHandler<Object> 

    private ByteBuf content;
    private ChannelHandlerContext ctx;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception 
        this.ctx = ctx;
        content = ctx.alloc().directBuffer(DiscardClient.SIZE).writeZero(DiscardClient.SIZE);

        generateTraffic();
    

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception 
        content.release();
    

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception 
        cause.printStackTrace();
        ctx.close();
    

    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception 

    

    private void generateTraffic() 
        ctx.writeAndFlush(content.duplicate().retain()).addListener(trafficGenerator);
    


    private final ChannelFutureListener trafficGenerator = future -> 
        if (future.isSuccess()) 
            generateTraffic();
         else 
            future.cause().printStackTrace();
            future.channel().close();
        
    ;

其他的代码不多说,大致的用法和先前写的差不多,但是我在最后写了一个generateTraffic方法,在里面ctx增加listener,而这个listener的用法就是在当future的请求成功后,我们再次调用generateTraffic()方法,因此会不断的发送数据和注册listener。

DiscardClient:

package cn.com.discard;

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

/**
 * Created by xiaxuan on 17/7/6.
 */
public class DiscardClient 

    static final String HOST = System.getProperty("host", "127.0.0.1");
    static final int PORT = Integer.valueOf(System.getProperty("port", "8080"));
    static final int SIZE = Integer.valueOf(System.getProperty("size", "256"));

    public static void main(String[] args) 
        EventLoopGroup group = new NioEventLoopGroup();
        try 
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() 
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception 
                            ch.pipeline().addLast(new DiscardClientHandler());
                        
                    );

            ChannelFuture f = bootstrap.connect(HOST, PORT).sync();

            f.channel().closeFuture().sync();
         catch (InterruptedException e) 
            e.printStackTrace();
         finally 
            group.shutdownGracefully();
        
    

在上面代码中,只用一个NioEventLoopGroup对象,因为只需要一个发送数据的对象即可,不需要如server一样多个NioEventLoopGroup对象对连接进行处理。其他写法和server类似。

当我们启动运行server和client后,虽然控制台没有打出任何内容,但是如果在server的handler中在数据丢弃的地方将msg打出来的话,可以看到server不断的收到数据,这是因为Client在添加listener时在每次成功的时候再次调用发送数据的方法然后再次添加listener,因此便一直递归下去。

4.部分netty知识详解


4.1 @ChannelHandler.Sharable注解


以上还需要提一下其他文章中的一个写法,这是我过了很久才知道是什么意思,就是在handler上加一个注解:

@ChannelHandler.Sharable

Sharable注解主要是用来标示一个ChannelHandler可以被安全地共享,即可以在多个Channel的ChannelPipeline中使用同一个ChannelHandler,而不必每一个ChannelPipeline都重新new一个新的ChannelHandler。也就是说您的ChannelHandler是线程安全的。这种情况比如会用在统计整体的吞吐量的时候用到。

4.2 bossGroup和workerGroup


在server程序中有两个NioEventLoop对象,分别为bossGroup和workerGroup,用途也就是一个用于接收连接,一个用于处理数据,服务端监听线程和IO线程分离,类似于Reactor的多线程模型,它的工作原理图如下:

更多的日后再来进行分析,现在只是讲一些皮毛所在。

4.3 f.channel().closeFuture().sync()含义


这个的用途实质上就是保证程序不会顺序运行下去后然后程序结束了,保证当先线程被阻塞在closeFuture这里,程序不会运行结束。


5.总结


这篇文章没有讲述太多东西,就是一个netty的一个简单入门,但是我把这个简单入门程序的方方面面都搞清楚却是花费了我很长一段时间,在一段时间内,只知道这么写,练习了许多的程序,到netty的一般使用的api都比较熟悉后,回来一遍遍的查这些属性的含义,于是才慢慢的搞清楚用途,今日写这篇文章,记载学习netty的开始。

以上是关于netty入门及介绍的主要内容,如果未能解决你的问题,请参考以下文章

即时通讯开发之Netty入门长文:基本介绍环境搭建

七.Netty入门到超神系列-Netty介绍和线程模型

Netty

[Netty源码系列]-Nio入门

Netty入门介绍

Netty介绍及使用