I heard that wechat search “Java fish” will change strong oh!

This article is in Java Server, which contains my complete series of Java articles, can be read for study or interview

What is Netty

Netty is an asynchronous, event-driven network application framework for rapid development of maintainable, high-performance network servers and clients. Netty is widely used. It can be said that among the mainstream frameworks, if there are network requirements, Netty framework is generally used. For example, Dubbo, ES, and Zookeeper all use Netty. So even if you don’t have Netty usage scenarios in your daily work, Netty is worth learning about.

The bottom layer of Netty is developed based on NIO. In fact, most Java programmers are relatively weak in network development ability. Therefore, if there is a network related development business, if you implement it through BIO or NIO, there will be a lot of problems. Netty can be used to develop network applications quickly, so some people call Netty as the Spring of network development framework.

The difference between NIO and BIO, as I mentioned in a previous blog post, is that BIO creates a new thread for each communication, while NIO uses multiplexing to process requests. Below is the NIO process.

(2) the first netty starter program

Since it is based on NIO development, netty’s entry program is similar to the NIO entry program we wrote at that time. First develop a server side, and the Development process of Netty can follow a set of specifications:

1. Start with ServerBootstrap and assemble Netty components

2. Assemble the eventLoopGroup

3. Assemble Channel

4. Handle connection and read/write requests through handler

public class FirstServer {
    public static void main(String[] args) {
        // 1. Initiators on the server side assemble Netty components
        new ServerBootstrap()
                // Create the eventLoop group
                .group(new NioEventLoopGroup())
                // select the ServerSocketChannel implementation for the server
                .channel(NioServerSocketChannel.class)
                //4. Handle connection and read/write
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        // Convert byteBuffer to a string
                        nioSocketChannel.pipeline().addLast(new StringDecoder());
                        // Custom handler, which displays data after receiving read events
                        nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println(msg); }}); }})//5
                .bind(8080); }}Copy the code

Then develop a client, and the overall flow of the client is very similar to that of the server:

1. Use Bootstrap to assemble NetTY components

2. Assemble the eventLoopGroup

3. Assemble Channel

4. Add a handler handler

5. Establish a connection

6. Send data to the server

public class FirstClient {
    public static void main(String[] args) throws InterruptedException {
        // start class
        new Bootstrap()
                // Add the EventLoop group
                .group(new NioEventLoopGroup())
                // add channel
                .channel(NioSocketChannel.class)
                // add a handler
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        // Encode the content to be sent
                        nioSocketChannel.pipeline().addLast(newStringEncoder()); }})//5. Connect to server
                .connect(new InetSocketAddress("localhost".8080))
                .sync()
                .channel()
                //6
                .writeAndFlush("hello"); }}Copy the code

After starting the server and then the client, you can find that the server receives the information from the client. It doesn’t matter if you’re still a little confused by this, we’ll explain each component below.

Understand the components in Netty

3.1 EventLoop

An EventLoop is a single-threaded executor that maintains a Selector and handles IO events on a Channel.

3.2 EventLoopGroup

An EventLoopGroup is a group of Eventloops. A Channel usually calls the Register method in the EventLoopGroup to bind one of the Eventloops. Subsequent I/O events in the Channel are processed by the EventLoop.

3.3 the channel

A channel is a data transmission stream. A channel can be understood as a carrier of communication.

3.4 ChannelHandler

Channelhandlers are used to handle various events on a Channel. All channelHandlers are linked together to form a pipeline. Simply put, a channel is a data transfer channel, and a ChannelHandler is used to process the data in the channel.

3.5 ByteBuf

ByteBuf is the data transmission carrier in NetTY. The basic unit of network data is always bytes. ByteBuf is used to transmit the bytes on the network.

(4) EventLoop

EventLoop can handle a variety of tasks. Using EventLoop alone can be done through the following steps:

1. Create an EventLoopGroup

