关于java:netty系列之channelServerChannel和netty中的实现

8次阅读

共计 9047 个字符,预计需要花费 23 分钟才能阅读完成。

简介

咱们晓得 channel 是 netty 中用于沟通 ByteBuf 和 Event 的桥梁,在 netty 服务的创立过程中,不论是客户端的 Bootstrap 还是服务器端的 ServerBootstrap,都须要调用 channel 办法来指定对应的 channel 类型。

那么 netty 中 channel 到底有哪些类型呢?他们具体是如何工作的呢?一起来看看。

channel 和 ServerChannel

Channel 在 netty 中是一个 interface,在 Channel 中定义了很多十分有用的办法。通常来说如果是客户端的话,对应的 channel 就是一般的 channel。如果是服务器端的话,对应的 channel 就应该是 ServerChannel。

那么客户端 channel 和服务器端 channel 有什么区别呢?咱们先来看下 ServerChannel 的定义:

public interface ServerChannel extends Channel {// This is a tag interface.}

能够看到 ServerChannel 继承自 Channel,示意服务端的 Channel 也是 Channel 的一种。

然而很奇怪的是,你能够看到 ServerChannel 中并没有新增任何新的办法。也就是说 ServerChannel 和 Channel 在定义上实质是一样的。你能够把 ServerChannel 看做是一个 tag interface 而已。

那么 channel 和 ServerChannel 有什么分割呢?

咱们晓得在 Channel 中定义了一个 parent 办法:

Channel parent();

这个 parent 办法返回的是该 channel 的父 channel。咱们以最简略的 LocalChannel 和 LocalServerChannel 为例,来查看一下他们的父子关系到底是怎么创立的。

首先 parent 的值是通过 LocalChannel 和 LocalServerChannel 的公共父类 AbstractChannel 来实现的:

    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();}

对于 LocalChannel 来说,能够通过它的构造函数来设置 parent channel:

    protected LocalChannel(LocalServerChannel parent, LocalChannel peer) {super(parent);
        config().setAllocator(new PreferHeapByteBufAllocator(config.getAllocator()));
        this.peer = peer;
        localAddress = parent.localAddress();
        remoteAddress = peer.localAddress();}

咱们晓得当 client 端想要连贯到 server 端的时候,须要调用 client channel 的 connect 办法,对于 LocalChannel 来说,它的 connect 办法实际上调用的是 pipeline 的 connect 办法:

public ChannelFuture connect(SocketAddress remoteAddress) {return pipeline.connect(remoteAddress);
    }

最终会调用 LocalChannel 中的 LocalUnsafe.connect 办法。

而在 LocalUnsafe.connect 办法中又会调用 serverChannel.serve 办法。

serverChannel 的 newLocalChannel 办法会创立新的 LocalChannel 并返回:

    protected LocalChannel newLocalChannel(LocalChannel peer) {return new LocalChannel(this, peer);
    }

这里应用 newLocalChannel 办法创立的 LocalChannel 就是 serverChannel 的子 channel。

最初返回的 LocalChannel 会作为 client 端 LocalChannel 的 peer channel 而存在。

netty 中 channel 的实现

在 netty 中 channel 和 Serverchannel 有很多个实现类,用来实现不同的业务性能。

为了循序渐进一步步理解 netty 中 channel 的机密,这里咱们先来探讨一下 netty 中 channel 的根本实现 LocalChannel 和 LocalServerChannel 的工作原理。

下图是 LocalChannel 和 LocalServerChannel 的次要继承和依赖关系:

<img src=”https://img-blog.csdnimg.cn/1d9c19d567084c199dfade76c8a0d52a.png” style=”zoom:67%;” />

从图中能够看到,LocalChannel 继承自 AbstractChannel 而 LocalServerChannel 则继承自 AbstractServerChannel。

因为 ServerChannel 继承自 Channel, 所以很天然的 AbstractServerChannel 又继承自 AbstractChannel。

接下来,咱们通过比照剖析 AbstractChannel 和 AbstractServerChannel,LocalChannel 和 LocalServerChannel 来一探 netty 中 channel 实现的底层原理。

AbstractChannel 和 AbstractServerChannel

AbstractChannel 是对 Channel 的最根本的实现。先来看下 AbstractChannel 中都有那些性能。

首先 AbstractChannel 中定义了 Channel 接口中要返回的一些和 channel 相干的根本属性, 包含父 channel,channel id,pipline,localAddress,remoteAddress,eventLoop 等, 如下所示:

    private final Channel parent;
    private final ChannelId id;
    private final DefaultChannelPipeline pipeline;
    private volatile SocketAddress localAddress;
    private volatile SocketAddress remoteAddress;
    private volatile EventLoop eventLoop;

    private final Unsafe unsafe;

要留神的是 AbstractChannel 中还有一个十分中要的 Unsafe 属性。

Unsafe 自身就是 Channel 接口中定义的一个外部接口, 它的作用就是为各个不同类型的 transport 提供特定的实现。

