Text around the figure below, Netty-Channel simplified version of the system architecture diagram to start from the top Channel interfaces began to start, down progressive, less gossip and Syria, direct open line and
Overview: can be seen from the figure, starting from the top-level interface Channel, defines a set of methods in the interface as standard, followed by a two to abstract interface class, this abstract class method to interface , was partially achieved, depending on the features and start branches into Channel Channel and the client end of the service
review
Classification of Channel
The server and the client, Channel can be divided into two categories (the two types of branch see above):
- Server:
NioServerSocketChannel
- Client:
NioSocketChannel
What is a Channel?
channel is a conduit for the buffer Buf byte and the other end is connected to an entity, this example may be the Socket, File may be, Nio in network programming model, the server and the client IO data exchange (push each other to give information) of the medium is the Channel
Netty of native Jdk ServerSocketChannel
is encapsulated and packaged in a reinforced NioXXXChannel
, with respect to native JdkChannel, Netty Channel is increased following components
- id to identify unique information
- There may be a parent Channel
- Pipeline pepiline
- unsafe internal classes for reading and writing data
- The associated lifetime experience NioEventLoop
This blog will traceability system image above to find out NioXXXChannel
relative to the native channel above jdk where to add new components
Source start -Channel
Now come on map Channel
part, he is an interface, netty use it specifies a Channel
is a function that has, in its document of Channel的
what is, as well as the individual components are described
- It describes what channel is dim
Channel
ByChannelPipeline
a plurality ofHandler
processors,Channel
the use of which is processing the IO dataChannel
中的所有Io操作都是异步的,一经调用就马上返回,于是Netty基于Jdk原生的Future
进行了封装,ChannelFuture
, 读写操作会返回这个对象,实现自动通知IO操作已完成Channel
是可以有parent的, 如下
// 创建客户端channel时,会把服务端的Channel设置成自己的parent
// 于是就像下面:
服务端的channel = 客户端的channel.parent();
服务的channel.parent()==null;
此外,Channel还定义了大量的抽象方法, 如下:
/**
* todo 返回一个仅供内部使用的unsafe对象, Chanel上 IO数据的读写都是借助这个类完成的
*/
Unsafe unsafe();
// 返回Channel的管道
ChannelPipeline pipeline();
ByteBufAllocator alloc();
@Override // todo 进入第一个实现 , 读取Channel中的 IO数据
Channel read();
// 返回Channel id
ChannelId id();
// todo 返回channel所注册的 eventLoop
EventLoop eventLoop();
// 返回当前Channel的父channel
Channel parent();
// todo 描述了 关于channel的 一些列配置信息
ChannelConfig config();
// 检查channel是否开启
boolean isOpen();
// 检查channel是否注册
boolean isRegistered();
// todo 什么是active 他说的是channel状态, 什么状态呢? 当前channel 若和Selector正常的通信就说明 active
boolean isActive();
// 返回channel的元数据
ChannelMetadata metadata();
// 服务器的ip地址
SocketAddress localAddress();
// remoteAddress 客户端的ip地址
SocketAddress remoteAddress();
ChannelFuture closeFuture();
boolean isWritable();
long bytesBeforeUnwritable();
long bytesBeforeWritable();
@Override
Channel flush();
Channel重要的内部接口 unsafe
Netty中,真正帮助Channel完成IO读写操作的是它的内部类unsafe
, 源码如下, 很多重要的功能在这个接口中定义, 下面列举的常用的方法
interface Unsafe {
// 把channel注册进EventLoop
void register(EventLoop eventLoop, ChannelPromise promise);
// todo 给channel绑定一个 adress,
void bind(SocketAddress localAddress, ChannelPromise promise);
// 把channel注册进Selector
void deregister(ChannelPromise promise);
// 从channel中读取IO数据
void beginRead();
// 往channe写入数据
void write(Object msg, ChannelPromise promise);
...
...
AbstractChanel
接着往下看,下面来到Channel
接口的直接实现类,AbstractChannel
他是个抽象类, AbstractChannel
重写部分Channel
接口预定义的方法, 它的抽象内部类AbstractUnsafe
实现了Channel
的内部接口unsafe
我们现在是从上往下看,但是当我们创建对象使用的时候其实是使用的特化的对象,创建特化的对象就难免会调层层往上调用父类的构造方法, 所以我们看看AbstractChannel
的构造方法干了什么活? 源码如下:
protected AbstractChannel(Channel parent) {
this.parent = parent;
// todo channelId 代表Chanel唯一的身份标志
id = newId();
// todo 创建一个unsafe对象
unsafe = newUnsafe();
// todo 在这里初始化了每一个channel都会有的pipeline组件
pipeline = newChannelPipeline();
}
我们看,AbstractChannel
构造函数, 接受的子类传递进来的参数只有一个parent CHannel,而且,还不有可能为空, 所以在AbstractChannel
是没有维护jdk底层的Channel的, 相反他会维护着Channel关联的EventLoop,我是怎么知道的呢? 首先,它的属性中存在这个字段,而且,将channel注册进selector的Register()方法是AbastractChannel
重写的,Selector在哪呢? 在EventLoop里面,它怎么得到的呢? 它的子类传递了给了它
终于看出来点眉目,构造方法做了四件事
- 设置parent
- 如果当前创建的channel是客户端的channel,把parent初始化为他对应的parent
- 如果为服务端的channel,这就是null
- 创建唯一的id
- 创建针对channel进行io读写的
unsafe
- 创建channel的处理器handler链
channelPipeline
AbstractChannel中维护着EventLoop
AbstractChanel
的重要抽象内部类AbstractUnsafe
继承了Channel
的内部接口Unsafe
他的源码如下,我贴出来了两个重要的方法, 关于这两个方法的解析,我写在代码的下面
protected abstract class AbstractUnsafe implements Unsafe {
@Override
// todo 入参 eventLoop == SingleThreadEventLoop promise == NioServerSocketChannel + Executor
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;
}
// todo 赋值给自己的 事件循环, 把当前的eventLoop赋值给当前的Channel上 作用是标记后续的所有注册的操作都得交给我这个eventLoop处理, 正好对应着下面的判断
// todo 保证了 即便是在多线程的环境下一条channel 也只能注册关联上唯一的eventLoop,唯一的线程
AbstractChannel.this.eventLoop = eventLoop;
// todo 下面的分支判断里面执行的代码是一样的!!, 为什么? 这是netty的重点, 它大量的使用线程, 线程之间就会产生同步和并发的问题
// todo 下面的分支,目的就是把线程可能带来的问题降到最低限度
// todo 进入inEventLoop() --> 判断当前执行这行代码的线程是否就是 SingleThreadEventExecutor里面维护的那条唯一的线程
// todo 解释下面分支的必要性, 一个eventLoop可以注册多个channel, 但是channel的整个生命周期中所有的IO事件,仅仅和它关联上的thread有关系
// todo 而且,一个eventLoop在他的整个生命周期中,只和唯一的线程进行绑定,
//
// todo 当我们注册channel的时候就得确保给他专属它的thread,
// todo 如果是新的连接到了,
if (eventLoop.inEventLoop()) {
// todo 进入regist0()
register0(promise);
} else {
try {
// todo 如果不是,它以一个任务的形式提交 事件循环 , 新的任务在新的线程开始, 规避了多线程的并发
// todo 他是SimpleThreadEventExucutor中execute()实现的,把任务添加到执行队列执行
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;
// todo 进入这个方法doRegister()
// todo 它把系统创建的ServerSocketChannel 注册进了选择器
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.
// todo 确保在 notify the promise前调用 handlerAdded(...)
// todo 这是必需的,因为用户可能已经通过ChannelFutureListener中的管道触发了事件。
// todo 如果需要的话,执行HandlerAdded()方法
// todo 正是这个方法, 回调了前面我们添加 Initializer 中添加 Accpter的重要方法
pipeline.invokeHandlerAddedIfNeeded();
// todo !!!!!!! 观察者模式!!!!!! 通知观察者,谁是观察者? 暂时理解ChannelHandler 是观察者
safeSetSuccess(promise);
// todo 传播行为, 传播什么行为呢? 在head---> ServerBootStraptAccptor ---> tail传播事件ChannelRegistered , 也就是挨个调用它们的ChannelRegisted函数
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.
// todo 对于服务端: javaChannel().socket().isBound(); 即 当Channel绑定上了端口 isActive()才会返回true
// todo 对于客户端的连接 ch.isOpen() && ch.isConnected(); 返回true , 就是说, Channel是open的 打开状态的就是true
if (isActive()) {
if (firstRegistration) {
// todo 在pipeline中传播ChannelActive的行为,跟进去
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
// todo 可以接受客户端的数据了
beginRead();
}
}
} catch (Throwable t) {
// Close the channel directly to avoid FD leak.
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
@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();
// todo 由于端口的绑定未完成,所以 wasActive是 false
try {
// todo 绑定端口, 进去就是NIO原生JDK绑定端口的代码
doBind(localAddress);
// todo 端口绑定完成 isActive()是true
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
// todo 根据上面的逻辑判断, 结果为 true
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
// todo 来到这里很重要, 向下传递事件行为, 传播行为的时候, 从管道的第一个节点开始传播, 第一个节点被封装成 HeadContext的对象
// todo 进入方法, 去 HeadContext里面查看做了哪些事情
// todo 她会触发channel的read, 最终重新为 已经注册进selector 的 chanel, 二次注册添加上感性趣的accept事件
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
// todo 观察者模式, 设置改变状态, 通知观察者的方法回调
safeSetSuccess(promise);
}
AbstractChannel
抽象内部类的register(EventLoop,channelPromise)方法
这个方法,是将channel注册进EventLoop的Selector, 它的调用顺序如下:
本类方法 regist()--> 本类方法 register0() --> 本类抽象方法doRegister()
doRegister() 在这里设计成抽象方法,等着子类去具体的实现, 为啥这样做呢?
刚才说了,AbstractChannel
本身就是个模板,而且它仅仅维护了EventLoop,没有拿到channel引用的它根本不可能进行注册的逻辑,那谁有jdk原生channel的引用呢? 它的直接子类AbstractNioChannel
下面是AbstractNioChannel
的构造方法, 它自己维护jdk原生的Channel,所以由他重写doRegister()
,
*/ // todo 无论是服务端的channel 还是客户端的channel都会使用这个方法进行初始化
// // TODO: 2019/6/23 null ServerSocketChannel accept
// todo 如果是在创建NioSocketChannel parent==NioServerSocketChannel ch == SocketChanel
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);// todo 继续向上跟,创建基本的组件
// todo 如果是创建NioSocketChannel 这就是在保存原生的jdkchannel
// todo 如果是创建NioServerSocketChannel 这就是在保存ServerSocketChannel
this.ch = ch;
// todo 设置上感兴趣的事件
this.readInterestOp = readInterestOp;
try {
// todo 作为服务端, ServerSocketChannel 设置为非阻塞的
// todo 作为客户端 SocketChannel 设置为非阻塞的
ch.configureBlocking(false);
} catch (IOException e) {
AbstractChannel
抽象内部类的bind()方法
bind()方法的调用顺序, 本类方法 bind()--> 本类的抽象方法 dobind()
方法的目的是给Channel绑定上属于它的端口,同样有一个抽象方法,等着子类去实现,因为我们已经知道了AbstractChannel
不维护channel的引用,于是我就去找dobind()
这个抽象函数的实现, 结果发现,AbstractChannel
的直接子类AbstractNioChannel
中根本不没有他的实现,这是被允许的,因为AbstractNioChannel
本身也是抽象类, 到底是谁实现呢? 如下图:在NioServerSocketChannel中获取出 Jdk原生的channel, 客户端和服务端的channel又不同,所以绑定端口这中特化的任务,交给他们自己实现
AbstractChannel
的beginRead()()方法
上面完成注册之后,就去绑定端口,当端口绑定完成,就会channel处于active
状态,下一步就是执行beginRead()
,执行的流程如下
本类抽象方法 beginRead()
--> 本类抽象方法doBeginRead()
这个read()
就是从已经绑定好端口的channel中读取IO数据,和上面的方法一样,对应没有channel
应用的AbstractChannel
来说,netty把它设计成抽象方法,交给拥有jdk 原生channel
引用的AbstractNioChannel
实现
小结:
AbstractChannel
作为Channel的直接实现类,本身又是抽象类,于是它实现了Channel的预留的一些抽象方法, 初始化了channel的四个组件 id pipeline unsafe parent, 更为重要的是它的抽象内部类 实现了 关于nettyChannel的注册,绑定,读取数据的逻辑,而且以抽象类的方法,挖好了填空题等待子类的特化实现
递进AbstractNioChannel
跟进构造方法
依然是来到AbstractNioChannel
的构造方法,发现它做了如下的构造工作:
- 把parent传递给了
AbstractChannel
- 把子类传递过来的Channel要告诉Selector的感兴趣的选项保存
- 设置channel为非阻塞
// todo 无论是服务端的channel 还是客户端的channel都会使用这个方法进行初始化
// // TODO: 2019/6/23 null ServerSocketChannel accept
// todo 如果是在创建NioSocketChannel parent==NioServerSocketChannel ch == SocketChanel
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);// todo 继续向上跟,创建基本的组件
// todo 如果是创建NioSocketChannel 这就是在保存原生的jdkchannel
// todo 如果是创建NioServerSocketChannel 这就是在保存ServerSocketChannel
this.ch = ch;
// todo 设置上感兴趣的事件
this.readInterestOp = readInterestOp;
try {
// todo 作为服务端, ServerSocketChannel 设置为非阻塞的
// todo 作为客户端 SocketChannel 设置为非阻塞的
ch.configureBlocking(false);
} catch (IOException e) {
重写了它父类的doRegister()
AbstractNioChannel
维护channel的引用,真正的实现把 jdk 原生的 channel注册进 Selector中
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
// todo javaChannel() -- 返回SelectableChanel 可选择的Channel,换句话说,可以和Selector搭配使用,他是channel体系的顶级抽象类, 实际的类型是 ServerSocketChannel
// todo eventLoop().unwrappedSelector(), -- > 获取选择器, 现在在AbstractNioChannel中 获取到的eventLoop是BossGroup里面的
// todo 到目前看, 他是把ServerSocketChannel(系统创建的) 注册进了 EventLoop的选择器
// todo 这里的 最后一个参数是 this是当前的channel , 意思是把当前的Channel当成是一个 attachment(附件) 绑定到selector上 作用???
// todo 现在知道了attachment的作用了
// todo 1. 当channel在这里注册进 selector中返回一个selectionKey, 这个key告诉selector 这个channel是自己的
// todo 2. 当selector轮询到 有channel出现了自己的感兴趣的事件时, 需要从成百上千的channel精确的匹配出 出现Io事件的channel,
// todo 于是seleor就在这里提前把channel存放入 attachment中, 后来使用
// todo 最后一个 this 参数, 如果是服务启动时, 他就是NioServerSocketChannel 如果是客户端他就是 NioSocketChannel
// todo 到目前为止, 虽然注册上了,但是它不关心任何事件
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
新增内部接口
AbstractNioChannel
新添加了一个内部接口,作为原Channel的扩展,源码如下, 我们着重关心的就是这个新接口的 read()
方法, 它的作用是从channel去读取IO数据,作为接口的抽象方法,它规范服务端和客户端根据自己需求去不同的实现这个read()
怎么特化实现这个read方法呢? 若是服务端,它read的结果就是一个新的客户端的连接, 如果是客户端,它read的结果就是 客户端发送过来的数据,所以很有必要去特化
/**
* Read from underlying {@link SelectableChannel}
*/
// todo 两个实现类, NioByteUnsafe , 处理关于客户端发来的信息
// todo NioMessageUnsafe 处理客户端新进来的连接
void read();
/**
* Special {@link Unsafe} sub-type which allows to access the underlying {@link SelectableChannel}
*/
public interface NioUnsafe extends Unsafe {
/**
* Return underlying {@link SelectableChannel}
*/
SelectableChannel ch();
/**
* Finish connect
*/
void finishConnect();
void forceFlush();
}
AbstractNioChannel
的抽象内部内同时继承了它父类的AbstractUnsafe
实现了当前的NioUnsafe
, 再往后看, 问题来了, 服务端和客户端在的针对read的特化实现在哪里呢? 想想看肯定在它子类的unsafe内部类中,如下图,紫框框
一会再具体看这两个 内部类是如何特化read的 注意啊,不再是抽象的了
再进一步 AbstractNioMessageChannel
它的构造函数如下, 只是调用父类的构造函数,传递参数
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
// todo 在进去
// todo null ServerSocketChannel accept
super(parent, ch, readInterestOp);
}
AbstractNioMessageChannel
的MessageNioUnsafe
对read()
特化实现
在read方法中,我们可以看到,他调用是本类的抽象方法doReadMessages(List<Object> buf)
, 方法的实现类是继承体系的最底层的NioServerSocketChannel
, 因为他就是那个特化的服务端channel
当然如果我们一开始跟进read()时,来到的客户端的AbstractNioByteChannel
,现在我们找到的doReadMessage()
就是由 客户端的channelNioSocketChannel
完成的doReadBytes()
// todo 用于处理新链接进来的内部类
private final class NioMessageUnsafe extends AbstractNioUnsafe {
// todo 这个容器用于存放临时读到的连接
private final List<Object> readBuf = new ArrayList<Object>();
// todo 接受新链接的 read来到这里
@Override
public void read() {
...
doBeginRead(buf);
...
}
// todo 处理新的连接 是在 NioServerSocketChannel中实现的, 进入查看
protected abstract int doReadMessages(List<Object> buf) throws Exception;
最终,特化的channel实现
Now we come to the bottom, the whole Zhang Jicheng map on show in front of all, here go see, specialized server Channel NioServetSocketChannel
and NioSocketChannel
on doReadMessages()
and doReadBytes()
their realization
The service side, we see that the data is read in creating a new Jdk remote channel, because it is creating a new connection chanel
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
// todo java Nio底层在这里 创建jdk底层的 原生channel
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
// todo 把java原生的channel, 封装成 Netty自定义的封装的channel , 这里的buf是list集合对象,由上一层传递过来的
// todo this -- NioServerSocketChannel
// todo ch -- SocketChnnel
buf.add(new NioSocketChannel(this, ch));
return 1;
}
...
The client, read IO data sent by the client
@Override
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}
summary:
Netty's channel inherited system, now completed, I believe, when we are now again being from the NioServerEventLoop
start, to see if he's initialization process should be very simple, which I hope I can keep in mind a few points
AbstractChannel
MaintenanceNioChannel
ofEventLoop
AbstractNioChannel
Maintenance of native jdkchannel
AbstractChannel
TheAbstractUnsafe
main template defines a set, to provide a fill-subclass, the following three fill in the blank- Registration Registration into the chanel Selector
- Bind the bind port chanel
- Add interest to the event, to create out of the channel on the secondary registration event netty interested can handle
- io operations is unsafe channel inner class completed
- From server channel, read the new connection
NioMessageUnsafe
- Client from the channel, read out the data
NioByteUnsafe
NioEventLoop the start timing is ServerSocketChannel initialization is complete at the end of the service NioServerSocketChannel and registration executed after NioEventLoop, the next step is to bind the port, but before the bound port, complete NioEventLoop the start of work, because the program runs up to this stage, is still only MainThread a thread, following began to read the source code NioEventLoop open a new line Cheng Zili the house
- From server channel, read the new connection