Distributed topic|After two days of entanglement, I decided to publish this article (Little Bai must read the Netty Tutorial, there are many pictures)


Before writing the code, let's take a look at netty's threading model. This is more interesting than the fixed-format code. After reading the threading model, you will know the meaning of the fixed pieces of code written by netty.

Thread model diagram

Insert picture description here

This thread model diagram probably contains these components: bossGroup, workGroup, selector (accept), selector (read and write), pipeline, NioSocketChannel, NioServerSocketChannel;

  • bossgroup,workgroup

    In netty, processing client requests will be registered on two types of selectors. These two types of selectors correspond to two thread pools, bossGroup and workgroup, respectively. BossGroup mainly deals with the selectors registered for the connection between the client and the server; the
    workgroup is also known by the name It is the thread pool used for work. It is mainly responsible for processing the selector logic of client read events;
    in the first line of code to create netty, these two thread pools are created. In general, the bossgroup will be set as a thread , Workgroup will set up multiple threads, if not written by default, netty will get the number of cpu cores in the current server*2 as the number of threads created by default.

  • selector(accepet), selector (read and write)

    The selector and the selector in NIO are the same component, but there are two types of selectors in netty: the selector that handles connection events and the selector that handles read and write events;
    but these events are handled in NIO. In the same selector, NIO judges whether it is a connection event or a read-write event by traversing the key, and then handed over to the logic of the back-end thread processing;

  • NioServerSocketChannel

    This is a channel created after the server is started, and then the channel will be registered in the selector, and the accept event of interest will be added. All subsequent connections initiated by the client will be monitored by the channel. Specifically for what to do, we will introduce the next component

  • NioSocketChannel

    After the client initiates a connection request, the server generates a NioSocketChannel by calling the accepet method of NioServerSocketChannel, and then selects an eventLoop from the workGroup, then registers the channel to the selector of the eventLoop thread, and adds the interested read event ; All
    subsequent client and server read and write operations will be performed in this channel.

  • pipline

    pipline is a pipeline processor that implements the chain of responsibility mode. After initialization, some processors will be added, such as encoders, decoders, and business logic processors. After select receives the data sent by the client, it will send the data Throw it into this pipeline, and then execute these processors in sequence from beginning to end;
    if the server sends data to the client, the processors will be executed in turn from the end to the head, but sending data from the server to the client will only Execute the outbound processor; the client sends data to the server, only the inbound processor is executed.

After introducing these basic components, we should have a preliminary understanding of netty's threading model. Now we probably sort out the entire process of netty:

Process explanation

  1. When the server is initialized, two thread groups bossGroup and workGoup will be created;

  2. Create a NioServerSocketChannel and register it on the selector of eventLoop in bossGroup, add accept events that you are interested in, and listen to the specified port;

  3. Client1 initiates a connection request, an accept event is generated on the server side, and the accept event is obtained by traversing the key in the selector;

  4. The NioServerSocketChannel on the server side is blocked by the accept method (in fact, the event has already come, and there is no need to block), and a client channel1 (NioSocketChannel) is returned;

  5. After obtaining chnnel1, the server will select an eventloop1 from the workgroup, register channel1 to the selector1 of the eventloop1, and add the reading event of interest; at this time, the pipeline1 in the channel has been initialized, and all processing All the devices are added to pipline1;

  6. At this time, a new client2 is added to initiate a connection, and the same operation will be performed, and finally chnnel2 will be registered on the selector2 in another eventloop2, and the interested read event will be added; at this time, the pipeline2 in the channel has been initialized, and All processors have been added to pipline2;

  7. If client1 sends data to the server, the selector1 generated by the server will listen to the event (read event), read the data in the channel, and deliver the data to pipline1 for subsequent logical processing;

  8. If client2 sends data to the server, the selector2 generated by the server will listen to the event (read event), read the data in the channel, and deliver the data to pipe2 to perform subsequent logical processing;

Get started quickly

