1.Netty简介

Netty是由JBOSS提供的一个java开源框架。
Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。


2.为什么使用Netty

虽然 JAVA NIO 框架提供了多路复用 IO 的支持,但是并没有提供上层“信息格式”的良好封装。例如前两者并没有提供针对 Protocol Buffer、JSON 这些信息格式的封装,但是 Netty 框架提供了这些数据格式封装(基于责任链模式的编
码和解码功能);

2、NIO 的类库和 API 相当复杂,使用它来开发,需要非常熟练地掌握 Selector、ByteBuffer、ServerSocketChannel、SocketChannel 等,需要很多额外的编程技能来辅助使用 NIO,例如,因为 NIO 涉及了 Reactor 线程模型,所以必须必须对多线程和网络编程非常熟悉才能写出高质量的 NIO 程序

3、要编写一个可靠的、易维护的、高性能的 NIO 服务器应用。除了框架本身要兼容实现各类操作系统的实现外。更重要的是它应该还要处理很多上层特有服务,例如:客户端的权限、还有上面提到的信息格式封装、简单的数据读取,断连重连,半包读写,心跳等等,这些 Netty 框架都提供了响应的支持。

4、JAVA NIO 框架存在一个 poll/epoll bug:Selector doesn’t block on Selector.select(timeout),不能 block 意味着 CPU 的使用率会变成 100%(这是底层 JNI 的问题,上层要处理这个异常实际上也好办)。当然这个 bug 只有在 Linux内核上才能重现


3.Netty重要组件

3.1 Channel接口

在Java 的网络编程中,其基本的构造是类 Socket。Netty 的 Channel 接口所提供的 API,被用于所有的 I/O 操作。大大地降低了直接使用 Socket类的复杂性。此外,Channel也是拥有许多预定义的、专门化实现的广泛类层次结构的根。


channel生命周期

  • ChannelUnregistered :Channel 已经被创建,但还未注册EventLoop
  • ChannelRegistered :Channel 已经被注册到了 EventLoop
  • ChannelActive :Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接收和发送数据了
  • ChannelInactive :Channel 没有连接到远程节点

当这些状态发生改变时,将会生成对应的事件。这些事件将会被转发给 ChannelPipeline中的 ChannelHandler,其可以随后对它们做出响应。


channel重要方法

1.eventLoop: 返回分配给 Channel 的 EventLoop

2.pipeline: 返回分配给 Channel 的 ChannelPipeline

3.isActive: 如果 Channel 是活动的,则返回 true。活动的意义可能依赖于底层的传输。
例如,一个 Socket 传输一旦连接到了远程节点便是活动的,而一个 Datagram 传输一旦被打开便是活动的。

4.localAddress: 返回本地的 SokcetAddress

5,remoteAddress: 返回远程的 SocketAddress

6.write: 将数据写到远程节点。这个数据将被传递给 ChannelPipeline,并且排队直到它被冲刷

7.flush: 将之前已写的数据冲刷到底层传输,如一个 Socket

8.writeAndFlush: 一个简便的方法,等同于调用 write()并接着调用 flush()


3.2 EventLoop丶EventLoopGroup

EventLoop 定义了 Netty 的核心抽象,用于处理网络连接的生命周期中所发生的事件。EventLoop充当任务调度丶线程管理丶线程分配的重要对象。
image.png

3.2.1任务调度

查看EventLoop的继承关系可看出,EventLoop具有任务调度,充当线程池的作用,一个 EventLoop 将由一个永远都不会改变的 Thread 驱动.根据配置和可用核心的不同,可能会创建多个 EventLoop 实例用以优化资源的使用,且单个 EventLoop可指派用于服务多个 Channel(处理多个网络连接)。

Netty 的 EventLoop 在继承了ScheduledExecutorService,可调度一个任务以便稍后(延迟)执行或者周期性地执行。
比如,想要注册一个在客户端已经连接了 5 分钟之后触发的任务。一个常见的做法是,发送心跳消息到远程节点,检查连接是否还活着。如果没有响应,你便知道可以关闭该 Channel了。


3.2.2线程管理

在内部,提交任务,如果(当前)调用线程正是支撑 EventLoop 的线程,那么所提交的代码块将会被(直接)执行。否则,EventLoop 将调度该任务以便稍后执行,并将它放入到内部队列中。当 EventLoop 下次处理它的事件时,它会执行队列中的那些任务/事件。
image.png


3.2.3线程分配

服务于 Channel 的 I/O 和事件的 EventLoop 则包含在 EventLoopGroup 中。

