Netty introduction
Netty is a high performance. The highly scalable asynchronous event driven network application framework greatly simplifies network programming such as TCP and UDP client and server development. Netty's four key elements:
- Reactor thread model: a high performance multithreaded programming idea
- Self defined Channel concept in Netty: enhanced Channel concept
- Channel pipeline responsibility chain design pattern: event handling mechanism
- Memory management: enhanced ByteBuf buffer
Netty thread model
In order to make NIO better use of multithreading, Netty implements the Reactor thread model.
There are four concepts in the Reactor model:
- Resources (request / task)
- Synchronous Event Demultiplexer
- Dispatcher distributor
- Request Handler
Clients | Reactor thread (Boss) | Worker thread pool |
---|---|---|
Request -- > | Event multiplexer -- > dispatcher -- > | processor |
Event Loop (Selector event polling) - > request handler long time consuming task or I/O processing read write.. -- > |
EventLoopGroup initialization process
EventLoopGroup constructs a group of eventloops (threads). Generally, the multiplex Reactor thread model is adopted. Two groups of eventloopgroups are used to deal with different events in different channels. One group of Main mainly deals with the accept event of the server, and the other group of Sub mainly deals with the I/O event of the client
EventLoopGroup(Main) | EventLoopGroup(Sub) |
---|---|
EventLoop (executor thread executor, EventLoop corresponds to a thread) | |
↓ Run (poll channel event assignment to perform task task) ↓ | Processing request |
TaskQueue| Selector | |
More eventloops -->Assign to Sub | I/O processing |
Main processing server accept | Mainly handle client I/O |
Execution process | Netty4.1.44.Final source location |
---|---|
new NioEventLoopGroup(): constructor ↓ | NioEventLoopGroup: Line 43 |
Determine the number of threads: default cpus*2 ↓ | MultithreadEventLoopGroup: 52 lines, 40 lines |
new Executor: build thread executor | MultithreadEventExecutorGroup: Line 76 |
For - > newchild(): build EventLoop | MultithreadEventExecutorGroup: Line 84 |
new EventExecutorChooser | MultithreadEventExecutorGroup: Line 111 |
Start of EventLoop
EventLoop implements the executor interface. When the executor method is called to submit a task, it determines whether to start. If it is not started, it calls the built-in executor to create a new thread to trigger the execution of the run method.
Execution process | Netty4.1.44.Final source location |
---|---|
executor: request to execute task | SingleThreadEventExecutor: execute(): 828 lines |
addTask: add to task queue | SingleThreadEventExecutor: addTask(): 840 lines |
Determine whether it is the call of EventLoop itself | SingleThreadEventExecutor: inEventLoop(): 841 lines |
startThread -> doStartThread↓ | SingleThreadEventExecutor: startThread(): 842 lines |
Using executor to create a new thread to execute run | SingleThreadEventExecutor: doStartThread(): Line 990; SingleThreadEventExecutor.this.run(): Line 1001 |
Bind port procedure
Execution process | Netty4.1.44.Final source location |
---|---|
Bind (port): ServerBootstrap.bind(20480) ↓ | AbstractBootstrap: bind(): 233 lines |
Create and initialize channels | AbstractBootstrap: initAndRegister(): 260 lines; AbstractBootstrap: newChannel(): 298 lines; ServerBootstrap: init(): 125 lines |
Register to the Selector in the EventLoop (submit the task to the EventLoop for execution. After registration, continue binding) | AbstractBootstrap: config().group().register(channel): 311 lines; AbstractChannel: register(): 452 lines; doRegister(): 497 lines; AbstractNioChannel: doRegister(): 380 lines |
doBind0() -> channel.bind↓ | AbstractBootstrap: doBind0(): 344 lines |
pipeline.bind↓ | AbstractChannel: pipeline.bind: 247 lines; AbstractChannelHandlerContext: bind: 479 lines; AbstractChannelHandlerContext: invokeBind: 506 lines |
HandlerContext.bind↓ | DefaultChannelPipeline: unsafe.bind: 1346 lines |
AbstractUnsafe.bind↓ | AbstractChannel: doBind: Line 551 |
NioServerSocketChannel.doBind | NioServerSocketChannel: doBind: Line 132 |
Channel concept
Channel in Netty is an abstract concept, which can be understood as the enhancement and expansion of JDK NIO Channel.
A lot of properties and methods have been added. For complete information, see code comments. Here are some common properties and methods:
AbstractChannel |
---|
-pipeline DefaultChannelPipeline / / in channel event handling link |
-eventLoop EventLoop / / the bound EventLoop. The user performs the operation |
-unsafe Unsafe / / provides encapsulation of I/O related operations |
... |
+config() ChannelConfig / / channel configuration information is returned |
+read() Channel / / start reading data and trigger the read link call |
+write(Object msg) ChannelFuture / / write data and trigger the link call |
+Bind (socketaddresses socketaddresses) channelfuture / / bind |
... |
Netty thread model test code
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; import java.util.Queue; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; /** * @Author: Wenx * @Description: * @Date: Created in 2019/11/25 13:46 * @Modified By: */ public class MyNettyServer { public static void main(String[] args) { // Create eventloopgroup accept thread group MyEventLoopGroup mainGroup = new MyEventLoopGroup(1); // Create eventloopgroup I / O thread group MyEventLoopGroup subGroup = new MyEventLoopGroup(2); try { // Server startup and guidance tool class MyServerBootstrap b = new MyServerBootstrap(); // Configure the reactor thread group processed by the server and other configurations of the server b.group(mainGroup, subGroup).channel(ServerSocketChannel.class) .option("SO_BACKLOG", 100) .handler("new LoggingHandler()") .childHandler("new EchoServerHandler()"); // Start the service through bind FutureTask f = b.bind(20480); // Block the main thread until the network service is shut down f.get(); } catch (Exception e) { e.printStackTrace(); } finally { // Close thread group mainGroup.shutdown(); subGroup.shutdown(); } } } class MyServerBootstrap { /** * main Thread group */ private volatile MyEventLoopGroup group; /** * sub Thread group */ private volatile MyEventLoopGroup childGroup; /** * Bind thread group, single reactor mode * * @param group main/sub Thread group * @return */ public MyServerBootstrap group(MyEventLoopGroup group) { return group(group, group); } /** * Bind thread group, multi reactor mode * * @param parentGroup main Thread group * @param childGroup sub Thread group * @return */ public MyServerBootstrap group(MyEventLoopGroup parentGroup, MyEventLoopGroup childGroup) { if (parentGroup == null) { throw new NullPointerException("group"); } if (group != null) { throw new IllegalStateException("group set already"); } group = parentGroup; if (childGroup == null) { throw new NullPointerException("childGroup"); } if (this.childGroup != null) { throw new IllegalStateException("childGroup set already"); } this.childGroup = childGroup; return this; } /** * Configure the channel factory * * @param channelClass channel Factory class * @return */ public MyServerBootstrap channel(Class<?> channelClass) { // Pseudo code. Here is the value assigned to channelFactory, which is convenient for subsequent initialization of channel // Because we are NIO TCP, we will directly use ServerSocketChannel, SocketChannel in the future return this; } /** * configuration parameter * * @param option Parameter KEY * @param value Parameter value * @return */ public MyServerBootstrap option(Object option, Object value) { // Pseudo code. Here is to add options to options for later configuration. return this; } /** * main Thread group processor * * @param handler processor * @return */ public MyServerBootstrap handler(Object handler) { // Pseudocode. Here, the handler is added to the groupHandler to be used by the subsequent pipeline // We will simulate the log Handler later, and print the client information when accept ing directly return this; } /** * sub Thread group processor * * @param childHandler processor * @return */ public MyServerBootstrap childHandler(Object childHandler) { // Pseudo code. Here, the handler is added to childHandler for subsequent pipeline to use // We will simulate the EchoHandler to print the data sent by the client and return the data processing return this; } /** * Bind port to open service * * @param inetPort * @return */ public FutureTask bind(int inetPort) { InetSocketAddress localAddress = new InetSocketAddress(inetPort); if (localAddress == null) { throw new NullPointerException("localAddress"); } try { // 1. Initialization and registration ServerSocketChannel channel = initAndRegister(); // 2. Bind port to start service channel.bind(localAddress); } catch (Exception e) { e.printStackTrace(); } FutureTask futureTask = new FutureTask(() -> null); return futureTask; } /** * Initialization and registration * * @return Channel * @throws Exception */ final ServerSocketChannel initAndRegister() throws Exception { // 1. Initialize the channel. Use channelFactory to create newChannel. Here, create ServerSocketChannel directly ServerSocketChannel channel = ServerSocketChannel.open(); channel.configureBlocking(false); // 2. Initialize the pipeline responsibility chain // Pseudo code // group load handler, process accept, simulate log handler to print client information // childGroup loads childHandler, processes I/O, simulates EchoHandler to print data sent by client and returns data processing // 3. Bind serverSocketChannel registration to selector // Netty enhances Channel encapsulation with many parameters. If we directly transmit this, it will not be encapsulated. It is easy to understand SelectionKey selectionKey = group.register(channel, this); selectionKey.interestOps(SelectionKey.OP_ACCEPT); return channel; } /** * Many parameters should have been included in the Channel encapsulation enhancement, so we will not encapsulate it for easy understanding * * @param key * @throws Exception */ public void channelHandler(SelectionKey key) throws Exception { SelectableChannel channel = key.channel(); if (channel instanceof ServerSocketChannel) { acceptHandler((ServerSocketChannel) key.channel()); } else if (channel instanceof SocketChannel) { ioHandler((SocketChannel) key.channel()); } } /** * mainReactor Processing accept * * @param server Server Channel * @throws Exception */ public void acceptHandler(ServerSocketChannel server) throws Exception { SocketChannel client = server.accept(); client.configureBlocking(false); // After receiving the connection from the client, bind the read event registration of SocketChannel to the selector, and distribute it to the I/O thread to continue reading the data. SelectionKey selectionKey = childGroup.register(client, this); selectionKey.interestOps(SelectionKey.OP_READ); System.out.println(Thread.currentThread().getName() + "Receiving connection : " + client.getRemoteAddress()); } /** * subReactor Processing I/O * * @param client Client Channel * @throws Exception */ public void ioHandler(SocketChannel client) throws Exception { ByteBuffer readBuff = ByteBuffer.allocate(1024); while (client.isOpen() && client.read(readBuff) != -1) { // In the case of long connection, it is necessary to manually judge whether the reading of data is finished // Here is a simple judgment: if the request is more than 0 bytes, it is considered that the request is over if (readBuff.position() > 0) { break; } } // If there is no data, do not continue the following processing if (readBuff.position() == 0) { return; } readBuff.flip(); byte[] content = new byte[readBuff.limit()]; readBuff.get(content); String response = "Have received:" + new String(content); System.out.println(response); System.out.println("Come from:" + client.getRemoteAddress()); // TODO business operation database interface call, etc ByteBuffer writeBuff = ByteBuffer.wrap(response.getBytes()); while (writeBuff.hasRemaining()) { client.write(writeBuff); } } } class MyEventLoopGroup { /** * Default EventLoop threads: cpus*2 */ private static final int DEFAULT_EVENT_LOOP_THREADS = Runtime.getRuntime().availableProcessors() * 2; /** * EventLoop aggregate */ private final MyEventLoop[] children; /** * Chooser Indexes */ private final AtomicInteger idx = new AtomicInteger(); /** * Constructor */ public MyEventLoopGroup() { this(0); } /** * Constructor * * @param nThreads EventLoop Number of threads */ public MyEventLoopGroup(int nThreads) { if (nThreads <= 0) { nThreads = DEFAULT_EVENT_LOOP_THREADS; } children = new MyEventLoop[nThreads]; for (int i = 0; i < nThreads; i++) { try { children[i] = newChild(); } catch (Exception e) { throw new IllegalStateException("failed to create a child event loop", e); } } } /** * Create EventLoop thread * * @return EventLoop * @throws Exception */ public MyEventLoop newChild() throws Exception { return new MyEventLoop(); } /** * Select an EventLoop thread * * @return EventLoop */ public MyEventLoop next() { // Chooser int index = Math.abs(idx.getAndIncrement() % children.length); return children[index]; } /** * Bind Channel registration to the Selector of EventLoop * * @param channel * @return * @throws Exception */ public SelectionKey register(SelectableChannel channel, MyServerBootstrap b) throws Exception { return next().register(channel, b); } /** * Close EventLoopGroup */ public void shutdown() { for (MyEventLoop el : children) { el.shutdown(); } } } class MyEventLoop implements Executor { /** * selector */ private Selector selector; /** * Task queue */ private final Queue<Runnable> taskQueue; /** * Task thread */ private volatile Thread thread; /** * Thread running status */ private volatile boolean running = false; /** * Constructor * * @throws IOException */ public MyEventLoop() throws IOException { // Initialize selector and task queue selector = Selector.open(); taskQueue = new LinkedBlockingQueue<>(); } /** * Add task to task queue and wait for thread to execute * * @param task Tasks to be performed */ @Override public void execute(Runnable task) { if (task == null) { throw new NullPointerException("task"); } taskQueue.offer(task); if (!running || thread == null) { startThread(); } } /** * Start task thread */ private void startThread() { if (!running) { running = true; if (thread == null) { thread = new Thread(new Runnable() { @Override public void run() { while (running) { try { processSelectedKeys(); } finally { runAllTasks(); } } } }); } thread.start(); System.out.println("Start up:" + thread.getName()); System.out.println("Come from:" + Thread.currentThread().getName()); } } /** * Process SelectedKeys */ private void processSelectedKeys() { try { selector.select(1000); // Get query results Set<SelectionKey> selected = selector.selectedKeys(); // Traverse query results Iterator<SelectionKey> iterator = selected.iterator(); while (iterator.hasNext()) { // Encapsulated query results SelectionKey key = iterator.next(); iterator.remove(); int readyOps = key.readyOps(); // Follow Read and Accept events if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { try { ((MyServerBootstrap) key.attachment()).channelHandler(key); if (!key.channel().isOpen()) { key.cancel(); // If closed, unsubscribe from this KEY } } catch (Exception ex) { key.cancel(); // If there is any exception, cancel the subscription of this KEY } } } selector.selectNow(); } catch (IOException e) { e.printStackTrace(); } } /** * Perform all tasks */ private void runAllTasks() { // Perform tasks in the queue Runnable task; while ((task = taskQueue.poll()) != null) { task.run(); } } public SelectionKey register(SelectableChannel channel, MyServerBootstrap b) throws Exception { // register is submitted in the form of task to ensure that there is no contest between selector and the thread of selector.select() FutureTask<SelectionKey> f = new FutureTask<>(() -> channel.register(selector, 0, b)); execute(f); System.out.println("register: " + channel.getClass().getName()); return f.get(); } /** * Close EventLoop */ public void shutdown() { try { running = false; if (thread != null) { thread.interrupt(); } selector.close(); } catch (IOException e) { e.printStackTrace(); } } }