三Netty--Netty入门应用

三Netty--Netty入门应用

Netty的开发过程,需要注意对比和NIO开发流程的对应关系。

仍旧以TimeServer为例,简单介绍Netty开发流程。

3.2 Server端开发

3.2.1 TimeServer代码

public class TimeServer {

    //server端bind接口,就是开启服务
    public void bind(int port) throws InterruptedException {

        /** EventLoopGroup是个线程组,包含一组NIO线程,用于网络事件处理,本质是reactor线程组
         * bossgroup用来处理网络连接;workerGroup用来处理读写请求--
         * */
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            //ServerBootstrap是用来启动NIO的辅助启动类,包含了一系列的配置
            ServerBootstrap b = new ServerBootstrap();
            //group,配置两个NIO线程组
            b.group(bossGroup, workerGroup)
                    //设置创建的channel类型
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    /**childHandler用于绑定IO事件的处理类,用于处理网络IO事件
                     * 类似于NIO中的handler类
                     * 该ChildChannelHandler,需要实现ChannelInitializer类,用于初始化IO的handler处理类
                     * 其实现方法initChannel,初始化channel,并向ch.pipeiine上添加对IO的具体处理类
                     * 该处理类可以自己定制实现,netty也提供了日志记录、编解码等组件类*/
                    .childHandler((ChannelHandler) new ChildChannelHandler());

            //绑定端口,并同步等待绑定成功
            ChannelFuture f = b.bind(port).sync();

            //等待服务端监听端口关闭
            f.channel().closeFuture().sync();
        }
      //当需要释放资源时,由于必须要finally,因此使用try、finally的组合
      finally {
            //try finally优雅退出,释放线程池资源(group为线程池)
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    //处理client端的SocketChannel的IO请求
    private class ChildChannelHandler extends ChannelInitializer {
        @Override
        protected void initChannel(Channel ch) throws Exception {
          //添加自定义的网络事件IO处理类,完成逻辑实现
            ch.pipeline().addLast(new TimeServerHandler());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 8080;
        new TimeServer().bind(port);
    }

}

编程技巧:当需要释放资源时,由于必须要finally,因此使用try、finally的组合。

需要注意的两个类:

(1)EventLoopGroup:

EventLoopGroup是个线程组,包含一组NIO线程,用于网络事件处理,本质是reactor线程组
bossgroup用来处理网络连接;workerGroup用来处理读写请求

(2)private class ChildChannelHandler extends ChannelInitializer :channel初始化处理类

childHandler用于绑定IO事件的处理类,用于处理网络IO事件
* 类似于NIO中的handler类;
* 该ChildChannelHandler,需要实现ChannelInitializer类,用于初始化IO的handler处理类;
* 其实现方法initChannel,初始化channel,并向ch.pipeiine上添加对IO的具体处理类;
* 该处理类可以自己定制实现,netty也提供了日志记录、编解码等组件类*/

(3)ch.pipeline().addLast(new TimeServerHandler()):网络IO处理类——自定义类

自定义网络IO的逻辑业务实现,需要实现ChannelHandlerAdapter类,然后加入channel的管道pipeline中,实现对网络IO的流水线处理。该类使用了adapter的适配器模式,实现了骨架。

image-20221128153058358

每个方法,都fire触发了channelHandler网络管道处理类的对应方法(因此,NIO中向selector的注册各种监听,和轮询就绪channel,就在这里实现)。

public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {

/**——————————————————————————————————————handler实现链式调用的关键—————————————————————————————————————— */
  //当channel读取read到数据时,会调用channelHandlerContext的fireXXX方法,执行channelPipeline链上下一个ChannelInboundHandler的执行,以实现链式调用。
  
  //读取channel发送过来的ByteBuf
      public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ctx.fireChannelRead(msg);
    }
  
//当读取完毕后,调用此方法---用来在前一个读取完毕后,进行ctx.flush()操作
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelReadComplete();
    }
  
  //处理异常
      public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.fireExceptionCaught(cause);
    }
  
  //用在建立连接后,channelActive活动状态,进行操作(如client建立联系,发送请求消息内容)
      public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelActive();
    }
  
  

3.2.2 IO处理类(ChannelHandlerAdapter)

channelHandlerAdapter为接口适配器模式。该类是抽象类abstract class,两个子类实现该抽象类,然后创建channelHandler类,可以接口多态模式,创建对象,但是对应方法,可以是子类中的方法。例如,这里创建server端的channelhandler类,但是其既需要inbound方法,又需要outbound方法,因此,使用该adapter类,可以创建一个适配器抽象类。继承该类,就可以实现一个既有inbound又有outbound方法的handler。

image-20221128154211208

public class TimeServerHandler extends ChannelHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //这里的msg是client通过channel传过来的ByteBuf形式数据,需要转换为string型
        ByteBuf buf = (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req);

        //………………IO处理
        //省略server端对client传递的请求的处理
        String resp = "返回处理结果";
        ByteBuf respBB = Unpooled.copiedBuffer(resp.getBytes(StandardCharsets.UTF_8));
        /** 注意:*/
        ctx.write(respBB);
    }

    @Override
  //读取channel发送msg完毕后操作
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            /**  flush,将消息发送队列中的消息写入SocketChannel中,并发送给对方。
         * 从性能角度考虑,为了不频繁唤醒Selector进行消息发送,netty的write方法,不是直接写入SocketChannel中,
         * 调用write方法只把待发送消息放到发送缓冲数组中。贼读取完毕后,再通过flush(),将发送区中的消息全部写到SocketChannel*/
        ctx.flush();
    }

    //对异常出现时,关闭
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

注意:

netty中write,writeAndFlush,flush的关系。

3.3 Client端开发

3.3.1 TimeClient客户端开发

public class TimeClient {

    public void connect(int port, String host) {
        //配置客户端NIO线程组
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .option(ChannelOption.TCP_NODELAY,Boolean.TRUE)
                    //添加网络IO处理类(其实是个channel初始化类,负责添加具体的IO处理业务类)
                    .handler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //添加具体业务处理类
                            ch.pipeline().addLast(new TimeClientHandler());
                        }
                    });

        }finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new TimeClient().connect(80,"111");
    }
}

注意:(回顾)

handler(new ChannelInitializer()

当创建NioSocketChannel成功后,进行初始化时,将ChannelHandler设置到ChannelPipeline中(就是自定义IO业务处理类)。

3.3.2 IO处理类

//adapter适配器模式
public class TimeClientHandler extends ChannelHandlerAdapter {

    //待发送请求
    private final ByteBuf request;

    // 构造函数,定义第一条请求,在初始化TimeClientHandler时,构造request请求
    public TimeClientHandler() {
        byte[] req = "第一条请求".getBytes(StandardCharsets.UTF_8);
        request = Unpooled.buffer(req.length);
        request.writeBytes(req);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //发送消息
        ctx.writeAndFlush(request);
    }

    /** 该处就是client读取server返回的结果*/
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        //首先将ByteBuf的msg转换为string
        ByteBuf buf = (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req);

        //处理结果
        System.out.println(body);
    }

    @Override
    //出现exception时,释放ChannelHandlerContext资源
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}
posted @ 2023-03-10 17:21  LeasonXue  阅读(29)  评论(0)    收藏  举报