IO多路复用:在当前的线程模型中,它们可能会被多个 Channel 所共享。这使得可以通过尽可能少量的 Thread 来支撑大量的 Channel,而不是每个 Channel 分配一个 Thread。

分配EventLoop:EventLoopGroup 负责为每个新创建的 Channel 分配一个 EventLoop。在当前实现中,使用顺序循环(round-robin)的方式进行分配以获取一个均衡的分布,并且相同的 EventLoop 可能会被分配给多个 Channel。

线程安全:一旦一个 Channel 被分配给一个 EventLoop,它将在它的整个生命周期中都使用这个EventLoop(以及相关联的 Thread)。请牢记这一点,因为它可以使你从担忧你的ChannelHandler 实现中的线程安全和同步问题中解脱出来。

注意:需要注意,EventLoop对 ThreadLocal的使用的影响。因为一个 EventLoop通常会用于支撑多个 Channel.

所以对于所有相关联的 Channel 来说若使用它来实现状态追踪则会有线程安全问题。但是在一些无状态的上下文中,它仍然可以被用于在多个 Channel 之间共享一些重度的或者代价昂贵的对象,甚至是事件。


3.3 ChannelFuture 接口

Netty中所有的 I/O 操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在未来的某个时间点确定其结果的方法。因此Netty 提供了ChannelFuture 接口。

其 addListener()方法注册了一个 ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。


3.4 ChannelHandler 接口

3.4.1 ChannelHandler主要接口

对于应用程序开发人员的角度来看,Netty 的主要组件是 ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。ChannelHandler 的方法是由网络事件触发的。

image.png

对于netty来说,主要流程就是消息通讯从进入入站处理器再到出去出站处理器,即接收消息再响应消息。

入站处理器:举例来说,ChannelInboundHandler是一个经常实现的子接口。这种类型的ChannelHandler接收入站事件和数据,这些数据随后将会被你的应用程序的业务逻辑所处理。

主要方法如下:

  • channelRegistered 当 Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用
  • channelUnregistered 当 Channel 从它的 EventLoop 注销并且无法处理任何 I/O 时被调用
  • channelActive 当 Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪
  • channelInactive 当 Channel 离开活动状态并且不再连接它的远程节点时被调用
  • channelReadComplete 当 Channel 上的一个读操作完成时被调用
  • channelRead 当从 Channel 读取数据时被调用

出站处理器:当你要给连接的客户端发送响应时,也可以从 ChannelInboundHandler 直接冲刷数据然后输出到对端,即调用writeAndFlush或flush时则会经过出站处理器(常实现channelOutbountHandler子接口)

主要方法如下:

  • bind(ChannelHandlerContext,SocketAddress,ChannelPromise)当请求将 Channel 绑定到本地地址时被调用
  • connect(ChannelHandlerContext,SocketAddress,SocketAddress,ChannelPromise)当请求将 Channel 连接到远程节点时被调用
  • disconnect(ChannelHandlerContext,ChannelPromise)当请求将 Channel 从远程节点断开时被调用
  • close(ChannelHandlerContext,ChannelPromise) 当请求关闭 Channel 时被调用
  • deregister(ChannelHandlerContext,ChannelPromise)当请求将 Channel 从它的 EventLoop 注销时被调用
  • read(ChannelHandlerContext) 当请求从 Channel 读取更多的数据时被调用
  • flush(ChannelHandlerContext) 当请求通过 Channel 将入队数据冲刷到远程节点时被调用
  • write(ChannelHandlerContext,Object,ChannelPromise) 当请求通过 Channel 将数据写到远程节点时被调用。

ChannelHandler 的适配器有一些适配器类使用于自定义处理器,因为它们提供了定义在对应接口中的所有方法的默认实现。因为你有时会忽略那些不感兴趣的事件,所以 Netty 提供了抽象基类 ChannelInboundHandlerAdapter 和ChannelOutboundHandlerAdapter。


3.4.2 ChannelHandler生命周期

接口 ChannelHandler 定义的生命周期操作,在ChannelHandler被添加到ChannelPipeline中或者被从 ChannelPipeline 中移除时会调用这些操作。这些方法中的每一个都接受一个ChannelHandlerContext 参数。

  • handlerAdded: 当把 ChannelHandler 添加到 ChannelPipeline 中时被调用
  • handlerRemoved: 当从 ChannelPipeline 中移除 ChannelHandler 时被调用
  • exceptionCaught: 当处理过程中在 ChannelPipeline 中有错误产生时被调用

3.5 ChannelPipeline 接口

image.png

