Netty基本概念

Netty

Netty入门知识

1.1 Netty概述

1.1.1 Netty简介
  • Netty是一个异步时间驱动的网络应用程序框架,用于快速开发可维护的高性能服务器和客户端。
  • Netty是一个NIO客户机-服务器框架,它支持快速、简单地开发网络应用程序,如服务器和客户机。它大大简化了网络编程,如TCP和UDP套接字服务器。
  • “快速和简单”并不意味着生成的应用程序将受到可维护性或性能问题的影响。Netty经过精心设计,并积累了许多协议(如ftp、smtp、http)的实施经验,以及各种二进制和基于文本的遗留协议。因此,Netty成功地找到了一种方法,在不妥协的情况下实现了易于开发、性能、稳定性和灵活性。
1.1.2 谁在使用Netty

​ Dobbo、zk、RocketMQ、ElasticSearch、Spring5(对HTTP协议的实现)、GRpc、Spark等大型开源项目都在使用Netty作为底层通讯框架。

1.1.3 Netty执行流程

在这里插入图片描述

1.1.4 Netty中核心概念
  1. Channel

    管道,其是对Socket的封装,其包含了一组API,大大简化了直接与Socket进行操作的复杂性。

  2. EventLoopGroup

    EventLoopGroup是一个EventLoop线程池,包含了很多的EventLoop。

    Netty为每个Channel分配了一个EventLoop,用于处理用户连接请求、对用户请求的处理等所有事件。EventLoop本身只是一个线程驱动,在其生命周期内只会绑定一个线程,让该线程处理一个Channel的所有IO事件。

    一个Channel一旦与一个EventLoop相绑定,则在Channel的整个生命周期内是不会也不能发生变化。但一个EventLoop可以与多个Channel相绑定。

  3. ServerBootStrap

    用于配置整个Netty代码,将各个组件关联起来。服务端使用的是ServerBootStrap,而客户端使用的是BootStrap。

  4. ChannelHandler(管道数据处理器)与ChannelPipeline(管线对象)

    ChannelHandler是对Channel中数据的处理器,这些处理器可以是系统本身定义好的编码器,也可以是用户自定义的。这些处理器会被统一添加到一个ChannelPipeline的对象中,然后按照添加的顺序对Channel中的数据进行依次处理。

  5. ChannelFuture

    Netty中所有I/O操作都是异步的,即操作不会立即得到返回结果,所以Netty中定义了一个ChannelFuture对象作为这个异步操作的“代言人”,表示异步操作本身。如果想获取到该异步操作的返回值,可以通过该异步操作对象的addListener()方法为该异步操作添加监听器,为其注册回调:当结果出来后马上调用执行。

    Netty的异步编程模型都是建立在Future与回调概念之上的。

1.2 Http服务端01-primary

​ 通过该程序达到的目的是,对 Netty 编程的基本结构及流程有所了解。
该程序是通过 Netty 实现 HTTP 请求的处理,即接收 HTTP 请求,返回 HTTP 响应。
这个代码相当于“SpringMVC + Tomcat”。

1.2.1 创建工程

​ 创建一个普通的Maven的java工程。

1.2.2 导入依赖

​ 仅导入一个netty-all依赖即可。

<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.targer>1.8</maven.compiler.targer>
</properties>
<dependencies>
    <!-- netty-all 依赖  -->
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.36.Final</version>
    </dependency>
    <!--lombok 依赖 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.6</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
1.2.3 定义服务器启动类

​ 该服务器就是用于创建并初始化服务器启动对象ServerBootStrap。

/**
 * 服务器启动类
 */
