netty基本功能使用

1 Netty简介

  Netty是最流行的NIO框架,它的健壮性、功能、性能、可定制性和可扩展性在同类框架都是首屈一指的。它已经得到成百上千的商业/商用项目验证,如Hadoop的RPC框架Avro、RocketMQ以及主流的分布式通信框架Dubbox等等。

Netty是基于Java NIO client-server的网络应用框架,使用Netty可以快速开发网络应用,例如服务器和客户端协议。Netty提供了一种新的方式来开发网络应用程序,这种新的方式使它很容易使用和具有很强的扩展性。Netty的内部实现是很复杂的,但是Netty提供了简单易用的API从网络处理代码中解耦业务逻辑。Netty是完全基于NIO实现的,所以整个Netty都是异步的。

  网络应用程序通常需要有较高的可扩展性,无论是Netty还是其他的基于Java Nio的框架,都会提供可扩展性的解决方案。Netty中一个关键组成部分是它的异步特性,本片文章将讨论同步(阻塞)和异步(非阻塞)的IO来说明为什么使用异步代码解

决扩展性问题以及如何使用异步。

2 NIO的通信步骤

  ①创建ServerSocketChannel,为其配置非阻塞模式。

  ②绑定监听,配置TCP参数,录入backlog大小等。

  ③创建一个独立的IO线程,用于轮询多路复用器Selector。

  ④创建Selector,将之前创建的ServerSocketChannel注册到Selector上,并设置监听标识位SelectionKey.OP_ACCEPT。

  ⑤启动IO线程,在循环体中执行Selector.select()方法,轮询就绪的通道。

  ⑥当轮询到处于就绪状态的通道时,需要进行操作位判断,如果是ACCEPT状态,说明是新的客户端接入,则调用accept方法接收新的客户端。

  ⑦设置新接入客户端的一些参数,如非阻塞,并将其继续注册到Selector上,设置监听标识位等。

  ⑧如果轮询的通道标识位是READ,则进行读取,构造Buffer对象等。

  ⑨更细节的问题还有数据没发送完成继续发送的问题......

3  Netty通信的步骤

  ①创建两个NIO线程组,一个专门用于网络事件处理(接受客户端的连接),另一个则进行网络通信的读写。

  ②创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传出数据的缓存大小等。

  ③创建一个用于实际处理数据的类ChannelInitializer,进行初始化的准备工作,比如设置接受传出数据的字符集、格式以及实际处理数据的接口。

  ④绑定端口,执行同步阻塞方法等待服务器端启动即可。

//服务端代码
package netty.test;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.ReadTimeoutHandler;

public class NettyServer {
    private int port;

    public NettyServer(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) //指定NIO的模式
                    .childHandler(new ChannelInitializer<SocketChannel>() { //配置具体的数据处理方式
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline()
                                    .addLast(MarshallingCodeCFactory.buildMarshallingDecoder())//jboss解码
                                    .addLast(MarshallingCodeCFactory.buildMarshallingEncoder())//jboss编码
                                    .addLast(new ReadTimeoutHandler(5))//客户端5s没上送消息自动断开通道,短连接配合
                                    .addLast(new ServerHandler(1));//业务处理者添加到pipeline最后
                        }
                    })
                    /**
                     * 对于ChannelOption.SO_BACKLOG的解释:
                     * 服务器端TCP内核维护有两个队列,我们称之为A、B队列。客户端向服务器端connect时,会发送带有SYN标志的包(第一次握手),服务器端
                     * 接收到客户端发送的SYN时,向客户端发送SYN ACK确认(第二次握手),此时TCP内核模块把客户端连接加入到A队列中,然后服务器接收到
                     * 客户端发送的ACK时(第三次握手),TCP内核模块把客户端连接从A队列移动到B队列,连接完成,应用程序的accept会返回。也就是说accept
                     * 从B队列中取出完成了三次握手的连接。
                     * A队列和B队列的长度之和就是backlog。当A、B队列的长度之和大于ChannelOption.SO_BACKLOG时,新的连接将会被TCP内核拒绝。
                     * 所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户端无法连接。要注意的是,backlog对程序支持的
                     * 连接数并无影响,backlog影响的只是还没有被accept取出的连接
                     */
                    .option(ChannelOption.SO_BACKLOG, 128) //设置TCP缓冲区
                    .option(ChannelOption.SO_SNDBUF, 32 * 1024) //设置发送数据缓冲大小
                    .option(ChannelOption.SO_RCVBUF, 32 * 1024) //设置接受数据缓冲大小
                    .childOption(ChannelOption.SO_KEEPALIVE, false); //短连接
            ChannelFuture future = bootstrap.bind(port).sync(); //连接本机服务器

            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new NettyServer(8081).run();
    }

}

