Netty组件

1、NioEventLoopGroup

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

ed89c8418691526cbecabf71522a946ff01.jpg

2、ChannelFuture 接口

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

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

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

4、ChannelHandler

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

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

160992155dd4e55603ceb9434e3b7237f38.jpg

内部方法:

acf54b94532ed83bbbf04f62a83d7c5b62b.jpg

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

99436281cdfc04681baeaf282e2bddc351f.jpg

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

73f76f3b56238333401ced9c65da154b31b.jpg

 

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

f5acca3c4523c83536965a56799cfe24f54.jpg

内部方法:

b7b29a2daf7a3d1ad8a3e0aaa421edd6120.jpg

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

注意:

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

比如:

8d9205e3312e03d667a7a6a6a2b6ad847af.jpg

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

5、ChannelPipeline

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

ea8cbd640479e4c2be46ea51d30d096e9cc.jpg

6、ChannelHandlerContext

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

9e2d9e5bc2f0eb840c4a9cef37a6d9689fd.jpg

内部方法:

e4a37a0d6ce7841061910b508562298dd72.jpg

8100bf90ff97290e43eb6ee64297c6c5f15.jpg

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<Message>(){});
        ReferenceCountUtil.release(msg);//通过使用 ReferenceCountUtil.realse(...)方法释放资源
        ctx.fireChannelRead(message);
    }

}

 

入站处理器2:业务处理

public class EchoServerInHandler2 extends SimpleChannelInboundHandler<Message> {
    /**
     * 服务端读取到网络数据后的处理
     * @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<SocketChannel>() {
                    @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);进行传递。

0c2b33a765b1b2f0ef215cc1980472dc576.jpg

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

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

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

6b8fae3e02ce62fa43b44903f9705b5812c.jpg

 

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<SocketChannel>() {
                    @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<Message>(){});
        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<Message> {
    /**
     * 客户端读取到数据
     * @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);
    }
}

 

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

a2bd3c7efe307f8dcdd31f98c8bbb76ce0a.jpg

cefb40833d2d7b02ba3b5d717821a7c64c8.jpg

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值