java网络编程—NIO与Netty(三)

版权声明:坚持原创,坚持深入原理,未经博主允许不得转载!! https://blog.csdn.net/lemon89/article/details/77116890

上两篇文章最为Netty的预备知识,主要介绍了 IO 与 NIO的相关核心知识。
相关文章
java网络编程—NIO与Netty(四)
java网络编程—NIO与Netty(三)
java网络编程—NIO与Netty(二)
java网络编程—NIO与Netty(一)
java网络编程—基石:五种IO模型及原理(多路复用\epoll)
接下来开始重点深入解析Netty

Netty特点

  • 基于 Java NIO 的异步的和事件驱动的实现
  • Netty 也包含了一组设计模式,将应用程序逻辑从网络层解耦

Netty 实例

为了直观,请引入netty 依赖并先运行以下代码:

Netty 服务端(监听传入的连接):

public class EchoServer {
    private final int port;

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

    public static void main(String[] args) throws Exception {
        new EchoServer(9099).start();
    }

    public void start() throws Exception {
        final EchoServerHandler serverHandler = new EchoServerHandler();
        // 1.创建EventLoopGroup:指定了 NioEventLoopGroup来接受和处理新的连接
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();// 2.创建ServerBootstrap
            // 3.指定所使用的NIO传输 Channel,并且指定类型。源码中每次会通过ChannelFactory工厂创建channel,其实就是根据这个class了下反射创建对象。
            b.group(group).channel(NioServerSocketChannel.class)
                    // 4.使用指定的 端口设置套 接字地址:服务器将绑定到这个地址以监听新的连接请求。
                    .localAddress(new InetSocketAddress(port))
                    // 5.添加一个 EchoServerHandler 到子 Channel 的 ChannelPipeline
                    // EchoServerHandler 是@Shareable,所 以我们可以总是使用 同样的实例
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        // ChannelInitializer。这是关键。当一个新的连接
                        // 被接受时,一个新的子 Channel 将会被创建,而 ChannelInitializer 将会把一个你的
                        // EchoServerHandler 的实例添加到该 Channel 的 ChannelPipeline 中
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(serverHandler);
                        }
                    });

            // 6.异步地绑定服务器; 调用 sync()方法阻塞 等待直到绑定完成
            ChannelFuture f = b.bind().sync();
            System.out.println(EchoServer.class.getName() + " started and listening for connections on "
                    + f.channel().localAddress());
            f.channel().closeFuture().sync();// 7.获取这个channel的closeFuture,并且阻塞当前线程直到它完成channel关闭
        } finally {
            group.shutdownGracefully().sync();// 8.关闭 EventLoopGroup,释放所有的资源

        }
    }

    @Sharable // 表示一个channelhandler可以被多个channel线程安全的共享
    static class EchoServerHandler extends ChannelInboundHandlerAdapter {
        // 对于每个传入的消息都要调用
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            ByteBuf in = (ByteBuf) msg;
            System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
            // 将接收到的消息 写给发送者,而 不冲刷出站消息.异步
            ctx.write(in);
        }

        // 通知ChannelInboundHandler最后一次对channelRead()的调用是当前批量读取中的最后一条消息
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            // 将未决消息冲刷到远程节点,并且关闭该 Channel
            ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }

        // 在读取操作期间, 有异常抛出时会调用
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }
}

Server 实现了业务逻辑:
main()方法引导了服务器。引导过程中所需要的步骤如下

  • 创建一个 ServerBootstrap 的实例以引导和绑定服务器;
  • 创建并分配一个 NioEventLoopGroup 实例以进行事件的处理,如接受新连接以及读/写数据;
  • 指定服务器绑定的本地的 InetSocketAddress;
  • 使用一个 EchoServerHandler 的实例初始化每一个新的 Channel;
  • 调用 ServerBootstrap.bind()方法以绑定服务器。

Client 客户端(建立到一个或者多个进程的连接)

public class EchoClient {
    private final String host;

    private final int port;

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

    public void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler());

                        }
                    });
            ChannelFuture f = b.connect().sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws Exception {
        new EchoClient("localhost", 9099).start();
    }

    @Sharable
    static class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
        // —在到服务器的连接已经建立之后将被调用
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
        }

        // 当从服务器接收到一条消息时被调用;
        @Override
        public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
            System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
        }

        // 在处理过程中引发异常时被调用。
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }
}
  • 为初始化客户端, 创建了一个 Bootstrap 实例;
  • 为进行事件处理分配了一个 NioEventLoopGroup 实例, 其中事件处理包括创建新的
    连接以及处理入站和出站数据;
  • 为服务器连接创建了一个 InetSocketAddress 实例;
  • 当连接被建立时,一个 EchoClientHandler 实例会被安装到(该 Channel 的)
    ChannelPipeline 中;
  • 在一切都设置完成后,调用 Bootstrap.connect()方法连接到远程节点;

Netty核心抽象理解

Channel

注意:并非JDK源码中的channel。
能够执行 IO操作(write\read)的连接(Connection),比如文件读写连接通道、Socket等,在网络编程中通常可以理解为异步IO操作的Socket

Channel接口中定义的基本的 I/O 操作(bind()、 connect()、 read()和 write())依赖于底层网络传输所提供的原语,通过Channel接口中定义的API 降低了直接使用 Socket 类的复杂性

  • EmbeddedChannel;
  • LocalServerChannel;
  • NioDatagramChannel;
  • NioSctpChannel;
  • NioSocketChannel。

结合前文Server创建的例子:b.group(group).channel(NioServerSocketChannel.class)
源码中每次会通过ChannelFactory工厂创建channel,其实就是根据这个class了下反射创建对象。