2. Get the EventLoop from the EventLoopGroup

3. Execute the task through EventLoop

This is expressed in code:

public class TestEventLoop {
    public static void main(String[] args) {
        // create an event loop group
        NioEventLoopGroup can process I/O events, common tasks, and scheduled tasks
        EventLoopGroup group=new NioEventLoopGroup();
        // get the next event loop object
        EventLoop eventLoop = group.next();
        //3. Perform common tasks
        eventLoop.execute(()->{
            System.out.println("Normal mission");
        });
        //4. Execute a scheduled task
        eventLoop.scheduleAtFixedRate(()->{
            System.out.println("Scheduled task");
        },0.1, TimeUnit.SECONDS); }}Copy the code

The group(new NioEventLoopGroup()) we wrote in the introductory program uses EventLoop to handle IO tasks.

In Netty, we also specify boss and Work when binding group. Boss handles connections, work handles subsequent operations after receiving read/write requests, and sometimes we can also customize EventLoopGroup to handle other tasks. So the previous FirstServer can be written as follows:

public class NioServer {
    public static void main(String[] args) {
        // Bosses are used to handle connections
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        //work is used to process read and write requests
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        //otherGroup handles common tasks, such as printing a piece of content
        EventLoopGroup otherGroup=new DefaultEventLoop();
        
        new ServerBootstrap()
                .group(bossGroup,workGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf byteBuf = (ByteBuf) msg;
                                System.out.println(byteBuf.toString());
                                ctx.fireChannelRead(msg); // Pass the MSG to the next handler
                            }
                        })
                        .addLast(otherGroup,"handler".new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf byteBuf = (ByteBuf) msg; System.out.println(byteBuf.toString()); }}); } }).bind(8080); }}Copy the code

(5) of the Channel

There are several common methods in a Channel:

Close () closedchannel
pipeline(a)Add processorwrite(a)Writes data to a bufferflush(a)The data is flushed out, that is, to the serverwriteAndFlush(a)Write and spawn dataCopy the code

We walk you through the client code of the introductory case

5.1 Channel connection

public class NioClient  {
    public static void main(String[] args) throws InterruptedException {
        ChannelFuture channelFuture = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        nioSocketChannel.pipeline().addLast(newStringEncoder()); }})// Connect is an asynchronous call, so you must wait for the connection to be established using the sync method
                .connect(new InetSocketAddress("localhost".8080));
        Use sync to block the thread until the connection is established
        channelFuture.sync();
        channelFuture.channel().writeAndFlush("hello,world"); }}Copy the code

Channelfuture.sync (); When the connect method is called to establish a connection, the connect method is actually an asynchronous method, so there is no way to fetch the connected channel, let alone write data, without waiting for the connection to be established with channelFuture.sync().

Instead of using Sync to wait for a connection, you can set listeners to get channelFuture

public static void main(String[] args) throws InterruptedException {
    ChannelFuture channelFuture = new Bootstrap()
            .group(new NioEventLoopGroup())
            .channel(NioSocketChannel.class)
            .handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                    nioSocketChannel.pipeline().addLast(newStringEncoder()); }})// Connect is an asynchronous call, so you must wait for the connection to be established using the sync method
            .connect(new InetSocketAddress("localhost".8080));
    // use the addListener method to process the result asynchronously
    channelFuture.addListener(new ChannelFutureListener() {
        After the NIO connection has been established, the operationComplete method is called
        @Override
        public void operationComplete(ChannelFuture channelFuture) throws Exception {
            Channel channel = channelFuture.channel();
            channel.writeAndFlush("hello,world"); }}); }Copy the code

The idea is the same, wait until the connection is established to deal with the corresponding method.

5.2 Closure of channel

In addition to the connection being an asynchronous method, a channel’s closing method is also asynchronous and therefore requires passing