class ServerHandler  extends ChannelInboundHandlerAdapter  {
    private Integer id;


    public ServerHandler(){}
    public ServerHandler(Integer id){
        this.id = id;
    }



    @Override
    public void channelActive(final ChannelHandlerContext ctx) { //链接刚刚建立好时会触发一次该方法

        final ByteBuf time = ctx.alloc().buffer(4); //初始化缓冲区,制定最初大小4个字节

        time.writeBytes("testttttttttt".getBytes());

        final ChannelFuture f = ctx.writeAndFlush(time); //发送个通知到客户端,该任务是一个futrue

        f.addListener(new ChannelFutureListener() {//为该futrue添加一个监听器,执行完时关闭客户端链接
            @Override
            public void operationComplete(ChannelFuture future) {
               if(false)
                ctx.close();  //关闭客户端链接
            }
        });
    }


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//接受客户端信息
        //do something msg
        ByteBuf buf = (ByteBuf)msg;
        byte[] data = new byte[buf.readableBytes()];
        buf.readBytes(data);
        String request = new String(data, "utf-8");
        System.out.println("server"+id+" receive request: " + request);
        //写给客户端
        String response = "response:"+request;
        ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
        //.addListener(ChannelFutureListener.CLOSE);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {//异常处理方法
        System.out.println("server found exception");
        cause.printStackTrace();
        ctx.close();
    }

}
View Code
//客户端
package netty.test;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.ReferenceCountUtil;

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(workerGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new ClientHandler());
                    }
                });
        ChannelFuture future = bootstrap.connect("10.1.0.127", 10086).sync();
        String data = "{\"items\":{\"longitude\":114.27900999999999,\"terminalNumber\":\"352016900000001\",\"speed\":90,\"time\":1529907551,\"command\":\"\",\"sequence\":10263,\"status\":15,\"processCode\":\"OBD_PARASET_1\",\"terminalType\":\"OBD\",\"session\":1,\"latitude\":24.4188915,\"versionNo\":\"\",\"bearing\":242},\"indexes\":[\"sequence\",\"processCode\",\"terminalType\",\"terminalNumber\",\"time\",\"session\",\"latitude\",\"longitude\",\"speed\",\"bearing\",\"command\",\"versionNo\",\"status\"],\"sessionId\":null}";
        future.channel().writeAndFlush(Unpooled.copiedBuffer(data.getBytes()));
        future.channel().closeFuture().sync();
        workerGroup.shutdownGracefully();
    }
}


 class ClientHandler extends ChannelInboundHandlerAdapter  {

@Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//接收服务器返回
        try {
            ByteBuf buf = (ByteBuf) msg;
            byte[] data = new byte[buf.readableBytes()];
            buf.readBytes(data);
            System.out.println("Server response:" + new String(data).trim());
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {//异常处理
        System.out.println("client found Exception");
        cause.printStackTrace();
        ctx.close();
    }

}
View Code

4 TCP粘包、拆包问题

TCP是一个“流”协议,所谓流就是没有界限的遗传数据。大家可以想象一下,如果河水就好比数据,他们是连成一片的,没有分界线,

TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的具体情况进行包的划分,也就是说,在业务上一个完整的包可能会被

TCP分成多个包进行发送,也可能把多个小包封装成一个大的数据包发送出去,这就是所谓的粘包/拆包问题。

Netty中解决TCP粘包/拆包的方法:

①分隔符类:DelimiterBasedFrameDecoder(自定义分隔符)

②定长:FixedLengthFrameDecoder

 

 

更多的基本功能:如编解码、消息压缩等:https://blog.csdn.net/haoyuyang/article/details/53243785

posted on 2018-10-11 14:16  javaGreenHand。。。  阅读(55)  评论(0)    收藏  举报