Article Directory
1. Netty component architecture
Netty frame based event-driven master from Reactor multithreaded implementation shown in work flow process which follows. According to different functions, the components of Netty can be divided into two parts, namelyEvent distribution component and business processing component
2. Event Distribution Components
The main event distribution component contains EventLoopGroup
and Acceptor
categories, which EventLoopGroup
is the event loop group, responsible for the event distribution processing, Acceptor
the processor server process accept the event, will be responsible for registering the main event loop to set the new connection from the event loop group, from To the role of the connection
2.1 Event loop group EventLoopGroup
EventLoopGroup
mainManages the life cycle of EventLoop, maintains a set of EventLoop internally, each EventLoop is responsible for processing events on multiple Channels, and one Channel corresponds to only one EventLoop
2.1.1 NioEventLoopGroup instance of event loop group
NioEventLoopGroup
The inheritance structure is very deep, and each key attribute is distributed in different parent classes. The more important ones are as follows:
MultithreadEventLoopGroup:
DEFAULT_EVENT_LOOP_THREADS
: Indicates the default number of event loop threads, the default value isNumber of cores available in the machine * 2MultithreadEventExecutorGroup:
children
: EventExecutor array, which maintains the event loop thread belonging to the current group, which is the NioEventLoop instance object
MultithreadEventExecutorGroup
Is the EventLoopGroup
most important thing to realize its main constructor completed the following things:
new ThreadPerTaskExecutor(new DefaultThreadFactory())
Generate an Executor instance and specify its thread factory- Calling
newChild()
method NioEventLoop new instance of the current group, and specify the parameters for the ExecutorThreadPerTaskExecutor
object that is used to create and start a subsequent thread EventLoop- If there is a failure of the new NioEventLoop example, calls each NioEventLoop instance has created
shutdownGracefully()
ways to start an event loop thread
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
if (executor == null) {
// #1
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
// #2
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
if (!success) {
for (int j = 0; j < i; j ++) {
// #3
children[j].shutdownGracefully();
}
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
// Let the caller handle the interruption.
Thread.currentThread().interrupt();
break;
}
}
}
}
}
chooser = chooserFactory.newChooser(children);
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
2.1.2 Event loop instance NioEventLoop
NioEventLoop
Maintains a queue of threads and tasks, similar to the single-threaded thread pool that supports asynchronous tasks submission, the main execution I/O 任务
and 非 I/O 任务
. The execution time of both tasks than the variable ioRatio
control, the default is 50,Indicates that the execution time of non-IO tasks is equal to the execution time of IO tasks
I/O 任务
That selectionKey ready in the event, such as accept, connect, read, write, etc., by aNioEventLoop#processSelectedKeys()
trigger method非 IO 任务
Added to the task in the task queue, such as register0, bind0 other tasks, theNioEventLoop#runAllTasks()
method for triggering
NioEventLoop
The inheritance structure of is also very deep, and its key attributes are as follows:
selector
Selector, used to monitor various events on the channel connection registered on itSingleThreadEventExecutor:
taskQueue
: Queue where tasks are stored
executor
: Thread factories and executors for new threads
NioEventLoop#run()
The method is the core logic of event loop processing, and the SingleThreadEventExecutor#execute()
method is the entrance to the beginning of the event loop. No further analysis will be done here.
2.2 Connect to the Acceptor
Netty is Acceptor
in the realization of belonging 业务处理组件
, because of its implementation class ServerBootstrapAcceptor
inherit ChannelHandler
, mainly responsible for the completion of the processing of the Channel Server side
The reason why this component is classified as an event distribution component is because fromFunctional divisionFrom the point of view, it belongs to the connection structure between MainReactor and SubReactor, and is responsible for registering the new connection established by MainReactor receiving client requests to SubReactor
ServerBootstrapAcceptor#channelRead()
This is the Acceptor
most important method to achieve the logic of the new register connection to SubReactor
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
3. Business processing components
Business processing components mainly include Channel
, ChannelHandler
and ChannelHandlerContext
. A Channel
contains a ChannelPipeline
while ChannelPipeline
, it also maintains a made ChannelHandlerContext
doubly linked list, each of which ChannelHandlerContext
in turn is associated with aChannelHandler
According to ChannelHandler
a different implementation of the interface class inheritance, it can be divided ChannelInboundHandler入站处理器
and ChannelOutboundHandler出站处理器
both read and write operations respectively. 入站事件(read)
And 出站事件(write)
in ChannelPipeline
the chain processing,If there is no interceptor to terminate the delivery of the event, the inbound event will be passed from the linked list head to the last inbound processor tail, and the outbound event will be passed from the linked list tail to the first outbound processor head. There are two types ChannelHandlers do not interfere with each other
3.1 Channel
Channel
Is a pipeline,Is used to connect the byte buffer to the entity at the other endIt can be divided into 服务端 NioServerSocketChannel
and 客户端 NioSocketChannel
two broad categories. In Netty Channel
through ChannelPipeline
the plurality of the ChannelHandler
processor, the final completion of processing the IO data
- Server NioServerSocketChannel
class inheritsAbstractNioMessageChannel
holding aNioMessageUnsafe
target to complete the Chanel IO operations- The client NioSocketChannel
class inheritsAbstractNioByteChannel
holding aNioByteUnsafe
target to complete the process on Channel eventsAll inherited
AbstractChannel
Channel instances will be holding aDefaultChannelPipeline
object for the completion of IO data stream processing, and the other will hold aEventLoop
event loop instance object identifier Channel belongs, it can be used to handle job submission
3.2 ChannelHandler
ChannelHandler
Is an interface to process I / O events or intercepting I / O operations, an event may be forwarded to ChannelPipeline
the next service processing chain ChannelHandler
processing termination event or delivery
ChannelHandler
Mainly divided into进站处理器ChannelInboundHandler
and出站处理器ChannelOutboundHandler
, respectively read and write operation process. By a user to write the processing logic processor, which will be packaged in the internal NettyAbstractChannelHandlerContext
objects, and then registered to a service processing chain, to complete the logic processing of the data IO
3.3 ChannelHandlerContext
ChannelHandlerContext
Save Channel
relevant contextual information, which are mainly three kinds of realization DefaultChannelHandlerContext
, HeadContext
as well as TailContext
, for the following key attributes:
AbstractChannelHandlerContext:
next
: The next node of the
prev
current pipeline processor context node: The previous node of the
inbound
current pipeline processor context node: the inbound and outbound identification attribute of the current pipeline processor context node. If true, the node can handle inbound events
outbound
: the current pipeline processor access station identification attributes of the context node to true indicates that the node may process the outbound event
pipeline
:ChannelPipeline
example, implemented as aDefaultChannelPipeline
classDefaultChannelHandlerContext:
handler
: Encapsulated ChannelHandler handler
ChannelPipeline
Subclass DefaultChannelPipeline
is generated when a object is instantiated HeadContext
and TailContext
the object, and the front and rear pointers to each other, forming a doubly linked list. DefaultChannelPipeline#addLast()
The method will add nodes to the doubly linked list. The main processing steps are as follows:
newContext()
TheChannelHandler
encapsulated intoDefaultChannelHandlerContext
objectsaddLast0()
The newChannelHandlerContext
object is added to a doubly linked listcallHandlerAdded0()
CallbackChannelHandler#handlerAdded()
method so that the relevant logic written is executed when the processor is added to the processing chain
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);
newCtx = newContext(group, filterName(name, handler), handler);
addLast0(newCtx);
// If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}