ChannelFuture(异步通知)与ChannelFutureListener(监听回调)

异步的(channel)IO操作的结果,是JDK中Future的子类。

在Netty中所有的IO操作都是异步的,每个 Netty 的出站 I/O 操作都将返回一个 ChannelFuture。ChannelFuture通过注册ChannelFutureListener监听器的方式在这个channelfuture对应的操作执行完成时触发响应的动作。

@Override
    ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> listener);

    @Override
    ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);

    @Override
    ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> listener);
//示例:
ChannelFuture channelFuture  = b.connect();
            channelFuture.addListener(new ChannelFutureListener() {

                /*这个ChannelFuture f执行完成的时候,另外一个线程会执行operationComplete逻辑*/
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    System.out.println("isDone" + future.isDone());
                    System.out.println("isSuccess" + future.isSuccess());
                    System.out.println("this Channel Future " + future);
                }
            });

另外ChannelPromise是ChannelFuture的子类,通过增加了几个set方法实现可写功能。可以理解为可写入版本的ChannelFuture.

/**
 * Special {@link Future} which is writable.
 */
public interface Promise<V> extends Future<V> {
    /**
     * Marks this future as a success and notifies all
     * listeners.
     *
     * If it is success or failed already it will throw an {@link IllegalStateException}.
     */
    Promise<V> setSuccess(V result);
    /**
     * Marks this future as a success and notifies all
     * listeners.
     *
     * @return {@code true} if and only if successfully marked this future as
     *         a success. Otherwise {@code false} because this future is
     *         already marked as either a success or a failure.
     */
    boolean trySuccess(V result);

    /**
     * Marks this future as a failure and notifies all
     * listeners.
     *
     * If it is success or failed already it will throw an {@link IllegalStateException}.
     */
    Promise<V> setFailure(Throwable cause);
    /**
     * Marks this future as a failure and notifies all
     * listeners.
     *
     * @return {@code true} if and only if successfully marked this future as
     *         a failure. Otherwise {@code false} because this future is
     *         already marked as either a success or a failure.
     */
    boolean tryFailure(Throwable cause);
    /**
     * Make this future impossible to cancel.
     *
     * @return {@code true} if and only if successfully marked this future as uncancellable or it is already done
     *         without being cancelled.  {@code false} if this future has been cancelled already.
     */
    boolean setUncancellable();
............

事件驱动

Netty通过各种不同的事件来告诉我们某些状态的改变、操作的执行情况等,我们针对各种不同的事件去执行相应的动作。

所谓入站(Inbound)是指数据是从远端流向本地用户程序,出站(Outbound)则反之。

入站事件

  • 连接成功或者连接失效
  • 读取数据
  • 用户事件
  • 错误处理事件

出站事件

  • 打开或者关闭到远程节点的连接
  • 将数据写到或者冲刷到套接字
  • 用户事件
  • 错误处理事件

EventLoop 控制流、多线程处理、并发

在内部,会为每个channel分配一个EventLoop,用于处理所有事件,比如:
注册感兴趣的事件;
将事件派发给 ChannelHandler;
安排进一步的动作

每个EventLoop都由一个独立的线程驱动,用来处理一个channel的所有IO事件。

一个EventLoop通常可以处理多个channel

eventLoop-channel 图

- 一个 EventLoopGroup 包含一个或者多个 EventLoop;
- 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
- 一个 Channel 在它的生命周期内只注册于一个 EventLoop;
- 一个 EventLoop 可能会被分配给一个或多个 Channel
- 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;

通常 ChannelPipeline 中的每一个 ChannelHandler 都是通过它的 EventLoop(I/O 线程)来处
理传递给它的事件的。所以至关重要的是不要阻塞这个线程,因为这会对整体的 I/O 处理产生负面的影响

ChannelHandler 与 ChannelPipeLine与ChannelHandlerContext

ChannelHandler是一个接口族的父接口,用来接受并响应事件,netty中所有的数据处理都在channelhandler中完成。
每个channel都有个一个与之关联的channelPipeLine,这个channelPipeLine中持有一个ChannelHandler实例链。
Channelhandler充当了所有处理入站和出站数据的应用程序逻辑的容器,而ChannelPipeLine又是ChannelHandler的容器。

这里写图片描述

channel.pipeline().addLast(serverHandler)//添加一个**Handler 到子Channel的 ChannelPipeline

ChannelPipeline 提供了 ChannelHandler 链的容器,持有所有将应用于入站和出站数据以及事件的 ChannelHandler 实例

当 Channel 被创建时,它会被自动地分配到它专属的 ChannelPipeline。
当 ChannelHandler 被添加到 ChannelPipeline 时,它将会被分配一个 ChannelHandlerContext其代表了 ChannelHandler 和 ChannelPipeline 之间的绑定

一个消息或者其他入站事件发生,这个事件会从channelPipeline的头部开始,发送给头部的channelInboundHandler,直到最末端的channelInboundHandler,到达channlPiepeline的尾部。

出站事件发送时,这个事件会从channelPipeline的尾部开始,发送给ChannelOutboundHandler直达outBoundHander链条的头部。最终出站事件的数据会到达传输层(TCP)。

两种发送消息的方式。你可以直接写到 Channel 中,也可以 写到和 ChannelHandler相关联的ChannelHandlerContext对象中。
前一种方式将会导致消息从ChannelPipeline 的尾端开始流动,
而后者将导致消息从 ChannelPipeline 中的下一个 ChannelHandler 开始流动。

这里写图片描述
ChannelPipeline中的每个ChannelHandler将负责把事件转发到链中的下一个 ChannelHandler。

猜你喜欢

转载自blog.csdn.net/lemon89/article/details/77116890