Synchronous blocking mode waiting to close: Channel Channel = channelfuture.channel (); ChannelFuture closeFuture = channel.closeFuture(); System.out.println("Waiting for closure.");
// When another thread closes a channel,sync waits
closeFuture.sync();
System.out.println("Connection closed");
Copy the code

Listener callbacks can also be used:

Channel channel = channelFuture.channel();
ChannelFuture closeFuture = channel.closeFuture();
closeFuture.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture channelFuture) throws Exception {
        System.out.println("Connection closed"); }});Copy the code

(6) ChannelHandler

Channelhandlers are used to handle various events on a Channel. There are two types of channelhandlers: Inbound and outbount.

ChannelInboundHandlerAdapter subclasses of client is mainly used to read data, write the result back.

ChannelOutboundHandlerAdapter word class is mainly the result of the write back.

The handler and pipeline code was written in the previous example

public static void main(String[] args) {
    new ServerBootstrap()
            .group(new NioEventLoopGroup())
            .channel(NioServerSocketChannel.class)
            .childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                     // get pipeline from channel
                     ChannelPipeline pipeline = nioSocketChannel.pipeline();
                     // Add handler handler to pipeline
                    pipeline.addLast(new ChannelInboundHandlerAdapter(){
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            ByteBuf byteBuf = (ByteBuf) msg;
                            System.out.println(byteBuf.toString());
                            ctx.fireChannelRead(msg); // Pass the MSG to the next handler}});//3. Add multiple representations and execute them in sequence
                    pipeline.addLast(new ChannelInboundHandlerAdapter(){
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf byteBuf = (ByteBuf) msg; System.out.println(byteBuf.toString()); }}); } }).bind(8080);
Copy the code

(7) ByteBuf

ByteBuf in Netty is friendlier and more powerful than ByteBuffer in the JDK. The main advantages of ByteBuf are as follows:

1. Support automatic capacity expansion

2, support pooling technology, you can reuse instances, save memory

3, read and write pointer separation

4, Many methods embody zero copy, such as slice, duplicate, etc

The following steps will take you through ByteBuf.

1. Automatic capacity expansion

Create a default ByteBuf with an initial capacity of 256 that will automatically expand as data is written.

public static void main(String[] args) {
    ByteBuf buf= ByteBufAllocator.DEFAULT.buffer();
    System.out.println(buf);
    StringBuilder stringBuilder=new StringBuilder();
    for (int i = 0; i < 500; i++) {
        stringBuilder.append("1");
    }
    buf.writeBytes(stringBuilder.toString().getBytes());
    System.out.println(buf);
}
Copy the code

Results:

PooledUnsafeDirectByteBuf(ridx: 0, widx: 0, cap: 256)
PooledUnsafeDirectByteBuf(ridx: 0, widx: 500, cap: 512)
Copy the code

ByteBuf capacity expansion rules are as follows:

If the data size does not exceed 512, expand the data size to an integer multiple of 16 each time

If the data size exceeds 512, it is expanded to the next 2^n times

The capacity cannot exceed Max Capacity

Direct memory and heap memory

ByteBuf supports the creation of direct memory based ByteBuf as well as heap memory based ByteBuf. The difference between the two is:

Heap memory allocation efficiency is high, but the read and write performance is relatively low.

Direct memory allocation is less efficient, but read/write performance is higher (one less memory copy)

Netty uses direct memory by default as the way to create ByteBuf

ByteBufAllocator.DEFAULT.heapBuffer();
ByteBufAllocator.DEFAULT.directBuffer();
Copy the code

3. Pooling technology

ByteBuf supports pooling. Pooling means that ByteBuf can be reused after being created to save memory. Netty pooling is enabled or disabled by JVM parameters.

-Dio.netty.allocator.type={unpooled|pooled}
Copy the code

(8) Summary

Netty is a very powerful framework, but network development is a complex thing, I will use Netty to do some simple applications, through these applications will make Netty easier to understand some. I’m fish boy. See you next time!