共计 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 中最终要的办法次要有上面几个:
- 用于建设服务器端服务的 bind 办法:
public ChannelFuture bind(SocketAddress localAddress) {return pipeline.bind(localAddress);
}
- 用于客户端建设和服务器端连贯的 connect 办法:
public ChannelFuture connect(SocketAddress remoteAddress) {return pipeline.connect(remoteAddress);
}
- 断开连接的 disconnect 办法:
public ChannelFuture disconnect() {return pipeline.disconnect();
}
- 敞开 channel 的 close 办法:
public ChannelFuture close() {return pipeline.close();
}
- 勾销注册的 deregister 办法:
public ChannelFuture deregister() {return pipeline.deregister();
}
- 刷新数据的 flush 办法:
public Channel flush() {pipeline.flush();
return this;
}
- 读取数据的 read 办法:
public Channel read() {pipeline.read();
return this;
}
- 写入数据的办法:
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/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!