头图

Preface

Last article, we Netty made a basic overview, know what Netty and Netty simple applications.

Netty source code analysis series (1) Netty overview

In this article, we will talk Netty the architecture design of 0611472991822d. Before learning a framework, we must first understand its design principles, and then conduct in-depth analysis.

Next, we analyze the architecture design of Netty from three aspects.

Selector model

Java NIO is based on the Selector model to achieve non-blocking I/O . The bottom layer of Netty is based on Java NIO , so the Selector model is also used.

Selector model solves the traditional blocking I/O programming problem of one client and one thread. Selector provides a mechanism for monitoring one or more NIO channels and identifying when one or more NIO channels can be used for data transmission. In this way, one thread can manage multiple channels, thereby managing multiple network connections.

image-20210804231340262

Selector provides the ability to select and execute tasks that are ready. From a bottom-level perspective, the Selector polls whether the Channel is ready to perform each I/O operation. Selector allows a single thread to process multiple Channels. Selector is a multiplexing technology.

SelectableChannel

Not all Channels can be reused by Selector, only subclasses of abstract class SelectableChannel can be reused by Selector.

For example, FileChannel cannot be reused by the selector because FileChannel is not a subclass of SelectableChannel

In order to be used with Selector, SelectableChannel must first register an instance of this class register This method returns a new SelectionKey object, which indicates that Channel has been registered Selector Selector registering to Channel will remain registered until it is cancelled.

A Channel can be registered with any specific Selector at most once, but the same Channel can be registered to multiple Selectors. You can call the isRegistered method to determine whether a Channel is registered with one or more Selectors.

SelectableChannel can be safely used by multiple concurrent threads.

Channel registered to Selector

Use SelectableChannel of register method can be Channel registered to Selector . The method interface source code is as follows:

    public final SelectionKey register(Selector sel, int ops)
        throws ClosedChannelException
    {
        return register(sel, ops, null);
    }
    
    public abstract SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException;

The description of each option is as follows:

  • sel : Specify Channel to register for Selector .
  • ops : Specify the operation of the channel to be queried for Selector

A Channel registered in the Selector represents a SelectionKey event. The types of SelectionKey

  • OP_READ : Readable event; value: 1<<0
  • OP_WRITE : writable event; value: 1<<2
  • OP_CONNECT : The event of the client connecting to the server (tcp connection), generally to create the SocketChannel client channel; the value is: 1<<3
  • OP_ACCEPT : The server receives the client connection event, usually to create the ServerSocketChannel server channel; the value is: 1<<4

The specific registration code is as follows:

 // 1.创建通道管理器(Selector)
 Selector selector = Selector.open();
 
 // 2.创建通道ServerSocketChannel
 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
 
 // 3.channel要注册到Selector上就必须是非阻塞的,所以FileChannel是不可以使用Selector的,因为FileChannel是阻塞的
 serverSocketChannel.configureBlocking(false);
 
 // 4.第二个参数指定了我们对 Channel 的什么类型的事件感兴趣
 SelectionKey key = serverSocketChannel.register(selector , SelectionKey.OP_READ);
 
 // 也可以使用或运算|来组合多个事件,例如
 SelectionKey key = serverSocketChannel.register(selector , SelectionKey.OP_READ | SelectionKey.OP_WRITE);

worth noting: a Channel may only be registered to a Selector once, if Channel registered to Selector multiple times, in fact, equivalent to update SelectionKey of interest set .

SelectionKey

Channel relationship between 0611472991864f and Selector is determined, and once Channel in a certain ready state, it can be queried by the selector. The work then call Selector of select method completes. select method is to query the ready status of the channel operation of interest.

// 当注册事件到达时,方法返回,否则该方法会一直阻塞
selector.select();

SelectionKey contains the interest collection, which represents the selected event collection of interest. The interest collection can be read and written through SelectionKey, for example:

// 返回当前感兴趣的事件列表
int interestSet = key.interestOps();

// 也可通过interestSet判断其中包含的事件
boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;    

// 可以通过interestOps(int ops)方法修改事件列表
key.interestOps(interestSet | SelectionKey.OP_WRITE);

It can be seen that by using to operate on the interest set and the given SelectionKey constant, it can be determined whether a certain event is in the interest set.

SelectionKey contains the ready collection. The ready set is the set of operations for which the channel is ready. After a selection, the ready collection will be accessed first. You can access the ready collection like this:

int readySet = key.readyOps();

// 也可通过四个方法来分别判断不同事件是否就绪
key.isReadable();    //读事件是否就绪
key.isWritable();    //写事件是否就绪
key.isConnectable(); //客户端连接事件是否就绪
key.isAcceptable();  //服务端连接事件是否就绪

We can SelectionKey to get current channel and selector

//返回当前事件关联的通道,可转换的选项包括:`ServerSocketChannel`和`SocketChannel`
Channel channel = key.channel();

//返回当前事件所关联的Selector对象
Selector selector = key.selector();

You can attach an object or other information to the SelectionKey, so that you can easily identify a specific channel.

key.attach(theObject);
Object attachedObj = key.attachment();

You can also attach objects when registering Channel with the Selector register()

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

Traverse SelectionKey

Once the call select method, and the return value indicates that one or more channels is ready, and then by calling selector of selectedKey() method, access SelectionKey set of ready channels as follows:

Set<SelectionKey> selectionKeys = selector.selectedKeys();

You can traverse the selected key set to access the ready channel, the code is as follows:

// 获取监听事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 迭代处理
while (iterator.hasNext()) {
    // 获取事件
    SelectionKey key = iterator.next();
    // 移除事件,避免重复处理
    iterator.remove();
    // 可连接
    if (key.isAcceptable()) {
        ...
    } 
    // 可读
    if (key.isReadable()) {
        ...
    }
    //可写
    if(key.isWritable()){
        ...                
    }
}

Event driven

Netty is an asynchronous event-driven network application framework. In Netty, events refer to things that are of interest to certain operations. For example, in a Channel registered OP_READ , indicating that the Channel interested in reading, when Channel when there readable data, it will get a notification of an event.

The Netty event-driven model includes the following core components.

Channel

Channel (pipe) is a basic abstraction of Java NIO, which represents an open connection to entities such as hardware devices, files, network sockets, or a program that can complete one or more different I/O operations.

Callback

A callback is a method, a reference to a method that has been provided to another method. This allows the latter to call the former at an appropriate time. Netty uses callbacks to handle events internally; when a callback is triggered, related events can be ChannelHandler interface.

For example: In the previous article, in the pipeline processor code of the server developed by Netty, when Channel is a readable message in NettyServerHandler , the callback method channelRead will be called.

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    //读取数据实际(这里我们可以读取客户端发送的消息)
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("server ctx =" + ctx);
        Channel channel = ctx.channel();
        //将 msg 转成一个 ByteBuf
        //ByteBuf 是 Netty 提供的,不是 NIO 的 ByteBuffer.
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("客户端发送消息是:" + buf.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址:" + channel.remoteAddress());
    }


    //处理异常, 一般是需要关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

Future

Future can be regarded as a placeholder for the result of an asynchronous operation; it will be completed at some point in the future and provide access to its results. Netty provides ChannelFuture for use in asynchronous operations. Each Netty's Outbound I/O operations will all return a ChannelFuture (completely asynchronous and event-driven).

The following is an ChannelFutureListener use of 06114729918a01.

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ChannelFuture future = ctx.channel().close();
        future.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                //..
            }
        });
    }

Event and handler

In Netty, events are classified according to outbound/inbound data flow:

triggered by 16114729918a6f inbound data or related status changes include:

  • The connection has been activated or deactivated.
  • Data read.
  • User event.
  • Error event,

outbound event is the result of an action that will be

  • Open or close the connection to the remote node.
  • Write or flush data to the socket.

Each event can be distributed to a user-implemented method in the ChannelHandler The following figure shows how an event is handled by ChannelHandler

image-20210805153230027

ChannelHandler provides a basic abstraction for the processor, which can be understood as a callback that is executed in response to a specific event.

Chain of Responsibility Model

Chain of Responsibility Pattern (Chain of Responsibility Pattern) is a behavioral design pattern that creates a chain of processing objects for requests. Each node in the chain is regarded as an object, each node handles different requests, and a next node object is automatically maintained internally. When a request is sent from the head end of the chain, it will be passed to each node object in turn along the path of the chain until an object handles the request.

The focus of the chain of responsibility model is on this "chain". A chain handles similar requests, determines who will handle the request in the chain, and returns the corresponding results. In Netty, the ChannelPipeline interface is defined to abstract the chain of responsibility.

The chain of responsibility model defines an abstract handler (Handler) role that abstracts the request and defines a method to set and return a reference to the next handler. In Netty, the ChannelHandler interface is defined to assume this role.

The advantages and disadvantages of the chain of responsibility model

advantage:

  • The sender does not need to know which object will process the request sent by itself, which realizes the decoupling of the sender and the receiver.
  • Simplified the design of the sender object.
  • You can add nodes and delete nodes dynamically.

shortcoming:

  • All requests are traversed from the head of the chain, which is detrimental to performance.
  • It is not convenient to debug. Since this mode uses a similar recursive method, the logic is more complicated when debugging.

scenes to be used:

  • A request requires a series of processing work.
  • Business flow processing, such as document approval.
  • Expand and supplement the system.

ChannelPipeline

Netty's ChannelPipeline design adopts the chain of responsibility design pattern, and the bottom layer adopts the data structure of a doubly linked list, which connects the various processors on the chain in series.

For every request from the client, Netty believes that ChannelPipeline have the opportunity to process it. Therefore, all requests to the stack are propagated from the head node to the tail node (coming to the end). The msg of the node will be released).

inbound event : usually refers to the inbound data generated by the IO thread (common understanding: events coming up from the bottom of the socket are all inbound).
For example EventLoop received selector of OP_READ event, the inbound call processor socketChannel.read(ByteBuffer) After receiving the data, which will result in channel ChannelPipeline next included in channelRead method is called.

Outbound event : Usually refers to the actual output operation performed by the IO thread (common understanding: events that want to actively operate to the bottom of the socket are all outbound).
For example, the bind method intentionally requests that server socket bound to the given SocketAddress , which will cause the ChannelPipeline in the next outbound processor contained in the bind to be called.

Pass the event to the next processor

The processor must call ChannelHandlerContext to pass the event to the next processor.

The propagation method of inbound and outbound events is shown in the following figure:

The following example illustrates how event propagation is usually done:

public class MyInboundHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Connected!");
        ctx.fireChannelActive();
    }
}

public class MyOutboundHandler extends ChannelOutboundHandlerAdapter {

    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
        System.out.println("Closing...");
        ctx.close(promise);
    }
}

Summarize

It is precisely because of Netty's layered architecture design is very reasonable , that the development of various application servers and protocol stacks based on Netty can develop rapidly.

end

I am a code farmer who is being beaten and working hard to advance. If the article is helpful to you, remember to like and follow, thank you!


初念初恋
175 声望17 粉丝