1

Introduction

We often use browsers to access web pages to get relevant information. Generally speaking, HTTP or HTTPS protocols are used. These protocols are essentially IO. The client's request is In, and the server's return is Out. However, in the current agreement framework, all our needs cannot be fully satisfied. For example, if you use HTTP to download large files, you may need to wait for a long connection.
We also know that there are various IO methods, including synchronous IO, asynchronous IO, blocking IO and non-blocking IO. The performance of different IO methods is also different, and netty is a NIO framework based on asynchronous event-driven.

This series of articles will discuss the detailed use of netty, through the specific combination of principles + examples, let everyone understand and recognize the charm of netty.

netty introduction

Netty is an excellent NIO framework. Everyone's first image of IO should be more complicated, especially when dealing with various HTTP, TCP, and UDP protocols, which is very complicated to use. But netty provides a friendly encapsulation of these protocols, and IO programming can be done quickly and concisely through netty. Netty is easy to develop, has excellent performance, and has both stability and flexibility. If you want to develop high-performance services, then it is always right to use netty.

The latest version of netty is 4.1.66.Final. In fact, this version is the most stable version officially recommended. Netty also has a 5.x version, but it is not officially recommended.

If you want to use it in your project, you can introduce the following code:

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.66.Final</version>
        </dependency>

Below we will experience the charm of netty from a simple example.

netty's first server

What is a server? A program that can provide services to the outside world can be called a server. Setting up a server is the first step in all external services. How to use netty to build a server? The server is mainly responsible for processing various server requests. Netty provides a ChannelInboundHandlerAdapter class to handle such requests. We only need to inherit this class.

In NIO, each channel is a communication channel between the client and the server. ChannelInboundHandlerAdapter defines some events and situations that may occur on this channel, as shown in the following figure:

As shown in the above figure, many events can occur on the channel, such as establishing a connection, closing a connection, reading data, reading completion, registration, cancellation of registration, etc. These methods can all be overridden, we only need to create a new class and inherit ChannelInboundHandlerAdapter.

Here we create a new FirstServerHandler class and rewrite the channelRead and exceptionCaught methods. The first method is to read messages from the channel, and the second method is to handle exceptions.

public class FirstServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 对消息进行处理
        ByteBuf in = (ByteBuf) msg;
        try {
            log.info("收到消息:{}",in.toString(io.netty.util.CharsetUtil.US_ASCII));
        }finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 异常处理
        log.error("出现异常",cause);
        ctx.close();
    }
}

In the above example, after receiving the message, we call the release() method to release it without actual processing. Calling the release method is a common practice after the message is used. The above code will force msg to ByteBuf. If you don’t want to do the conversion, you can use it directly like this:

        try {
            // 消息处理
        } finally {
            ReferenceCountUtil.release(msg);
        }

In the exception handling method, we print out the exception information and close the exception context.

With Handler, we need to create a new Server class to use Handler to create channels and receive messages. Next we look at the message processing flow of netty.

In netty, the processing of IO is implemented using multi-threaded event loop. EventLoopGroup in netty is an abstract class of these event loops.

Let's observe the class structure of EventLoopGroup.

It can be seen that EventLoopGroup inherits from EventExecutorGroup, and EventExecutorGroup inherits from ScheduledExecutorService that comes with JDK.

So EventLoopGroup is essentially a thread pool service. It is called Group because it contains many EventLoops, which can be traversed by calling the next method.

EventLoop is used to process the IO information registered in the channel of the EventLoop. An EventLoop is an Executor, which is executed by continuously submitting tasks. Of course, one EventLoop can register multiple channels, but it is not handled in this way under normal circumstances.

EventLoopGroup composes multiple EventLoops into a Group. Through the next method, EventLoops in the Group can be traversed. In addition, EventLoopGroup provides some register methods to register Channel to the current EventLoop.

As you can see from the above figure, the return result of register is a ChannelFuture. Everyone knows that Future can be used to obtain the execution result of asynchronous tasks. The same ChannelFuture is also an asynchronous result carrier, which can be blocked by calling the sync method. Until the execution result is obtained.