The basic composition of netty and its threading model have been briefly discussed before, and now we demonstrate how to use netty for development: the code has been placed in the code cloud: piercing the cloud arrow

Add dependency

   <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>5.0.0.Alpha1</version>
   </dependency>

Server code

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {
    public static void main(String[] args) {
        // 创建 处理连接请求的线程组 1个
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 创建工作组线程 默认为 cpu核数*2 个
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            
            serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //在pipline中添加自定义的handle处理器
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("netty server start");
            // 绑定9000 端口号 sync指的是 创建完端口监听后,才执行后续操作
            ChannelFuture cf = serverBootstrap.bind(9000).sync();
            // 添加监听器 
            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    System.out.println("服务启动完成");
                }
            });
            // 注册chnnel的关闭事件,sync是只有当关闭事件发生后才结束该线程,否则一直阻塞
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

Create a custom processor and write our own business logic

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf msg1 = (ByteBuf) msg;
        System.out.println(String.format("收到客户端(%s)消息:%s", ctx.channel().remoteAddress().toString(), msg1.toString(CharsetUtil.UTF_8)));
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ByteBuf buf = Unpooled.copiedBuffer("HelloClient", CharsetUtil.UTF_8);
        ctx.writeAndFlush(buf);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(String.format("有新的客户端连接:%s", ctx.channel().remoteAddress().toString()));
    }
}
# 这里的ChannelInboundHandlerAdapter已经被废弃了,大家后续可以继承SimpleChannelInboundHandler,支持传入泛型,然后配合解码器使用,这里只是做个简单的演示。

Client code

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.CharsetUtil;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class NettyClient {
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(bossGroup).channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyClientHandler());
                        }

                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                            System.out.println(String.format("有新的客户端连接:%s", ctx.channel().remoteAddress().toString()));
                        }
                    });
            System.out.println("netty client start");
            ChannelFuture cf = bootstrap.connect("127.0.0.1", 9000).sync();
            cf.addListener((ChannelFutureListener) channelFuture -> System.out.println("客户端启动完成"));

            String msg = "";
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            do {
                try {
                    msg = br.readLine();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                ByteBuf buf = Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8);
                cf.channel().writeAndFlush(buf);
            } while (!msg.equals("end"));
            System.out.println("您已退出");
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
        }

    }
}

Create client-side custom processor

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ByteBuf buf = Unpooled.copiedBuffer("HelloServer", CharsetUtil.UTF_8);
        ctx.writeAndFlush(buf);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf msg1 = (ByteBuf) msg;
        System.out.println(String.format("收到服户端(%s)消息:%s", ctx.channel().remoteAddress().toString(), msg1.toString(CharsetUtil.UTF_8)));
    }

}

Netty related interview knowledge expansion

What is unpacking and sticking

Glossary

A TCP/UDP connection is established between the client and the server. If the size of the data sent is limited in the connection, and the data to be sent is larger than this limit, unpacking will occur; the
intercepted data packet will wait for the next time When sending data, send it together. If this part of the data is sent to the server together with other data packets at this time, it will cause sticky packets;
Insert picture description here

solution

  • Define the data format of the data sent by yourself, including data length and data content, and judge whether the data is over by the length
  • Use fixed-length decoder to achieve
  • Use specified start and end symbols to achieve

Explain what is zero copy

Before talking about zero copy, we need to introduce a term "direct memory". We know that java code runs in the jvm virtual machine, and the allocated memory data is allocated in the jvm. If you want to directly access the memory data outside the jvm, That is called direct memory access;
in netty, direct memory is used directly for socket reading and writing. No need to copy the data to the buffer in the jvm, but send the data directly to the socket, no need to perform an intermediate copy operation;Insert picture description here
Insert picture description here

Search on WeChat [AI Coder] Follow the handsome me, reply [Receive dry goods], and receive a copy of the latest interview information for 2021

Guess you like

Origin blog.csdn.net/weixin_34311210/article/details/113031240