从名字能够看出 Unsafe 是一个不平安的实现,它只是在 netty 的源代码中应用,它是不能呈现在用户代码中的。或者你能够将 Unsafe 看做是底层的实现,而包裹他的 AbstractChannel 或者其余的 Channel 是对底层实现的封装,对于普通用户来说,他们只须要应用 Channel 就能够了,并不需要深刻到更底层的内容。

另外,对于 Unsafe 来说,除了上面几个办法之外,残余的办法必须从 I/O thread 中调用:

localAddress()
remoteAddress()
closeForcibly()
register(EventLoop, ChannelPromise)
deregister(ChannelPromise)
voidPromise()

和一些根本的状态相干的数据:

private volatile boolean registered;
private boolean closeInitiated;

除了根本的属性设置和读取之外,咱们 channel 中最终要的办法次要有上面几个:

  1. 用于建设服务器端服务的 bind 办法:
public ChannelFuture bind(SocketAddress localAddress) {return pipeline.bind(localAddress);
    }
  1. 用于客户端建设和服务器端连贯的 connect 办法:
public ChannelFuture connect(SocketAddress remoteAddress) {return pipeline.connect(remoteAddress);
    }
  1. 断开连接的 disconnect 办法:
public ChannelFuture disconnect() {return pipeline.disconnect();
    }
  1. 敞开 channel 的 close 办法:
public ChannelFuture close() {return pipeline.close();
    }
  1. 勾销注册的 deregister 办法:
public ChannelFuture deregister() {return pipeline.deregister();
    }
  1. 刷新数据的 flush 办法:
    public Channel flush() {pipeline.flush();
        return this;
    }
  1. 读取数据的 read 办法:
    public Channel read() {pipeline.read();
        return this;
    }
  1. 写入数据的办法:
    public ChannelFuture write(Object msg) {return pipeline.write(msg);
    }

能够看到这些 channel 中的读写和绑定工作都是由和 channel 相干的 pipeline 来执行的。

其实也很好了解,channel 只是一个通道,和数据相干的操作,还是须要在管道中执行。

咱们以 bind 办法为例子,看一下 AbstractChannel 中的 pipline 是怎么实现的。

在 AbstractChannel 中,默认的 pipeline 是 DefaultChannelPipeline, 它的 bind 办法如下:

        public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {unsafe.bind(localAddress, promise);
        }

这里的 unsafe 实际上就是 AbstractChannel 中的 unsafe,unsafe 中的 bind 办法最终会调用 AbstractChannel 中的 dobind 办法:

protected abstract void doBind(SocketAddress localAddress) throws Exception;

所以归根到底,如果是基于 AbstractChannel 的各种实现,那么只须要实现它的这些 do* 办法即可。

好了,AbstractChannel 的介绍结束了。咱们再来看一下 AbstractServerChannel。AbstractServerChannel 继承自 AbstractChannel 并且实现了 ServerChannel 接口。

public abstract class AbstractServerChannel extends AbstractChannel implements ServerChannel 

咱们晓得 ServerChannel 和 Channel 实际上是雷同的,所以 AbstractServerChannel 只是在 AbstractChannel 的实现上进行了一些调整。

在 AbstractServerChannel 中,咱们一起来察看一下 AbstractServerChannel 和 AbstractChannel 到底有什么不同。

首先是 AbstractServerChannel 的构造函数:

protected AbstractServerChannel() {super(null);
    }

构造函数中,super 的 parent channel 是 null,示意 ServerChannel 自身并不存在父 channel,这是 ServerChannel 和 client channel
的第一个不同之处。因为 server channel 能够通过 worker event loop 来承受 client channel,所以 server channel 是 client channel 的父 channel。

另外,咱们还察看几个办法的实现:

public SocketAddress remoteAddress() {return null;}

对于 ServerChannel 来说不须要被动连贯到近程的 Server,所以并没有 remoteAddress。

另外,因为断开连接是由 client 端被动调用的,所以 server channel 的 doDisconnect 会抛出不反对该操作的异样:

    protected void doDisconnect() throws Exception {throw new UnsupportedOperationException();
    }

同时 ServerChannel 只是用来负责 accept 和 client channel 建设关联关系,所以 server channel 自身并不反对向 channel 内进行的 write 操作, 所以这个 doWrite 办法也是不反对的:

    protected void doWrite(ChannelOutboundBuffer in) throws Exception {throw new UnsupportedOperationException();
    }

最初 ServerChannel 只反对 bind 操作,所以 DefaultServerUnsafe 中的 connect 办法也会抛出 UnsupportedOperationException.

LocalChannel 和 LocalServerChannel

LocalChannel 和 LocalServerChannel 是 AbstractChannel 和 AbstractServerChannel 的最根本的实现。从名字就可以看进去,这两个 Channel 是本地 channel,咱们来看一下这两个 Channel 的具体实现。

首先咱们来看一下 LocalChannel,LocalChannel 有几点对 AbstractChannel 的扩大。