As you can see, the register method can also pass in a ChannelPromise object. ChannelPromise is a subclass of ChannelFuture and Promise at the same time. Promise is a subclass of Future. It is a special Future that can control the state of Future.

EventLoopGroup has many implementations of subclasses. Here we use NioEventLoopGroup, and Nio uses Selector to select the channel. Another feature is that NioEventLoopGroup can add child EventLoopGroup.

For the NIO server program, we need two groups, one group is called bossGroup, which is mainly used to monitor connections, and the other group is called worker group, which is used to handle connections accepted by the boss. These connections need to be registered in the worker group to proceed. handle.

Pass these two groups to ServerBootstrap, you can start the service from ServerBootstrap, the corresponding code is as follows:

//建立两个EventloopGroup用来处理连接和消息
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new FirstServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 绑定端口并开始接收连接
            ChannelFuture f = b.bind(port).sync();

The FirstServerHandler that we created at the beginning was added as the childHandler's handler when the Channel was initialized.

In this way, when there is a newly created channel, the FirstServerHandler will be used to process the data of the channel.

In the above example, we also specify some ChannelOptions to set some attributes of the channel.

Finally, we bound the corresponding port and started the server.

Netty's first client

We have written the server above and started it, and now we need a client to interact with it.

If you don't want to write code, you can directly interact with the server by telnet localhost 8000, but here we hope to use netty's API to build a client and server to interact.

The process of constructing netty client is basically the same as that of constructing netty server. First, we also need to create a Handler to process specific messages. Similarly, here we also inherit ChannelInboundHandlerAdapter.

The previous section mentioned that there are many methods in ChannelInboundHandlerAdapter, which can be rewritten according to your own business needs. Here we hope to send a message to the server when the Channel is active. Then you need to rewrite the channelActive method, and also want to deal with the exception, so you also need to rewrite the exceptionCaught method. If you want to process when the channel reads the message, you can override the channelRead method.

The created FirstClientHandler code is as follows:

@Slf4j
public class FirstClientHandler extends ChannelInboundHandlerAdapter {

    private ByteBuf content;
    private ChannelHandlerContext ctx;

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        this.ctx = ctx;
        content = ctx.alloc().directBuffer(256).writeBytes("Hello flydean.com".getBytes(StandardCharsets.UTF_8));
        // 发送消息
        sayHello();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 异常处理
        log.error("出现异常",cause);
        ctx.close();
    }
    
    private void sayHello() {
        // 向服务器输出消息
        ctx.writeAndFlush(content.retain());
    }
}

In the above code, we first apply for a ByteBuff from the ChannelHandlerContext, and then call its writeBytes method to write the data to be transmitted. Finally, call the writeAndFlush method of ctx to output a message to the server.

The next step is to start the client service. On the server side, we have built two NioEventLoopGroups, which take into account the selection of the channel and the reading of messages in the channel. For the client, this problem does not exist, and only one NioEventLoopGroup is needed here.

The server uses ServerBootstrap to start the service, and the client uses Bootstrap, and the startup business logic is basically the same as the server startup:

        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(new FirstClientHandler());
                 }
             });

            // 连接服务器
            ChannelFuture f = b.connect(HOST, PORT).sync();

Run server and client

With the above preparations, we can run. Run the server first, then the client.

If there is no problem, the following should be output:

[nioEventLoopGroup-3-1] INFO com.flydean01.FirstServerHandler - 收到消息:Hello flydean.com

Summarize

A complete server, client example is complete. Let's summarize the workflow of netty. For the server side, first establish a handler for the actual processing of messages, and then use ServerBootstrap to group EventLoop and bind the port to start. For the client, it is also necessary to establish a handler to process the message, then call Bootstrap to group EventLoop, and bind the port to start.

With the above discussion, you can develop your own NIO service. Is not it simple? Follow-up articles will have an in-depth discussion on netty's architecture and principles behind it, so stay tuned.

For the examples in this article, please refer to: learn-netty4

This article has been included in http://www.flydean.com/01-netty-startup/

The most popular interpretation, the most profound dry goods, the most concise tutorial, and many tips you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", know technology, know you better!


flydean
890 声望432 粉丝

欢迎访问我的个人网站:www.flydean.com