public class SomeServer {
    public static void main(String[] args) {
        //用于处理客户端连接请求,将请求发送给childGroup中的eventLoop
        EventLoopGroup parentGroup = new NioEventLoopGroup();
        //用于处理客户端请求
        EventLoopGroup childGroup = new NioEventLoopGroup();

        try {
            //用于启动ServerChannel
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(parentGroup,childGroup)//指定eventLoopGroup
                     .channel(NioServerSocketChannel.class)//指定使用NIO进行通信
                     .childHandler(new ChannelInitializer<SocketChannel>() {
                         //当Channel初始化创建完毕后就会触发该方法的执行,用于初始化Channel
                         @Override
                         protected void initChannel(SocketChannel socketChannel) throws Exception {
                             //从Channel中获取pipeline
                             ChannelPipeline pipeline = socketChannel.pipeline();
                             //将HttpServerCode处理器放入到pipeline的最后
                             //HttpServerCodec是什么?是HttpRequestDecoder与HttpResponseEncoder的复合体
                             //HttpRequestDecoder:http请求解码器,将Channel中的ByteBuf数据解码为HttpRequest对象
                             //HttpResponseEncoder:http响应编码器,将HttpResponse对象编码为将要在Channel中发送的ByteBuf数据
                             pipeline.addLast(new HttpServerCodec());
                             //将自定义的处理器放入到pipeline的最后
                             pipeline.addLast(new SomeServerHandler());
                         }
                     });//指定childGroup中的eventLoop所绑定的线程所处理的处理器
            //指定当前服务器所监听的端口号
            //bind()方法的执行时异步的
            //sync()方法会使bind()操作与后续的代码的执行由异步变为同步。
            ChannelFuture future = bootstrap.bind(8888).sync();
            //只要放回的是ChannelFuture,那么这个方法就是异步执行的。
            System.out.println("服务器启动成功。监听的端口号是8888!");
            //关闭Chanel
            //closeFuture()的指定是异步的
            //当Channel调用了close()方法并关闭成功之后才会触发closeFurture()方法的执行
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //优雅关闭(没有处理完毕的等待处理完毕之后在关闭)
            parentGroup.shutdownGracefully();
            childGroup.shutdownGracefully();
        }
    }
}
1.2.4 定义管道初始化器
/**
 * 管道初始化器
 * 当前类的实例在pipeline初始化完毕之后就会被GC
 */
public class SomeChannelInitialize extends ChannelInitializer<SocketChannel> {
    //当Channel初始化创建完毕后就会触发该方法的执行,用于初始化Channel
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //从Channel中获取pipeline
        ChannelPipeline pipeline = socketChannel.pipeline();
        //将HttpServerCode处理器放入到pipeline的最后
        //HttpServerCodec是什么?是HttpRequestDecoder与HttpResponseEncoder的复合体
        //HttpRequestDecoder:http请求解码器,将Channel中的ByteBuf数据解码为HttpRequest对象
        //HttpResponseEncoder:http响应编码器,将HttpResponse对象编码为将要在Channel中发送的ByteBuf数据
        pipeline.addLast(new HttpServerCodec());
        //将自定义的处理器放入到pipeline的最后
        pipeline.addLast(new SomeServerHandler());

    }
}

由于该类只需要初始化一次,所以一般直接在启动类利用匿名内部类的写法创建即可。

1.2.5 自定义服务端处理器
/**
 * 自定义服务器处理器(这才是真正的业务处理部分)
 * 需求:用户提交一个请求后,在浏览器就会看到Hello netty world
 */
