Table of contents
- foreword
- 1. Introduction
- 2. Why use Netty
- 3. Structure diagram
- 4. Forever Hello Word
- 5. Features and important components of Netty
foreword
This article mainly describes some features and important components of the Netty framework. I hope that after reading it, I can have a more intuitive feeling about the Netty framework. I hope it can help readers get started with Netty quickly and reduce some detours.
1. Introduction
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high-performance protocol servers and clients.
2. Why use Netty
From the official website, Netty is a network application framework for developing servers and clients. That is, a framework for network programming. Since it is network programming, Socket will not be discussed, why not use NIO?
2.1 Disadvantages of NIO
- The class library and API of NIO are complicated, and the learning cost is high. You need to be proficient in Selector, ServerSocketChannel, SocketChannel, ByteBuffer, etc.
- Familiarity with Java multithreaded programming is required. This is because NIO programming involves Reactor mode, you must be very familiar with multithreading and network programming in order to write high-quality NIO programs.
- The infamous epoll bug. It causes the Selector to poll empty, which eventually leads to 100% CPU. Until the JDK1.7 version has not been fundamentally resolved.
2.2 Advantages of Netty
In contrast, Netty has many advantages:
- The API is easy to use and the learning cost is low.
- Powerful, built-in a variety of decoding encoders, support for a variety of protocols.
- High performance. Compared with other mainstream NIO frameworks, Netty has the best performance.
- The community is active, BUG will be repaired in time, the iterative version cycle is short, and new functions are constantly added.
- Both Dubbo and Elasticsearch use Netty, and the quality has been verified.
3. Structure diagram
- The green core core module includes zero copy, API library, and extensible event model.
- The orange part is Protocol Support protocol support, including Http protocol, webSocket, SSL (Secure Socket Protocol), - - Google Protobuf protocol, zlib/gzip compression and decompression, Large File Transfer large file transfer, etc.
- The red part is Transport Services transmission services, including Socket, Datagram, Http Tunnel and so on.
It can be seen from the above that Netty's functions, protocols, and transmission methods are relatively complete and powerful.
4. Forever Hello Word
First build a HelloWord project, familiarize yourself with the API, and pave the way for later learning. Based on the picture below:
4.1 Introducing Maven dependencies
The version used is 4.1.20, a relatively stable version.
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.20.Final</version>
</dependency>
4.2 Create a server startup class
public class MyServer {
public static void main(String[] args) throws Exception {
//创建两个线程组 boosGroup、workerGroup
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建服务端的启动对象,设置参数
ServerBootstrap bootstrap = new ServerBootstrap();
//设置两个线程组boosGroup和workerGroup
bootstrap.group(bossGroup, workerGroup)
//设置服务端通道实现类型
.channel(NioServerSocketChannel.class)
//设置线程队列得到连接个数
.option(ChannelOption.SO_BACKLOG, 128)
//设置保持活动连接状态
.childOption(ChannelOption.SO_KEEPALIVE, true)
//使用匿名内部类的形式初始化通道对象
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//给pipeline管道设置处理器
socketChannel.pipeline().addLast(new MyServerHandler());
}
});//给workerGroup的EventLoop对应的管道设置处理器
System.out.println("java技术爱好者的服务端已经准备就绪...");
//绑定端口号,启动服务端
ChannelFuture channelFuture = bootstrap.bind(6666).sync();
//对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
4.3 Create a server-side processor
/**
* 自定义的Handler需要继承Netty规定好的HandlerAdapter
* 才能被Netty框架所关联,有点类似SpringMVC的适配器模式
**/
public class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//获取客户端发送过来的消息
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("收到客户端" + ctx.channel().remoteAddress() + "发送的消息:" + byteBuf.toString(CharsetUtil.UTF_8));
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//发送消息给客户端
ctx.writeAndFlush(Unpooled.copiedBuffer("服务端已收到消息,并给你发送一个问号?", CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//发生异常,关闭通道
ctx.close();
}
}
4.4 Create a client startup class
public class MyClient {
public static void main(String[] args) throws Exception {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
//创建bootstrap对象,配置参数
Bootstrap bootstrap = new Bootstrap();
//设置线程组
bootstrap.group(eventExecutors)
//设置客户端的通道实现类型
.channel(NioSocketChannel.class)
//使用匿名内部类初始化通道
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//添加客户端通道的处理器
ch.pipeline().addLast(new MyClientHandler());
}
});
System.out.println("客户端准备就绪,随时可以起飞~");
//连接服务端
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6666).sync();
//对通道关闭进行监听
channelFuture.channel().closeFuture().sync();
} finally {
//关闭线程组
eventExecutors.shutdownGracefully();
}
}
}
4.5 Create a client handler
public class MyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//发送消息到服务端
ctx.writeAndFlush(Unpooled.copiedBuffer("歪比巴卜~茉莉~Are you good~马来西亚~", CharsetUtil.UTF_8));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//接收服务端发送过来的消息
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("收到服务端" + ctx.channel().remoteAddress() + "的消息:" + byteBuf.toString(CharsetUtil.UTF_8));
}
}
4.6 Testing
Start the server first, then start the client, and you can see the result:
MyServer print result:
MyClient print result:
5. Features and important components of Netty
5.1 taskQueue task queue
If the Handler processor has some long-term business processing, it can be handed over to taskQueue for asynchronous processing. How to use it, please see the code demo:
public class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//获取到线程池eventLoop,添加线程,执行
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
//长时间操作,不至于长时间的业务操作导致Handler阻塞
Thread.sleep(1000);
System.out.println("长时间的业务处理");
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
When we make a debug debug, we can see that the added taskQueue has a task.
5.2 scheduleTaskQueue delayed task queue
The delayed task queue is very similar to the task queue described above, except that there is an additional setting that can delay execution for a certain period of time. Please see the code demonstration:
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
try {
//长时间操作,不至于长时间的业务操作导致Handler阻塞
Thread.sleep(1000);
System.out.println("长时间的业务处理");
} catch (Exception e) {
e.printStackTrace();
}
}
},5, TimeUnit.SECONDS);//5秒后执行
Still open debug to debug and view, we can have a scheduleTaskQueue task to be executed
5.3 Future asynchronous mechanism
When building the HelloWord project, we saw a line of code like this:
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6666);
Many operations return this ChannelFuture object. What is this ChannelFuture object used for?
ChannelFuture provides a way to be notified asynchronously when an operation is complete. Generally, in Socket programming, waiting for the response result is blocked synchronously, but Netty will not cause blocking, because ChannelFuture obtains the result in a form similar to the observer mode. Please see a code demo:
//添加监听器
channelFuture.addListener(new ChannelFutureListener() {
//使用匿名内部类,ChannelFutureListener接口
//重写operationComplete方法
@Override
public void operationComplete(ChannelFuture future) throws Exception {
//判断是否操作成功
if (future.isSuccess()) {
System.out.println("连接成功");
} else {
System.out.println("连接失败");
}
}
});
5.4 Bootstrap与ServerBootStrap
Bootstrap and ServerBootStrap are a factory class provided by Netty to create client and server starters. Using this factory class is very convenient to create startup classes. According to some examples above, it can be seen that it can greatly reduce the difficulty of development. . First look at a class diagram:
It can be seen that they are all inherited from the AbstractBootStrap abstract class, so the configuration methods are roughly the same.
In general, the steps to create a starter with Bootstrap can be divided into the following steps:
5.4.1 group()
In the previous article "Reactor Mode", we said that the server needs to use two thread groups:
bossGroup is used to monitor client connections, and is responsible for creating connections with clients and registering connections to the Selector of workerGroup.
workerGroup is used to handle read and write events for each connection.
Generally, create a thread group and use the following new directly to finish the job:
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
I am a little curious, since it is a thread group, what is the default number of threads? In-depth source code:
//使用一个常量保存
private static final int DEFAULT_EVENT_LOOP_THREADS;
static {
//NettyRuntime.availableProcessors() * 2,cpu核数的两倍赋值给常量
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
}
}
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
//如果不传入,则使用常量的值,也就是cpu核数的两倍
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
As you can see from the source code, the default number of threads is twice the number of CPU cores. Suppose you want to customize the number of threads, you can use a parameterized constructor:
//设置bossGroup线程数为1
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//设置workerGroup线程数为16
EventLoopGroup workerGroup = new NioEventLoopGroup(16);
5.4.2 channel()
This method is used to set the channel type. When the connection is established, the corresponding Channel instance will be created according to this setting.
Using the debug mode, you can see that
the channel types are as follows:
NioSocketChannel: Asynchronous non-blocking client TCP Socket connection.
NioServerSocketChannel: Asynchronous non-blocking server-side TCP Socket connection.
These two channel types are commonly used because they are asynchronous and non-blocking. So it is the first choice.
OioSocketChannel: Synchronous blocking client TCP Socket connection.
OioServerSocketChannel: Synchronous blocking server-side TCP Socket connection.
I have debugged it locally, but it is different from Nio in use, because it is blocked, so the API calls are also different. Because it is blocking IO, few people will choose to use Oio, so it is difficult to find examples. I thought about it for a while, and after several error reports, I finally got it through. code show as below:
//server端代码,跟上面几乎一样,只需改三个地方
//这个地方使用的是OioEventLoopGroup
EventLoopGroup bossGroup = new OioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup)//只需要设置一个线程组boosGroup
.channel(OioServerSocketChannel.class)//设置服务端通道实现类型
//client端代码,只需改两个地方
//使用的是OioEventLoopGroup
EventLoopGroup eventExecutors = new OioEventLoopGroup();
//通道类型设置为OioSocketChannel
bootstrap.group(eventExecutors)//设置线程组
.channel(OioSocketChannel.class)//设置客户端的通道实现类型
NioSctpChannel: Asynchronous client Sctp (Stream Control Transmission Protocol, Stream Control Transmission Protocol) connection.
NioSctpServerChannel: Asynchronous Sctp server connection.
It failed to start locally. I read some comments from netizens on the Internet, saying that it can only be started under the Linux environment. From the error message: SCTP not supported on this platform, does not support this platform. Because my computer is a window system, what netizens said makes sense.
5.4.3 option()与childOption()
Let me first talk about the difference between the two.
option() sets the server to receive incoming connections, that is, the boosGroup thread.
childOption() is provided to the connection received by the parent pipeline, that is, the workerGroup thread.
After figuring it out, let's take a look at some commonly used settings:
SocketChannel parameters, which are commonly used parameters of childOption():
SO_RCVBUF Socket parameter, TCP data receiving buffer size.
TCP_NODELAY TCP parameter, send data immediately, the default value is True.
SO_KEEPALIVE Socket parameter, connection keep alive, the default value is False. When this function is enabled, TCP will actively detect the validity of idle connections.
ServerSocketChannel parameters, which are common parameters of option():
SO_BACKLOG Socket parameter, the queue length for the server to accept connections, if the queue is full, the client connection will be rejected. The default value is 200 for Windows and 128 for others.
Due to space limitations, I won’t list others. You can go to the Internet to find information and have a look.
5.4.3 option()与childOption()
Let me first talk about the difference between the two.
option() sets the server to receive incoming connections, that is, the boosGroup thread.
childOption() is provided to the connection received by the parent pipeline, that is, the workerGroup thread.
After figuring it out, let's take a look at some commonly used settings:
SocketChannel parameters, which are commonly used parameters of childOption():
SO_RCVBUF Socket parameter, TCP data receiving buffer size.
TCP_NODELAY TCP parameter, send data immediately, the default value is True.
SO_KEEPALIVE Socket parameter, connection keep alive, the default value is False. When this function is enabled, TCP will actively detect the validity of idle connections.
ServerSocketChannel parameters, which are common parameters of option():
SO_BACKLOG Socket parameter, the queue length for the server to accept connections, if the queue is full, the client connection will be rejected. The default value is 200 for Windows and 128 for others.
Due to space limitations, I won’t list others. You can go to the Internet to find information and have a look.
5.4.4 Setting up the pipeline (emphasis)
ChannelPipeline is the chain of responsibility for Netty to process requests, and ChannelHandler is the processor that specifically processes requests. In fact, each channel has a pipeline of processors.
In Bootstrap, the childHandler() method needs to initialize the channel and instantiate a ChannelInitializer. At this time, it is necessary to rewrite the method of initChannel() to initialize the channel. The assembly line is carried out in this place.
//使用匿名内部类的形式初始化通道对象
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//给pipeline管道设置自定义的处理器
socketChannel.pipeline().addLast(new MyServerHandler());
}
});
Processor Handler is mainly divided into two types:
ChannelInboundHandlerAdapter (inbound handler), ChannelOutboundHandler (outbound handler)
Inbound refers to the data from the underlying java NIO Channel to the Netty Channel.
Outbound refers to operating the underlying java NIO Channel through Netty's Channel.
Commonly used events for ChannelInboundHandlerAdapter processors are:
- Register event fireChannelRegistered.
- Connection establishment event fireChannelActive.
- Read events and read completion events fireChannelRead, fireChannelReadComplete.
- Exception notification event fireExceptionCaught.
- User-defined event fireUserEventTriggered.
- Channel writable state change event fireChannelWritabilityChanged.
- Connection closed event fireChannelInactive.
Commonly used events for ChannelOutboundHandler processors are:
- Port binding bind.
- Connect to the server connect.
- Write event write.
- Refresh time flush.
- Read event read.
- Actively disconnect disconnect.
- Close the channel event close.
- There is also a similar handler(), which is mainly used to assemble the parent channel, which is the bossGroup thread. Under normal circumstances, this method is not used.
5.4.5 bind()
Provide the address and port number for the server or client to bind the server. The default is asynchronous startup. If the sync() method is added, it is synchronous.
There are five overloaded methods with the same name, all of which are used to bind the address port number. Not introduced one by one.
5.4.6 Close EventLoopGroup gracefully
//释放掉所有的资源,包括创建的线程
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
All child Channels will be closed. After closing, the underlying resources are released.
5.5 Channel
What is a Channel? Take a look at the description of the official documentation:
A nexus to a network socket or a component which is capable of I/O operations such as read, write, connect, and bind
Translation idea: A component that connects to a network socket or can perform I/O operations such as reading, writing, connecting, and binding.
If the above paragraph is more abstract, here is another paragraph:
A channel provides a user:
the current state of the channel (e.g. is it open? is it connected?),
the configuration parameters of the channel (e.g. receive buffer size),
the I/O operations that the channel supports (e.g. read, write, connect, and bind), and
the ChannelPipeline which handles all I/O events and requests associated with the channel.
Translation to the effect:
The channel provides users with:
- The current state of the channel (e.g. is it open? or connected?)
- Channel configuration parameters (such as the size of the receive buffer)
- The IO operations supported by the channel (such as read, write, connect, and bind), and the ChannelPipeline that handles all IO events and requests associated with the channel.
5.5.1 Get channel status
boolean isOpen(); //如果通道打开,则返回true
boolean isRegistered();//如果通道注册到EventLoop,则返回true
boolean isActive();//如果通道处于活动状态并且已连接,则返回true
boolean isWritable();//当且仅当I/O线程将立即执行请求的写入操作时,返回true。
The above are the methods to obtain the four states of the channel.
5.5.2 Get channel configuration parameters
To obtain a single piece of configuration information, use getOption(), code demonstration:
ChannelConfig config = channel.config();//获取配置参数
//获取ChannelOption.SO_BACKLOG参数,
Integer soBackLogConfig = config.getOption(ChannelOption.SO_BACKLOG);
//因为我启动器配置的是128,所以我这里获取的soBackLogConfig=128
To get multiple pieces of configuration information, use getOptions(), code demonstration:
ChannelConfig config = channel.config();
Map<ChannelOption<?>, Object> options = config.getOptions();
for (Map.Entry<ChannelOption<?>, Object> entry : options.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
/**
SO_REUSEADDR : false
WRITE_BUFFER_LOW_WATER_MARK : 32768
WRITE_BUFFER_WATER_MARK : WriteBufferWaterMark(low: 32768, high: 65536)
SO_BACKLOG : 128
以下省略...
*/
5.5.3 IO operations supported by channel
Write operation, here demonstrates writing a message from the server to the client:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.channel().writeAndFlush(Unpooled.copiedBuffer("这波啊,这波是肉蛋葱鸡~", CharsetUtil.UTF_8));
}
Client console:
//收到服务端/127.0.0.1:6666的消息:这波啊,这波是肉蛋葱鸡~
Connection operation, code demo:
ChannelFuture connect = channelFuture.channel().connect(new InetSocketAddress("127.0.0.1", 6666));//一般使用启动器,这种方式不常用
Get ChannelPipeline through channel and do related processing:
//获取ChannelPipeline对象
ChannelPipeline pipeline = ctx.channel().pipeline();
//往pipeline中添加ChannelHandler处理器,装配流水线
pipeline.addLast(new MyServerHandler());
5.6 Selector
In NioEventLoop, there is a member variable selector, which is the Selector of the nio package. In the previous "NIO Introduction", I have already talked about the Selector.
The Selector in Netty is also the same as the Selector in NIO, which is used to listen to events, manage channels registered in the Selector, and implement multiplexers.
5.7 PiPeline与ChannelPipeline
When introducing Channel earlier, we know that ChannelHandler pipeline processors can be assembled in the channel. It is impossible for a channel to have only one ChannelHandler processor. There must be many. Since many ChannelHandlers work in a pipeline, there must be an order.
So the pipeline appeared, and the pipeline is equivalent to the container of the processor. When initializing the channel, install the channelHandler in the pipeline in order, and then the channelHandler can be executed in order.
In a Channel, there is only one ChannelPipeline. The pipeline is created when the Channel is created. ChannelPipeline contains a list of ChannelHanders, and all ChannelHandlers will be registered to ChannelPipeline.
5.8 ChannelHandlerContext
In Netty, the Handler processor is defined by us, as mentioned above, it is realized by integrating the inbound processor or the outbound processor. At this time, if we want to get the pipeline object or channel object in the Handler, how to get it.
So Netty designed this ChannelHandlerContext context object, you can get channels, pipelines and other objects, and you can read and write operations.
Through the class diagram, ChannelHandlerContext is an interface, and there are three implementation classes below.
In fact, ChannelHandlerContext is in the form of a linked list in the pipeline. Look at a piece of source code to understand:
//ChannelPipeline实现类DefaultChannelPipeline的构造器方法
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
//设置头结点head,尾结点tail
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
Let me use a picture to show it, it will be more clear:
5.9 EventLoopGroup
Let's take a look at the class diagram of EventLoopGroup:
it includes the commonly used implementation class NioEventLoopGroup. OioEventLoopGroup is also used in previous examples.
From the architecture diagram of Netty, we can know that the server needs two thread groups to work together, and the interface of this thread group is EventLoopGroup.
Each EventLoopGroup includes one or more EventLoops, and each EventLoop maintains a Selector instance.
5.9.1 Implementation Principle of Polling Mechanism
Let's take a look at the source code of DefaultEventExecutorChooserFactory:
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
@Override
public EventExecutor next() {
//idx.getAndIncrement()相当于idx++,然后对任务长度取模
return executors[idx.getAndIncrement() & executors.length - 1];
}
This code can confirm that the execution method is the polling mechanism. Next, debug and debug:
there is another judgment here. If the number of threads is not 2 to the Nth power, the modulo algorithm is used to realize it.
@Override
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
Refer to the Netty official website documentation: API documentation
is not easy to create, if you think it is useful, please like it.