简介

咱们晓得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/

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

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