public class SomeServerHandler extends ChannelInboundHandlerAdapter {
    /**
     * 当Channel中有来自于客户端的数据时,就会触发该方法的执行
     * @param ctx 上下文对象
     * @param msg 就是来自于客户端的数据
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        System.out.println("msg = " + msg.getClass());
//        System.out.println("客户端地址 = " + ctx.channel().remoteAddress());
        if (msg instanceof HttpRequest){
            HttpRequest request =  (HttpRequest)msg;
            System.out.println("请求方式: " + request.method().name());
            System.out.println("请求URI:" + request.uri());
            if ("/favicon.ico".equals(request.uri())){
                System.out.println("浏览器发送的请求图标,我们不做处理!");
                return;
            }

            //构造response的响应体
            ByteBuf body = Unpooled.copiedBuffer("Hello netty world", CharsetUtil.UTF_8);
            //生成响应对象
            DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, body);
            //获取到response的头部后进行初始化
            HttpHeaders headers = response.headers();
            headers.set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            headers.set(HttpHeaderNames.CONTENT_LENGTH, body.readableBytes());

            //将响应对象写入到Channel
            //ctx.write(response);
            //ctx.flush();
            //ctx.writeAndFlush(response);
            ctx.writeAndFlush(response)
                    //添加监听器,响应体发送完毕之后直接关闭Channel
                    .addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 当Channel中的数据在处理过程中发生异常时就会触发该方法的执行
     * @param ctx 上下文
     * @param cause 发生的异常对象
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        //关闭Channel
        ctx.close();
    }
}

1.3 Socket编程02-socket

​ 在netty中若要学习TCP的拆包与粘包,则首先要清楚基于TCP协议的Socket编程。

1.3.1 创建工程

​ 复制 01-primary2 工程,在此基础上进行修改。
​ 本例要实现的功能是:客户端连接上服务端后,服务端会向客户端发送一个数据。客户
端每收到服务端的一个数据后,便会再向服务端发送一个数据。而服务端每收到客户端的一
个数据后,便会再向客户端发送一个数据。如此反复,无穷匮也。

1.3.2 定义服务端
  1. 定义服务端启动类
/**
 * 服务端启动类
 */
public class SomeServer {
    public static void main(String[] args) {
        EventLoopGroup parentGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(parentGroup,childGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //从Channel中获取pipeline
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //StringDecoder:字符串解码器,将Channel中的Bytebuf数据解码转成String
                            pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
                            //StringEncoder:字符串编码器,将String编码转成将要发送到Channel中的ByteBuf
                            pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
                            //自定义处理器
                            pipeline.addLast(new SomeSocketServerHandler());
                        }
                    });
            //指定服务的端口号
            ChannelFuture future = bootstrap.bind(8888).sync();
            System.out.println("服务器已启动,监听的端口为8888!");
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (parentGroup != null){
                parentGroup.shutdownGracefully();
            }
            if (childGroup != null){
                childGroup.shutdownGracefully();
            }
        }
    }
}
  1. 定义服务端处理器
public class SomeSocketServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //将来自于客户端的数据显示在服务端控制台
        System.out.println(ctx.channel().remoteAddress() + ", " + msg);
        //向客户端发送数据
        ctx.writeAndFlush("from server :" + UUID.randomUUID());
        TimeUnit.MILLISECONDS.sleep(3000);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
1.3.3 定义客户端
  1. 定义客户端启动类
/**
 * 客户端启动类
 */
public class SomeClient {
    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 socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new SomeSocketClinetHandler());
                        }
                    });
            ChannelFuture future = bootstrap.connect("localhost", 8888).sync();
            System.out.println("已连接上服务器!");
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (group != null)
            group.shutdownGracefully();
        }
    }
}
  1. 定义客户端处理器
public class SomeSocketClinetHandler extends SimpleChannelInboundHandler<String> {

    //msg的消息类型与类中的泛型类型是一致的
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(ctx.channel().remoteAddress() + ", " + msg);
        ctx.writeAndFlush("from client: " + LocalDateTime.now());
        TimeUnit.MILLISECONDS.sleep(3000);
    }

    //当Channel被激活(创建好初始化之后紧接着就是被激活)后悔触发该方法的执行
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush("from client: " + "begin talking!");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
1.3.4 两个处理器的区别

SimpleChannelInboundHandler中的channelRead0()方法会自动释放接受到来自对方的msg所占有的所有资源。

ChannelInboundHandlerAdapter中的channelRead()方法不会自动释放接受到来自对方的msg.

若对方没有向自己发送数据,则自定义处理器建议集成自ChannelInboundHandlerAdapter,因为若继承自SimpleChannelInboundHandler需要重写channelRead0()方法,而重写该方法的目的就是对来自对方的数据进行处理,因为对方根本没有发送数据,所以也没有必要重写channelRead()方法

若对方向自己发送了数据,而自己又需要将该数据在发给对方,则自定义处理器建议集成自ChannelInboundHandlerAdapter,因为write()方法的执行时异步的,且SimpleChannelInboundHandler中的channelRead0()方法会自定释放掉来自对方的msg。若write()方法中正在处理msg,而此时SimpleChannelInboundHandler中的channelRead0()方法执行完毕了,将msg释放了,此时就会报错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值