Netty组件

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

1、NioEventLoopGroup

一个Netty服务端启动时,通常会有两个NioEventLoopGroup:一个boss,一个worker。第一个NioEventLoopGroup正常只需要一个EventLoop,主要负责客户端的连接请求,然后打开一个Channel,交给第二个NioEventLoopGroup中的一个EventLoop来处理这个Channel上的所有读写事件。一个Channel只会被一个EventLoop处理,而一个EventLoop可能会被分配给多个Channel。

Netty组件_第1张图片

2、ChannelFuture 接口

由于Netty中的所有I/O操作都是异步的,因此Netty为了解决调用者如何获取异步操作结果的问题而专门设计了ChannelFuture接口. 

可以调用ChannelFuture里的方法获取channel,Channel与ChannelFuture可以说形影不离的.

3、Channel(通道)
可以理解为该类对socket进行了封装,提供了数据的读取和写入功能。

4、ChannelHandler

Netty 定义了下面两个重要的ChannelHandler 子接口:

ChannelInboundHandler(入站handler) —— channel上有消息可读时,netty就会将该消息交给入站处理器处理。可以有过个处理器,形成一个链条。

Netty组件_第2张图片

内部方法:

Netty组件_第3张图片

当某个 ChannelInboundHandler 的实现重写 channelRead()方法时,可以显式地释放与池化的 ByteBuf 实例相关的内存。Netty 为此提供了一个实用方法 ReferenceCountUtil.release();比如

Netty组件_第4张图片

但是以这种方式管理资源可能很繁琐,一个更加简单的方式是使用SimpleChannelInboundHandler 会自动释放资源。如下:

Netty组件_第5张图片

 

ChannelOutboundHandler(出站handler) —— channel写出数据时,会经过出站处理器的处理后,再发送到网络上。可以有多个处理器,形成一个链条。

Netty组件_第6张图片

内部方法:

Netty组件_第7张图片

ChannelPromise与ChannelFuture:ChannelOutboundHandler中的大部分方法都需要一个ChannelPromise参数,以便在操作完成时得到通知。ChannelPromise是ChannelFuture的一个子类,其定义了一些可写的方法,如setSuccess()和setFailure(),从而使ChannelFuture不可变。

注意:

如果一个消息被消费或者丢弃了,并且没有传递给 ChannelPipeline 中的下一个ChannelOutboundHandler,那么用户就有责任调用 ReferenceCountUtil.release()。

比如:

Netty组件_第8张图片

如果消息到达了实际的传输层,那么当它被写入时或者 Channel 关闭时,都将被自动释放。

5、ChannelPipeline

Netty会把出站Handler和入站Handler放到一个Pipeline中,同属一个方向的Handler则是有顺序的,因为上一个Handler处理的结果往往是下一个Handler的要求的输入。

Netty组件_第9张图片

6、ChannelHandlerContext

ChannelHandlerContext 代表了 ChannelHandler 和 ChannelPipeline 之间的关联,每当有 ChannelHandler 添加到 ChannelPipeline 中时,都会创建 ChannelHandlerContext。ChannelHandlerContext 的主要功能是管理它所关联的 ChannelHandler 和在同一个 ChannelPipeline 中的其他 ChannelHandler之间的交互。

Netty组件_第10张图片

内部方法:

Netty组件_第11张图片

Netty组件_第12张图片

1758a1f9cb2d240b59a3c135e873f6da71a.jpg

消息向下传递ctx.fireChannelRead(msg); 这个方法可能你可能会用到,其次就是write相关方法比较常用。

 

7、 实战多个入站和出站处理器

7.1 服务端

Message对象:

@Data
public class Message implements Serializable{
    //消息内容
    private String content;
    //消息时间戳
    long time;
}

入站处理器1:将Byte转为Messag对象

/**
 * 将ByteBuf数据 转为对象
 */