第一个扩大点是 LocalChannel 中增加了 channel 的几个状态:

private enum State {OPEN, BOUND, CONNECTED, CLOSED}

通过不同的状态,能够对 channel 进行更加细粒度的管制。

另外 LocalChannel 中增加了一个十分重要的属性:

private volatile LocalChannel peer;

因为 LocalChannel 示意的是客户端 channel,所以这个 peer 示意的是 client channel 对等的 server channel。接下来咱们看一下具体的实现。

首先是 LocalChannel 的构造函数:

    protected LocalChannel(LocalServerChannel parent, LocalChannel peer) {super(parent);
        config().setAllocator(new PreferHeapByteBufAllocator(config.getAllocator()));
        this.peer = peer;
        localAddress = parent.localAddress();
        remoteAddress = peer.localAddress();}

LocalChannel 能够承受一个 LocalServerChannel 作为它的 parent,还有一个 LocalChannel 作为它的对等 channel。

那么这个 peer 是怎么创立的呢?

咱们来看一下 LocalUnsafe 中 connect 的逻辑。

            if (state != State.BOUND) {
                // Not bound yet and no localAddress specified - get one.
                if (localAddress == null) {localAddress = new LocalAddress(LocalChannel.this);
                }
            }

            if (localAddress != null) {
                try {doBind(localAddress);
                } catch (Throwable t) {safeSetFailure(promise, t);
                    close(voidPromise());
                    return;
                }
            }

首先判断以后 channel 的状态,如果是非绑定状态,那么须要进行绑定操作。首先依据传入的 LocalChannel 创立对应的 LocalAddress。

这个 LocalAddress 只是 LocalChannel 的一种表现形式,并没有什么特地的性能。

咱们来看一下这个 doBind 办法:

    protected void doBind(SocketAddress localAddress) throws Exception {
        this.localAddress =
                LocalChannelRegistry.register(this, this.localAddress,
                        localAddress);
        state = State.BOUND;
    }

LocalChannelRegistry 中保护了一个 static 的 map,这个 map 中寄存的就是注册过的 Channel.

这里注册是为了在前面不便的拿到对应的 channel。

注册好 localChannel 之后,接下来就是依据注册好的 remoteAddress 来获取对应的 LocalServerChannel, 最初调用 LocalServerChannel 的 serve 办法创立一个新的 peer channel:

Channel boundChannel = LocalChannelRegistry.get(remoteAddress);
            if (!(boundChannel instanceof LocalServerChannel)) {Exception cause = new ConnectException("connection refused:" + remoteAddress);
                safeSetFailure(promise, cause);
                close(voidPromise());
                return;
            }

            LocalServerChannel serverChannel = (LocalServerChannel) boundChannel;
            peer = serverChannel.serve(LocalChannel.this);

serve 办法首先会创立一个新的 LocalChannel:

    protected LocalChannel newLocalChannel(LocalChannel peer) {return new LocalChannel(this, peer);
    }

如果咱们把之前的 Localchannel 称为 channelA,这里创立的新的 LocalChannel 称为 channelB。那么最初的后果就是 channelA 的 peer 是 channelB,而 channelB 的 parent 是 LocalServerChannel,channelB 的 peer 是 channelA。

这样就形成了一个对等 channel 之间的关系。

接下来咱们看下 localChannel 的 read 和 write 到底是怎么工作的。

首先看一下 LocalChannel 的 doWrite 办法:

Object msg = in.current();
...
peer.inboundBuffer.add(ReferenceCountUtil.retain(msg));
in.remove();
...
finishPeerRead(peer);

首先从 ChannelOutboundBuffer 拿到要写入的 msg,将其退出 peer 的 inboundBuffer 中,最初调用 finishPeerRead 办法。

从办法名字能够看出 finishPeerRead 就是调用 peer 的 read 办法。

事实上该办法会调用 peer 的 readInbound 办法,从刚刚写入的 inboundBuffer 中读取音讯:

    private void readInbound() {RecvByteBufAllocator.Handle handle = unsafe().recvBufAllocHandle();
        handle.reset(config());
        ChannelPipeline pipeline = pipeline();
        do {Object received = inboundBuffer.poll();
            if (received == null) {break;}
            pipeline.fireChannelRead(received);
        } while (handle.continueReading());

        pipeline.fireChannelReadComplete();}

所以,对于 localChannel 来说,它的写实际上写入到 peer 的 inboundBuffer 中。而后再调用 peer 的读办法,从 inboundBuffer 中读取数据。

相较于 localChannel 来说,localServerChannel 多了一个 serve 办法,用来创立 peer channel, 并调用 readInbound 开始从 inboundBuffer 中读取数据。

总结

本章具体介绍了 channel 和 serverChannel 的区别,和他们的最简略的本地实现。心愿大家对 channel 和 serverChannel 的工作原理有了最根本的理解。

本文已收录于 http://www.flydean.com/04-2-netty-channel-vs-serverchannel-md/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

正文完
 0