与channel绑定:当 Channel 被创建时,它将会被自动地分配一个新的 ChannelPipeline。这项关联是永久性的;Channel 既不能附加另外一个 ChannelPipeline,也不能分离其当前的。

ChannelHandler容器: ChannelHandler在ChannelPipeline里工作,执行顺序是由它们被添加的顺序来决定的,它们是在应用程序的初始化或者引导阶段被安装的。这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个 ChannelHandler。

入站运动:如果一个消息或者任何其他的入站事件被读取,那么它会从 ChannelPipeline 的头部开始流动,最终,数据将会到达 ChannelPipeline 的尾端,届时,所有处理就都结束了。

出站运动:数据的出站运动(即正在被写的数据)在概念上也是一样的。在这种情况下,数据将从ChannelOutboundHandler 链的尾端开始流动,直到它到达链的头部为止。在这之后,出站数据将会到达网络传输层,这里显示为 Socket。通常情况下,这将触发一个写操作。

顺序性:当两个类别的ChannelHandler都混合添加到同一个ChannelPipeline,虽然 ChannelInboundHandle 和 ChannelOutboundHandle 都扩展自 ChannelHandler,但是Netty 能区分 ChannelInboundHandler 实现和 ChannelOutboundHandler 实现,并确保数据只会在具有相同定向类型的两个 ChannelHandler 之间传递。

主要方法如下:

  • addFirst、addBefore、addAfter、addLast将一个 ChannelHandler 添加到 ChannelPipeline 中
  • remove 将一个 ChannelHandler 从 ChannelPipeline 中移除
  • replace 将 ChannelPipeline 中的一个 ChannelHandler 替换为另一个 ChannelHandler
  • get 通过类型或者名称返回 ChannelHandler
  • context 返回和 ChannelHandler 绑定的 ChannelHandlerContext
  • names 返回 ChannelPipeline 中所有 ChannelHandler 的名称

4.第一个简易netty程序

4.1 服务端

public class EchoServer  {

    private final int port;

    public EchoServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 9999;
        EchoServer echoServer = new EchoServer(port);
        System.out.println("服务器即将启动");
        echoServer.start();
        System.out.println("服务器关闭");
    }

    public void start() throws InterruptedException {
        final EchoServerHandler serverHandler = new EchoServerHandler();
        /*线程组*/
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            /*服务端启动必须*/
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)/*将线程组传入*/
                    .channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/
                    .localAddress(new InetSocketAddress(port))/*指定服务器监听端口*/
                    /*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel,
                    所以下面这段代码的作用就是为这个子channel增加handle*/
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel ch) throws Exception {
                            /*添加到该子channel的pipeline的尾部*/
                            ch.pipeline().addLast(serverHandler);
                        }
                    });
            ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/
            f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/

        } finally {
            group.shutdownGracefully().sync();/*优雅关闭线程组*/
        }

    }


}
@ChannelHandler.Sharable
/*不加这个注解那么在增加到childHandler时就必须new出来*/
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    /*客户端读到数据以后,就会执行*/
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf)msg;
        System.out.println("Server accept"+in.toString(CharsetUtil.UTF_8));
        ctx.write(in);

    }

    /*** 服务端读取完成网络数据后的处理*/
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
    }

    /*** 发生异常后的处理*/
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

4.2 客户端

public class EchoClient {

    private final int port;
    private final String host;

    public EchoClient(int port, String host) {
        this.port = port;
        this.host = host;
    }

    public void start() throws InterruptedException {
        /*线程组*/
        EventLoopGroup group = new NioEventLoopGroup();
        try{
            /*客户端启动必备*/
            Bootstrap b = new Bootstrap();
            b.group(group)/*把线程组传入*/
                    /*指定使用NIO进行网络传输*/
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host,port))
                    .handler(new EchoClientHandle());
            /*连接到远程节点,阻塞直到连接完成*/
            ChannelFuture f = b.connect().sync();
            /*阻塞程序,直到Channel发生了关闭*/
            f.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully().sync();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        new EchoClient(9999,"127.0.0.1").start();
    }
}
public class EchoClientHandle extends SimpleChannelInboundHandler<ByteBuf> {

    /*客户端读到数据以后,就会执行*/
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg)
            throws Exception {
        System.out.println("client acccept:"+msg.toString(CharsetUtil.UTF_8));
    }

    /*连接建立以后*/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer(
                "Hello Netty",CharsetUtil.UTF_8));
        //ctx.fireChannelActive();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();

        ctx.close();
    }
}

image.png

image.png

一个简易的netty程序就完成了,实现了通信功能。


y猪
246 声望25 粉丝