public class EchoServerInHandler1 extends ChannelInboundHandlerAdapter {
    /**
     * 服务端读取到网络数据后的处理
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        //ByteBuf为netty实现的缓冲区
        ByteBuf in = (ByteBuf)msg;
        String msgStr = in.toString(CharsetUtil.UTF_8);
        System.out.println("EchoServerInHandler1 处理:" + msgStr);
        Message message = JSON.parseObject(msgStr,new TypeReference(){});
        ReferenceCountUtil.release(msg);//通过使用 ReferenceCountUtil.realse(...)方法释放资源
        ctx.fireChannelRead(message);
    }

}

 

入站处理器2:业务处理

public class EchoServerInHandler2 extends SimpleChannelInboundHandler {
    /**
     * 服务端读取到网络数据后的处理
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, Message msg)
            throws Exception {
        System.out.println("EchoServerInHandler2 收到消息:" + msg);
        Message message = new Message();
        message.setContent("success");
        ctx.writeAndFlush(message);
    }
}

 

出站处理器1:将Message对象转为字节发送到网络

public class EchoServerOutHandler1 extends ChannelOutboundHandlerAdapter{

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("EchoServerOutHandler1 处理:"+msg);
        Message message = (Message)msg;
        String messageJson = JSON.toJSONString(message);
        ByteBuf byteBuf = Unpooled.copiedBuffer(messageJson, CharsetUtil.UTF_8);
        ctx.write(byteBuf, promise);
    }
}

出站处理器2:给Message对象加上时间戳

public class EchoServerOutHandler2 extends ChannelOutboundHandlerAdapter{

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("EchoServerOutHandler2 处理:"+msg);
        Message message = (Message) msg;
        message.setTime(System.currentTimeMillis());
        ctx.write(msg, promise);
    }
}

 

在pipeline上添加入站和出站处理器

public class EchoServer {
    private int port;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;
    private ServerBootstrap b;

    public EchoServer(int port) {
        this.port = port;
        //第一个线程组是用于接收Client连接的
        bossGroup = new NioEventLoopGroup(1);
        //第二个线程组是用于消息的读写操作
        workGroup = new NioEventLoopGroup(2);
        //服务端辅助启动类
        b = new ServerBootstrap();
        b.group(bossGroup, workGroup)
                //需要指定使用NioServerSocketChannel这种类型的通道
                .channel(NioServerSocketChannel.class)
                //向ChannelPipeline里添加业务处理handler
                .childHandler(new ChannelInitializer() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        //出站处理器 先注册后执行
                        ch.pipeline().addLast(new EchoServerOutHandler1());//out 1
                        ch.pipeline().addLast(new EchoServerOutHandler2());//out 2

                        //入站处理器 先注册先执行
                        ch.pipeline().addLast(new EchoServerInHandler1());//in1
                        ch.pipeline().addLast(new EchoServerInHandler2());//in2

                    }
                });
    }

    /**
     * 启动
     * @throws InterruptedException
     */
    public void start() throws InterruptedException {
        try {
            //绑定到端口,阻塞等待直到完成
            b.bind(this.port).sync();
            System.out.println("服务器启动成功");
        } finally {

        }
    }

    /**
     * 资源优雅释放
     */
    public void close() {
        try {
            if (bossGroup != null)
            bossGroup.shutdownGracefully().sync();
            if (workGroup != null)
            workGroup.shutdownGracefully().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        int port = 9999;
        EchoServer echoServer = new EchoServer(port);
        try {
            echoServer.start();
            //防止主程序退出
            System.in.read();
        } finally {
            echoServer.close();
        }

    }
}

需要注意处理器的顺序:

先说最基本的,读入数据,执行顺序和注册顺序一致 in1 --> in2 ,他们之间通过 ctx.fireChannelRead(msg);进行传递。

Netty组件_第13张图片

从EchoServerInHandler1开始执行到EchoServerInHandler2,逻辑处理,进行数据发送返回, 通过ctx.writeAndFlush()就完成从in -->out的转换。

ctx.writeAndFlush()从当前节点往前查找out类handler,所以就会以 out2----》out1 这样一种方式执行。

假如下面这种顺序, 出站处理器放到后面,这时调用ctx.writeAndFlush()往前找out类型的处理器就会找不到。这种情况要想执行outhandler的处理,应该执行ctx.channel().writeAndFlush();这是从链表结尾开始往前查找out类handler,这就是两种writeAndFlush的区别

Netty组件_第14张图片

 

7.2 客户端代码

public class EchoClient {

