From 55a15afbf771b2e706163caa159d3e4ea65d6ef0 Mon Sep 17 00:00:00 2001 From: AmyliaY <471816751@qq.com> Date: Tue, 1 Sep 2020 22:36:54 +0800 Subject: [PATCH] =?UTF-8?q?Netty=E7=9A=84Channel=E5=92=8CUnsafe=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=20=E6=BA=90=E7=A0=81=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 - .../Channel和Unsafe组件.md | 955 +++++++++++++++++- images/Netty/Netty的Channel组件.png | Bin 0 -> 15329 bytes 3 files changed, 954 insertions(+), 4 deletions(-) create mode 100644 images/Netty/Netty的Channel组件.png diff --git a/README.md b/README.md index 7396c18..5c2d1e9 100644 --- a/README.md +++ b/README.md @@ -138,9 +138,6 @@ ### Netty 粘拆包及解决方案 * [TCP粘拆包问题及Netty中的解决方案](docs/Netty/TCP粘拆包/TCP粘拆包问题及Netty中的解决方案.md) -### Netty 编解码 -* [Java序列化缺点与主流编解码框架](docs/Netty/Netty编解码/Java序列化缺点与主流编解码框架.md) - ### Netty 多协议开发 * [基于HTTP协议的Netty开发](docs/Netty/Netty多协议开发/基于HTTP协议的Netty开发.md) * [基于WebSocket协议的Netty开发](docs/Netty/Netty多协议开发/基于WebSocket协议的Netty开发.md) diff --git a/docs/Netty/Netty主要组件源码分析/Channel和Unsafe组件.md b/docs/Netty/Netty主要组件源码分析/Channel和Unsafe组件.md index fcb5dbe..a6c8652 100644 --- a/docs/Netty/Netty主要组件源码分析/Channel和Unsafe组件.md +++ b/docs/Netty/Netty主要组件源码分析/Channel和Unsafe组件.md @@ -1 +1,954 @@ -努力编写中... \ No newline at end of file +类似于 java.nio包 的 Channel,Netty 提供了自己的 Channel 和其子类实现,用于异步 I/O操作 等。Unsafe 是 Channel 的内部接口,聚合在 Channel 中协助进行网络读写相关的操作,因为它的设计初衷就是 Channel 的内部辅助类,不应该被 Netty框架 的上层使用者调用,所以被命名为 Unsafe。 + +## Channel 组件 +Netty 的 **Channel组件 是 Netty 对网络操作的封装**,**如 网络数据的读写,与客户端建立连接**,主动关闭连接 等,也包含了 Netty框架 相关的一些功能,如 获取该 Chanel 的 **EventLoop、ChannelPipeline** 等。另外,Netty 并没有直接使用 java.nio包 的 SocketChannel和ServerSocketChannel,而是**使用 NioSocketChannel和NioServerSocketChannel 对其进行了进一步的封装**。下面我们先从 Channel接口 的API开始分析,然后看一下其重要子类的源码实现。 + +为了便于后面的阅读源码,我们先看下 NioSocketChannel 和 NioServerSocketChannel 的继承关系类图。 +![在这里插入图片描述](../../../images/Netty/Netty的Channel组件.png) +#### Channel 接口 +```java +public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable { + + /** + * Channel 需要注册到 EventLoop 的多路复用器上,用于处理 I/O事件, + * EventLoop 实际上就是处理网络读写事件的 Reactor线程。 + */ + EventLoop eventLoop(); + + /** + * ChannelMetadata 封装了 TCP参数配置 + */ + ChannelMetadata metadata(); + + /** + * 对于服务端Channel而言,它的父Channel为空; + * 对于客户端Channel,它的 父Channel 就是创建它的 ServerSocketChannel + */ + Channel parent(); + + /** + * 每个 Channel 都有一个全局唯一标识 + */ + ChannelId id(); + + /** + * 获取当前 Channel 的配置信息,如 CONNECT_TIMEOUT_MILLIS + */ + ChannelConfig config(); + + /** + * 当前 Channel 是否已经打开 + */ + boolean isOpen(); + + /** + * 当前 Channel 是否已注册进 EventLoop + */ + boolean isRegistered(); + + /** + * 当前 Channel 是否已激活 + */ + boolean isActive(); + + /** + * 当前 Channel 的本地绑定地址 + */ + SocketAddress localAddress(); + + /** + * 当前 Channel 的远程绑定地址 + */ + SocketAddress remoteAddress(); + + /** + * 当前 Channel 是否可写 + */ + boolean isWritable(); + + /** + * 当前 Channel 内部的 Unsafe对象 + */ + Unsafe unsafe(); + + /** + * 当前 Channel 持有的 ChannelPipeline + */ + ChannelPipeline pipeline(); + + /** + * 从当前 Channel 中读取数据到第一个 inbound缓冲区 中,如果数据被成功读取, + * 触发ChannelHandler.channelRead(ChannelHandlerContext,Object)事件。 + * 读取操作API调用完成之后,紧接着会触发ChannelHandler.channelReadComplete(ChannelHandlerContext)事件, + * 这样业务的ChannelHandler可以决定是否需要继续读取数据。如果己经有读操作请求被挂起,则后续的读操作会被忽略。 + */ + @Override + Channel read(); + + /** + * 将之前写入到发送环形数组中的消息全部写入到目标Chanel中,发送给通信对方 + */ + @Override + Channel flush(); +} +``` + +#### AbstractChannel +```java +public abstract class AbstractChannel extends DefaultAttributeMap implements Channel { + + // 父Channel + private final Channel parent; + // Channel的全局唯一标识 + private final ChannelId id; + // 内部辅助类 Unsafe + private final Unsafe unsafe; + // Netty 会为每一个 channel 创建一个 pipeline + private final DefaultChannelPipeline pipeline; + + // 本地地址 + private volatile SocketAddress localAddress; + // 远程主机地址 + private volatile SocketAddress remoteAddress; + // 注册到了哪个 EventLoop 上 + private volatile EventLoop eventLoop; + // 是否已注册 + private volatile boolean registered; + + /** + * channnel 会将 网络IO操作 触发到 ChannelPipeline 对应的事件方法。 + * Netty 基于事件驱动,我们也可以理解为当 Chnanel 进行 IO操作 时会产生对应的IO 事件, + * 然后驱动事件在 ChannelPipeline 中传播,由对应的 ChannelHandler 对事件进行拦截和处理, + * 不关心的事件可以直接忽略 + */ + @Override + public ChannelFuture bind(SocketAddress localAddress) { + return pipeline.bind(localAddress); + } + + @Override + public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { + return pipeline.bind(localAddress, promise); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress) { + return pipeline.connect(remoteAddress); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { + return pipeline.connect(remoteAddress, localAddress); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { + return pipeline.connect(remoteAddress, promise); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { + return pipeline.connect(remoteAddress, localAddress, promise); + } + + @Override + public ChannelFuture disconnect() { + return pipeline.disconnect(); + } + + @Override + public ChannelFuture disconnect(ChannelPromise promise) { + return pipeline.disconnect(promise); + } + + @Override + public ChannelFuture close() { + return pipeline.close(); + } + + @Override + public ChannelFuture close(ChannelPromise promise) { + return pipeline.close(promise); + } + + @Override + public ChannelFuture deregister() { + return pipeline.deregister(); + } + + @Override + public ChannelFuture deregister(ChannelPromise promise) { + return pipeline.deregister(promise); + } + + @Override + public Channel flush() { + pipeline.flush(); + return this; + } + + @Override + public Channel read() { + pipeline.read(); + return this; + } + + @Override + public ChannelFuture write(Object msg) { + return pipeline.write(msg); + } + + @Override + public ChannelFuture write(Object msg, ChannelPromise promise) { + return pipeline.write(msg, promise); + } + + @Override + public ChannelFuture writeAndFlush(Object msg) { + return pipeline.writeAndFlush(msg); + } + + @Override + public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { + return pipeline.writeAndFlush(msg, promise); + } +} +``` + +#### AbstractNioChannel + +```java +public abstract class AbstractNioChannel extends AbstractChannel { + + // AbstractNioChannel 是 NioSocketChannel和NioServerSocketChannel 的公共父类,所以定义 + // 了一个 java.nio 的 SocketChannel 和 ServerSocketChannel 的公共父类 SelectableChannel, + // 用于设置 SelectableChannel参数 和进行 IO操作 + private final SelectableChannel ch; + // 它代表了 JDK 的 SelectionKey.OP_READ + protected final int readInterestOp; + // 该 SelectionKey 是 Channel 注册到 EventLoop 后返回的, + // 由于 Channel 会面临多个业务线程的并发写操作,当 SelectionKey 被修改了, + // 需要让其他业务线程感知到变化,所以使用volatile保证修改的可见性 + volatile SelectionKey selectionKey; + + /** + * Channel 的注册 + */ + @Override + protected void doRegister() throws Exception { + boolean selected = false; + for (;;) { + try { + selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this); + return; + } catch (CancelledKeyException e) { + if (!selected) { + // Force the Selector to select now as the "canceled" SelectionKey may still be + // cached and not removed because no Select.select(..) operation was called yet. + eventLoop().selectNow(); + selected = true; + } else { + // We forced a select operation on the selector before but the SelectionKey is still cached + // for whatever reason. JDK bug ? + throw e; + } + } + } + } + + protected SelectableChannel javaChannel() { + return ch; + } + + @Override + protected void doBeginRead() throws Exception { + // Channel.read() 或 ChannelHandlerContext.read() 被调用 + final SelectionKey selectionKey = this.selectionKey; + if (!selectionKey.isValid()) { + return; + } + + readPending = true; + + final int interestOps = selectionKey.interestOps(); + if ((interestOps & readInterestOp) == 0) { + selectionKey.interestOps(interestOps | readInterestOp); + } + } +} +``` + +#### NioServerSocketChannel +```java +public class NioServerSocketChannel extends AbstractNioMessageChannel + implements io.netty.channel.socket.ServerSocketChannel { + + // java.nio 包的内容,用于获取 java.nio.channels.ServerSocketChannel 实例 + private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider(); + + private static ServerSocketChannel newSocket(SelectorProvider provider) { + try { + /** + * 获取的是 java.nio.channels.ServerSocketChannel 实例 + */ + return provider.openServerSocketChannel(); + } catch (IOException e) { + throw new ChannelException("Failed to open a server socket.", e); + } + } + + /** + * Create a new instance + */ + public NioServerSocketChannel() { + this(newSocket(DEFAULT_SELECTOR_PROVIDER)); + } + + /** + * 在父类中完成了 非阻塞IO的配置,及事件的注册 + */ + public NioServerSocketChannel(ServerSocketChannel channel) { + super(null, channel, SelectionKey.OP_ACCEPT); + config = new NioServerSocketChannelConfig(this, javaChannel().socket()); + } + + /** + * 对 NioServerSocketChannel 来说,它的读取操作就是接收客户端的连接,创建 NioSocketChannel对象 + */ + @Override + protected int doReadMessages(List buf) throws Exception { + // 首先通过 ServerSocketChannel 的 accept()方法 接收新的客户端连接, + // 获取 java.nio.channels.SocketChannel 对象 + SocketChannel ch = SocketUtils.accept(javaChannel()); + + try { + // 如果获取到客户端连接对象 SocketChannel,则利用当前的 NioServerSocketChannel、EventLoop + // 和 SocketChannel 创建新的 NioSocketChannel,并添加到 buf 中 + if (ch != null) { + buf.add(new NioSocketChannel(this, ch)); + return 1; + } + } catch (Throwable t) { + logger.warn("Failed to create a new channel from an accepted socket.", t); + + try { + ch.close(); + } catch (Throwable t2) { + logger.warn("Failed to close a socket.", t2); + } + } + + return 0; + } +} +``` + + +#### NioSocketChannel +```java +public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel { + + // 与 NioServerSocketChannel 一样,也依赖了 java.nio包 的API + private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider(); + + /** + * 从这里可以看出,NioSocketChannel 对 java.nio.channels.SocketChannel 做了进一步封装 + * 使其 适用于 Netty框架 + */ + private static SocketChannel newSocket(SelectorProvider provider) { + try { + return provider.openSocketChannel(); + } catch (IOException e) { + throw new ChannelException("Failed to open a socket.", e); + } + } + + /** + * Create a new instance + */ + public NioSocketChannel() { + this(DEFAULT_SELECTOR_PROVIDER); + } + + public NioSocketChannel(SelectorProvider provider) { + this(newSocket(provider)); + } + + public NioSocketChannel(SocketChannel socket) { + this(null, socket); + } + + public NioSocketChannel(Channel parent, SocketChannel socket) { + // 在父类中完成 非阻塞IO的配置,注册事件 + super(parent, socket); + config = new NioSocketChannelConfig(this, socket.socket()); + } + + @Override + protected SocketChannel javaChannel() { + return (SocketChannel) super.javaChannel(); + } + + @Override + public boolean isActive() { + SocketChannel ch = javaChannel(); + return ch.isOpen() && ch.isConnected(); + } + + /** + * 与远程服务器建立连接 + */ + @Override + protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + if (localAddress != null) { + doBind0(localAddress); + } + + boolean success = false; + try { + // 根据远程地址建立TCP连接,对连接结果进行判断 + boolean connected = SocketUtils.connect(javaChannel(), remoteAddress); + if (!connected) { + selectionKey().interestOps(SelectionKey.OP_CONNECT); + } + success = true; + return connected; + } finally { + if (!success) { + doClose(); + } + } + } + + /** + * 关闭 Channel + */ + @Override + protected void doClose() throws Exception { + super.doClose(); + javaChannel().close(); + } + + /** + * 从 Channel 中读取数据 + */ + @Override + protected int doReadBytes(ByteBuf byteBuf) throws Exception { + final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle(); + allocHandle.attemptedBytesRead(byteBuf.writableBytes()); + return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead()); + } + + @Override + protected int doWriteBytes(ByteBuf buf) throws Exception { + final int expectedWrittenBytes = buf.readableBytes(); + return buf.readBytes(javaChannel(), expectedWrittenBytes); + } + + /** + * 向 Channel 中写数据 + */ + @Override + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + SocketChannel ch = javaChannel(); + int writeSpinCount = config().getWriteSpinCount(); + do { + if (in.isEmpty()) { + // All written so clear OP_WRITE + clearOpWrite(); + // Directly return here so incompleteWrite(...) is not called. + return; + } + + // Ensure the pending writes are made of ByteBufs only. + int maxBytesPerGatheringWrite = ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite(); + ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite); + int nioBufferCnt = in.nioBufferCount(); + + // Always us nioBuffers() to workaround data-corruption. + // See https://github.com/netty/netty/issues/2761 + switch (nioBufferCnt) { + case 0: + // We have something else beside ByteBuffers to write so fallback to normal writes. + writeSpinCount -= doWrite0(in); + break; + case 1: { + // Only one ByteBuf so use non-gathering write + // Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need + // to check if the total size of all the buffers is non-zero. + ByteBuffer buffer = nioBuffers[0]; + int attemptedBytes = buffer.remaining(); + final int localWrittenBytes = ch.write(buffer); + if (localWrittenBytes <= 0) { + incompleteWrite(true); + return; + } + adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite); + in.removeBytes(localWrittenBytes); + --writeSpinCount; + break; + } + default: { + // Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need + // to check if the total size of all the buffers is non-zero. + // We limit the max amount to int above so cast is safe + long attemptedBytes = in.nioBufferSize(); + final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt); + if (localWrittenBytes <= 0) { + incompleteWrite(true); + return; + } + // Casting to int is safe because we limit the total amount of data in the nioBuffers to int above. + adjustMaxBytesPerGatheringWrite((int) attemptedBytes, (int) localWrittenBytes, + maxBytesPerGatheringWrite); + in.removeBytes(localWrittenBytes); + --writeSpinCount; + break; + } + } + } while (writeSpinCount > 0); + + incompleteWrite(writeSpinCount < 0); + } +} +``` + +## Unsafe 功能简介 +Unsafe接口 实际上是 **Channel接口 的辅助接口**,它不应该被用户代码直接调用。**实际的 IO读写操作 都是由 Unsafe接口 负责完成的**。 + +```java +public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable { + + interface Unsafe { + + /** + * 返回绑定的 本地地址 + */ + SocketAddress localAddress(); + + /** + * 返回绑定的 远程地址 + */ + SocketAddress remoteAddress(); + + /** + * 将 Channel 注册到 EventLoop 上 + */ + void register(EventLoop eventLoop, ChannelPromise promise); + + /** + * 绑定 本地地址 到 Channel 上 + */ + void bind(SocketAddress localAddress, ChannelPromise promise); + + /** + * 连接到远程服务器 + */ + void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise); + + /** + * 断开连接 + */ + void disconnect(ChannelPromise promise); + + /** + * 关闭 Channel + */ + void close(ChannelPromise promise); + + /** + * 读就绪 网络事件 + */ + void beginRead(); + + /** + * 发送数据 + */ + void write(Object msg, ChannelPromise promise); + + /** + * 将缓冲区的数据 刷到 Channel + */ + void flush(); + } +} +``` + +#### AbstractUnsafe +```java +public abstract class AbstractChannel extends DefaultAttributeMap implements Channel { + + protected abstract class AbstractUnsafe implements Unsafe { + /** + * 将当前 Unsafe 对应的 Channel 注册到 EventLoop 的多路复用器上, + * 然后调用 DefaultChannelPipeline 的 fireChannelRegistered()方法, + * 如果 Channel 被激活 则调用 DefaultChannelPipeline 的 fireChannelActive()方法 + */ + @Override + public final void register(EventLoop eventLoop, final ChannelPromise promise) { + if (eventLoop == null) { + throw new NullPointerException("eventLoop"); + } + if (isRegistered()) { + promise.setFailure(new IllegalStateException("registered to an event loop already")); + return; + } + if (!isCompatible(eventLoop)) { + promise.setFailure( + new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName())); + return; + } + + AbstractChannel.this.eventLoop = eventLoop; + + if (eventLoop.inEventLoop()) { + register0(promise); + } else { + try { + eventLoop.execute(new Runnable() { + @Override + public void run() { + register0(promise); + } + }); + } catch (Throwable t) { + logger.warn( + "Force-closing a channel whose registration task was not accepted by an event loop: {}", + AbstractChannel.this, t); + closeForcibly(); + closeFuture.setClosed(); + safeSetFailure(promise, t); + } + } + } + + private void register0(ChannelPromise promise) { + try { + // check if the channel is still open as it could be closed in the mean time when the register + // call was outside of the eventLoop + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + boolean firstRegistration = neverRegistered; + doRegister(); + neverRegistered = false; + registered = true; + + // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the + // user may already fire events through the pipeline in the ChannelFutureListener. + pipeline.invokeHandlerAddedIfNeeded(); + + safeSetSuccess(promise); + pipeline.fireChannelRegistered(); + // Only fire a channelActive if the channel has never been registered. This prevents firing + // multiple channel actives if the channel is deregistered and re-registered. + if (isActive()) { + if (firstRegistration) { + pipeline.fireChannelActive(); + } else if (config().isAutoRead()) { + // This channel was registered before and autoRead() is set. This means we need to begin read + // again so that we process inbound data. + // + // See https://github.com/netty/netty/issues/4805 + beginRead(); + } + } + } catch (Throwable t) { + // Close the channel directly to avoid FD leak. + closeForcibly(); + closeFuture.setClosed(); + safeSetFailure(promise, t); + } + } + + /** + * 绑定指定的端口,对于服务端 用于绑定监听端口, + * 对于客户端,主要用于指定 客户端Channel 的本地绑定Socket地址。 + */ + @Override + public final void bind(final SocketAddress localAddress, final ChannelPromise promise) { + assertEventLoop(); + + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + + // See: https://github.com/netty/netty/issues/576 + if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) && + localAddress instanceof InetSocketAddress && + !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() && + !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) { + // Warn a user about the fact that a non-root user can't receive a + // broadcast packet on *nix if the socket is bound on non-wildcard address. + logger.warn( + "A non-root user can't receive a broadcast packet if the socket " + + "is not bound to a wildcard address; binding to a non-wildcard " + + "address (" + localAddress + ") anyway as requested."); + } + + boolean wasActive = isActive(); + try { + doBind(localAddress); + } catch (Throwable t) { + safeSetFailure(promise, t); + closeIfClosed(); + return; + } + + if (!wasActive && isActive()) { + invokeLater(new Runnable() { + @Override + public void run() { + pipeline.fireChannelActive(); + } + }); + } + + safeSetSuccess(promise); + } + + /** + * 客户端 或 服务端,主动关闭连接 + */ + @Override + public final void disconnect(final ChannelPromise promise) { + assertEventLoop(); + + if (!promise.setUncancellable()) { + return; + } + + boolean wasActive = isActive(); + try { + doDisconnect(); + } catch (Throwable t) { + safeSetFailure(promise, t); + closeIfClosed(); + return; + } + + if (wasActive && !isActive()) { + invokeLater(new Runnable() { + @Override + public void run() { + pipeline.fireChannelInactive(); + } + }); + } + + safeSetSuccess(promise); + closeIfClosed(); // doDisconnect() might have closed the channel + } + + /** + * 在链路关闭之前需要首先判断是否处于刷新状态,如果处于刷新状态说明还有消息尚 + * 未发送出去,需要等到所有消息发送完成再关闭链路,因此,将关闭操作封装成Runnable稍后再执行 + */ + @Override + public final void close(final ChannelPromise promise) { + assertEventLoop(); + + close(promise, CLOSE_CLOSED_CHANNEL_EXCEPTION, CLOSE_CLOSED_CHANNEL_EXCEPTION, false); + } + + /** + * 本方法实际上将消息添加到环形发送数组中,并不是真正的写Channel + */ + @Override + public final void write(Object msg, ChannelPromise promise) { + assertEventLoop(); + + ChannelOutboundBuffer outboundBuffer = this.outboundBuffer; + if (outboundBuffer == null) { + // If the outboundBuffer is null we know the channel was closed and so + // need to fail the future right away. If it is not null the handling of the rest + // will be done in flush0() + // See https://github.com/netty/netty/issues/2362 + safeSetFailure(promise, newWriteException(initialCloseCause)); + // release message now to prevent resource-leak + ReferenceCountUtil.release(msg); + return; + } + + int size; + try { + msg = filterOutboundMessage(msg); + size = pipeline.estimatorHandle().size(msg); + if (size < 0) { + size = 0; + } + } catch (Throwable t) { + safeSetFailure(promise, t); + ReferenceCountUtil.release(msg); + return; + } + + outboundBuffer.addMessage(msg, size, promise); + } + + /** + * 将缓冲区中待发送的消息全部写入 Channel,并发送给通信对方 + */ + @Override + public final void flush() { + assertEventLoop(); + + ChannelOutboundBuffer outboundBuffer = this.outboundBuffer; + if (outboundBuffer == null) { + return; + } + + outboundBuffer.addFlush(); + flush0(); + } + + @SuppressWarnings("deprecation") + protected void flush0() { + if (inFlush0) { + // Avoid re-entrance + return; + } + + final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer; + if (outboundBuffer == null || outboundBuffer.isEmpty()) { + return; + } + + inFlush0 = true; + + // Mark all pending write requests as failure if the channel is inactive. + if (!isActive()) { + try { + if (isOpen()) { + outboundBuffer.failFlushed(FLUSH0_NOT_YET_CONNECTED_EXCEPTION, true); + } else { + // Do not trigger channelWritabilityChanged because the channel is closed already. + outboundBuffer.failFlushed(newFlush0Exception(initialCloseCause), false); + } + } finally { + inFlush0 = false; + } + return; + } + + try { + doWrite(outboundBuffer); + } catch (Throwable t) { + if (t instanceof IOException && config().isAutoClose()) { + /** + * Just call {@link #close(ChannelPromise, Throwable, boolean)} here which will take care of + * failing all flushed messages and also ensure the actual close of the underlying transport + * will happen before the promises are notified. + * + * This is needed as otherwise {@link #isActive()} , {@link #isOpen()} and {@link #isWritable()} + * may still return {@code true} even if the channel should be closed as result of the exception. + */ + initialCloseCause = t; + close(voidPromise(), t, newFlush0Exception(t), false); + } else { + try { + shutdownOutput(voidPromise(), t); + } catch (Throwable t2) { + initialCloseCause = t; + close(voidPromise(), t2, newFlush0Exception(t), false); + } + } + } finally { + inFlush0 = false; + } + } + } +} +``` + +#### AbstractNioUnsafe +AbstractNioUnsafe 是 AbstractUnsafe类 的 NIO实现,它主要实现了 connect 、finishConnect 等方法。 +```java +public abstract class AbstractNioChannel extends AbstractChannel { + + /** + * 获取当前的连接状态进行缓存,然后发起连接操作。 + */ + @Override + public final void connect( + final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + + try { + if (connectPromise != null) { + // Already a connect in process. + throw new ConnectionPendingException(); + } + + boolean wasActive = isActive(); + if (doConnect(remoteAddress, localAddress)) { + fulfillConnectPromise(promise, wasActive); + } else { + connectPromise = promise; + requestedRemoteAddress = remoteAddress; + + // Schedule connect timeout. + int connectTimeoutMillis = config().getConnectTimeoutMillis(); + if (connectTimeoutMillis > 0) { + connectTimeoutFuture = eventLoop().schedule(new Runnable() { + @Override + public void run() { + ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise; + ConnectTimeoutException cause = + new ConnectTimeoutException("connection timed out: " + remoteAddress); + if (connectPromise != null && connectPromise.tryFailure(cause)) { + close(voidPromise()); + } + } + }, connectTimeoutMillis, TimeUnit.MILLISECONDS); + } + + promise.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isCancelled()) { + if (connectTimeoutFuture != null) { + connectTimeoutFuture.cancel(false); + } + connectPromise = null; + close(voidPromise()); + } + } + }); + } + } catch (Throwable t) { + promise.tryFailure(annotateConnectException(t, remoteAddress)); + closeIfClosed(); + } + } + + /** + * 对 TCP三次握手连接结果 进行判断 + */ + @Override + public final void finishConnect() { + // Note this method is invoked by the event loop only if the connection attempt was + // neither cancelled nor timed out. + + assert eventLoop().inEventLoop(); + + try { + boolean wasActive = isActive(); + doFinishConnect(); + fulfillConnectPromise(connectPromise, wasActive); + } catch (Throwable t) { + fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress)); + } finally { + // Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used + // See https://github.com/netty/netty/issues/1770 + if (connectTimeoutFuture != null) { + connectTimeoutFuture.cancel(false); + } + connectPromise = null; + } + } + } +} +``` \ No newline at end of file diff --git a/images/Netty/Netty的Channel组件.png b/images/Netty/Netty的Channel组件.png new file mode 100644 index 0000000000000000000000000000000000000000..bf471fe06a3f4a3ae94a9f58658104ab42e7e16d GIT binary patch literal 15329 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 literal 0 HcmV?d00001