    private final int port;
    private final String host;
    private Channel channel;
    private EventLoopGroup group;
    private Bootstrap b;

    public EchoClient(String host, int port) {
        this.host = host;
        this.port = port;
        //客户端启动辅助类
        b = new Bootstrap();
        //构建线程组 处理读写
        group = new NioEventLoopGroup();
        b.group(group)
                //指明使用NIO进行网络通讯
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        //出站处理器 先注册后执行
                        ch.pipeline().addLast(new EchoClientOutHandler1());
                        ch.pipeline().addLast(new EchoClientOutHandler2());

                        //入站处理器 先注册先执行
                        ch.pipeline().addLast(new EchoClientInHandler1());
                        ch.pipeline().addLast(new EchoClientInHandler2());


                    }
                });
    }

    public void start() {
        try {
            //连接到远程节点,阻塞等待直到连接完成
            ChannelFuture f = b.connect(new InetSocketAddress(host, port)).sync();
            //同步获取channel(通道,实际上是对socket的封装支持读取和写入)
            channel = f.sync().channel();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送消息
     * @param msg
     * @return
     */
    public boolean send(String msg) {
        Message message = new Message();
        message.setContent(msg);
        channel.writeAndFlush(message);
        return true;
    }

    /**
     * 关闭释放资源
     */
    public void close(){
        try {
            if (group != null)
            group.shutdownGracefully().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        EchoClient client = new EchoClient("127.0.0.1", 9999);
        try {
            client.start();
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String msg = scanner.next();
                client.send(msg);
            }
        }finally {
            client.close();
        }

    }
}


public class EchoClientInHandler1 extends ChannelInboundHandlerAdapter{

    /**读取数据
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        //ByteBuf为netty实现的缓冲区
        ByteBuf in = (ByteBuf)msg;
        String msgStr = in.toString(CharsetUtil.UTF_8);
        System.out.println("EchoClientInHandler1 处理:" + msgStr);
        Message message = JSON.parseObject(msgStr,new TypeReference(){});
        ctx.fireChannelRead(message);
    }

    /**
     * 当Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("EchoClientInHandler1 channelActive");
    }
    /**
     * exceptionCaught()事件处理方法当出现Throwable对象才会被调用
     * 即当Netty由于IO错误或者处理器在处理事件时抛出的异常时
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

public class EchoClientInHandler2 extends SimpleChannelInboundHandler {
    /**
     * 客户端读取到数据
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, Message msg)
            throws Exception {
        System.out.println("EchoClientInHandler2 handler:" + msg);
    }
}

/**
 * 该处理器将对象转为字节 发送到网络上
 */
public class EchoClientOutHandler1 extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("EchoClientOutHandler1 处理:"+msg);
        Message message = (Message)msg;
        String messageJson = JSON.toJSONString(message);
        ByteBuf byteBuf = Unpooled.copiedBuffer(messageJson, CharsetUtil.UTF_8);
        ctx.write(byteBuf, promise);
    }
}

/**
 * 该处理器给message加上时间戳
 */
public class EchoClientOutHandler2 extends ChannelOutboundHandlerAdapter{

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("EchoClientOutHandler2 处理:"+msg);
        Message message = (Message) msg;
        message.setTime(System.currentTimeMillis());
        ctx.write(msg, promise);
    }
}

 

启动服务端和客户端,然后发送消息

Netty组件_第15张图片

Netty组件_第16张图片

转载于:https://my.oschina.net/suzheworld/blog/3006174

你可能感兴趣的:(Netty组件)