Netty源码学习 - netty服务端通道的实例化流程

系列文章目录

Netty源码学习 - netty服务端通道的实例化流程
Netty源码学习 - netty服务端通道的初始化流程
Netty源码学习 - netty服务端通道的注册流程


一、创建maven项目

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>io.netty</groupId>
    <version>4.1.54.Final</version>
    <artifactId>netty-example</artifactId>
    <packaging>jar</packaging>

    <name>Netty/Example</name>


    <properties>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <jdk.source.veriosn>1.8</jdk.source.veriosn>
        <jdk.target.veriosn>1.8</jdk.target.veriosn>
    </properties>


    <dependencies>

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

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

        <!-- Needed for OCSP -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>1.65</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.54</version>
        </dependency>

        <!-- Needed on Java11 and later -->
        <dependency>
            <groupId>com.sun.activation</groupId>
            <artifactId>javax.activation</artifactId>
            <version>1.2.0</version>
        </dependency>

        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>2.6.1</version>
        </dependency>

        <dependency>
            <groupId>com.barchart.udt</groupId>
            <artifactId>barchart-udt-bundle</artifactId>
            <version>2.3.0</version>
        </dependency>

    </dependencies>

    <build>

        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>${jdk.source.veriosn}</source>
                    <target>${jdk.target.veriosn}</target>
                    <encoding>${maven.compiler.encoding}</encoding>
                </configuration>
            </plugin>

        </plugins>
    </build>


</project>


二、创建一个netty服务端


package io.netty.example.echo;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;

/**
 * Echoes back any received data from a client.
 */
public final class EchoServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoServerHandler());
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

对应的通道管理器

package io.netty.example.echo;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.StandardCharsets;

/**
 * Handler implementation for the echo server.
 */
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(EchoServerHandler.class);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf byteBuf = (ByteBuf) msg;
        logger.info("receive msg = {}", byteBuf.toString(StandardCharsets.UTF_8));
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

通过构造者模式构造一个ServerBootStrap之后就会执行绑定端口操作,此时就会创建一个用于通信的通道(Channel)并执行绑定操作。

/**
 * Create a new {@link Channel} and bind it.
 */
public ChannelFuture bind(SocketAddress localAddress) {
    validate();
    if (localAddress == null) {
        throw new NullPointerException("localAddress");
    }
    return doBind(localAddress);
}

在这里插入图片描述
这里会通过工厂创建一个通道,这里的Channel仍然是Netty中定义的。
在这里插入图片描述
这里的channelFactory属于AbstractBootstrap的一个属性,通过io.netty.bootstrap.AbstractBootstrap#channel方法定义并初始化的。

/**
 * The {@link Class} which is used to create {@link Channel} instances from.
 * You either use this or {@link #channelFactory(io.netty.channel.ChannelFactory)} if your
 * {@link Channel} implementation has no no-args constructor.
 */
public B channel(Class<? extends C> channelClass) {
    if (channelClass == null) {
        throw new NullPointerException("channelClass");
    }
    做一些验证然后设置channelFactory属性为这个创建的工厂对象
    return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}

其实无非就是将用户定义的channelClass包装到ReflectiveChannelFactory实例当中,然后,在需要获取通道的时候进行反射获取。

三、实例化通道对象

@Override
public T newChannel() {
    try {
        return clazz.getConstructor().newInstance();
    } catch (Throwable t) {
        throw new ChannelException("Unable to create Channel from class " + clazz, t);
    }
}

在构造ServerBootstrap我们设置channel为NioServerSocketChannel类型,因为这里反射就会进入到对应类的构造方法当中。

 /**
  * Create a new instance
  */
 public NioServerSocketChannel() {
     this(newSocket(DEFAULT_SELECTOR_PROVIDER));
 }

首先就是一个单例SelectorProvider对象。(如果没有设置系统参数或者SPI传入的话,则返回的为WindowsSelectorProvider对象。通过sun.nio.ch.DefaultSelectorProvider#create创建的,不同的系统应该是不一样的)。

private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

在这里插入图片描述
通过这个对象可以打开一个服务端的通道(java.nio.channels.ServerSocketChannel),不难看出此时就和NIO联系到一起了。

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        /**
         *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
         *  {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
         *
         *  See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
         */
        return provider.openServerSocketChannel();
    } catch (IOException e) {
        throw new ChannelException(
                "Failed to open a server socket.", e);
    }
}

对于客户端
在这里插入图片描述

如果是OIO(BIO)的话,那么创建的就是ServerSocket对象了。如下图所示
在这里插入图片描述
在获取了NIO的通道之后再继续创建Netty的通道实例

/**
 * Create a new instance using the given {@link ServerSocketChannel}.
 */
public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

以下为Netty当中NioServerSocketChannel和NioSocketChannel的继承关系图。
在这里插入图片描述
在最顶层的父类AbstractChannel的构造器当中会创建一个DefaultChannelPipeline,这也说明每个Channel对象都会包含一个pipeline。
在这里插入图片描述

1. 设置ChannelPipeline管道对象

在这里插入图片描述

 /**
  * Returns a new {@link DefaultChannelPipeline} instance.
  */
 protected DefaultChannelPipeline newChannelPipeline() {
     return new DefaultChannelPipeline(this);
 }

以下为关于ChannelPipeline的官方说明

A list of ChannelHandlers which handles or intercepts inbound events and outbound operations of a Channel. ChannelPipeline implements an advanced form of the Intercepting Filter pattern to give a user full control over how an event is handled and how the ChannelHandlers in a pipeline interact with each other.

通道管理器的列表,用于处理或拦截Channel的入站事件和出站操作。 通道管道对象实现了拦截器模式的高级形式,使用户可以完全控制事件的处理方式以及管道中的通道管理器如何进行交互。每一个通道都包含有自己的管道并且在每个通道创建的时候都会自动创建属于自己的管道对象。

一个事件是如何在管道上流动的呢?下图展示了一个典型的IO事件在管道上由通道管理器处理的情况。一个I/O事件要么被ChannelInboundHandler处理,要么被ChannelOutboundHandler处理,同时也可以通过相关的方法往下传递。

在这里插入图片描述

An inbound event is handled by the inbound handlers in the bottom-up direction as shown on the left side of the diagram. An inbound handler usually handles the inbound data generated by the I/O thread on the bottom of the diagram. The inbound data is often read from a remote peer via the actual input operation such as SocketChannel.read(ByteBuffer). If an inbound event goes beyond the top inbound handler, it is discarded silently, or logged if it needs your attention.

入站事件由入站处理程序按自下而上的方向进行处理,如图中左侧所示。 入站处理程序通常处理图底部I / O线程生成的入站数据。 通常通过实际的输入操作(例如SocketChannel.read(ByteBuffer)从远程另一端读取入站数据。 如果入站事件超出了顶部入站处理程序的范围,则将其静默丢弃,或者在需要引起注意时将其记录下来。

An outbound event is handled by the outbound handler in the top-down direction as shown on the right side of the diagram. An outbound handler usually generates or transforms the outbound traffic such as write requests. If an outbound event goes beyond the bottom outbound handler, it is handled by an I/O thread associated with the Channel. The I/O thread often performs the actual output operation such as SocketChannel.write(ByteBuffer).

出站事件由出站处理程序按自上而下的方向进行处理,如图中右侧所示。 出站处理程序通常会生成或转换出站流量,例如写请求。 如果出站事件超出了底部出站处理程序,则由与通道关联的I / O线程处理。 I / O线程通常执行实际的输出操作,例如SocketChannel.write(ByteBuffer)

假如说我们创建了如下的通道管道对象

   ChannelPipeline p = ...;
   p.addLast("1", new InboundHandlerA());
   p.addLast("2", new InboundHandlerB());
   p.addLast("3", new OutboundHandlerA());
   p.addLast("4", new OutboundHandlerB());
   p.addLast("5", new InboundOutboundHandlerX());

In the example above, the class whose name starts with Inbound means it is an inbound handler. The class whose name starts with Outbound means it is a outbound handler.
In the given example configuration, the handler evaluation order is 1, 2, 3, 4, 5 when an event goes inbound. When an event goes outbound, the order is 5, 4, 3, 2, 1. On top of this principle, ChannelPipeline skips the evaluation of certain handlers to shorten the stack depth:
3 and 4 don’t implement ChannelInboundHandler, and therefore the actual evaluation order of an inbound event will be: 1, 2, and 5.
1 and 2 don’t implement ChannelOutboundHandler, and therefore the actual evaluation order of a outbound event will be: 5, 4, and 3.
If 5 implements both ChannelInboundHandler and ChannelOutboundHandler, the evaluation order of an inbound and a outbound event could be 125 and 543 respectively.
Forwarding an event to the next handler
As you might noticed in the diagram shows, a handler has to invoke the event propagation methods in ChannelHandlerContext to forward an event to its next handler. Those methods include:
Inbound event propagation methods:

ChannelHandlerContext.fireChannelRegistered()
ChannelHandlerContext.fireChannelActive()
ChannelHandlerContext.fireChannelRead(Object)
ChannelHandlerContext.fireChannelReadComplete()
ChannelHandlerContext.fireExceptionCaught(Throwable)
ChannelHandlerContext.fireUserEventTriggered(Object)
ChannelHandlerContext.fireChannelWritabilityChanged()
ChannelHandlerContext.fireChannelInactive()
ChannelHandlerContext.fireChannelUnregistered()
Outbound event propagation methods:
ChannelHandlerContext.bind(SocketAddress, ChannelPromise)
ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
ChannelHandlerContext.write(Object, ChannelPromise)
ChannelHandlerContext.flush()
ChannelHandlerContext.read()
ChannelHandlerContext.disconnect(ChannelPromise)
ChannelHandlerContext.close(ChannelPromise)
ChannelHandlerContext.deregister(ChannelPromise)

and the following example shows how the event propagation is usually done:

   public class MyInboundHandler extends ChannelInboundHandlerAdapter {
        @Override
       public void channelActive(ChannelHandlerContext ctx) {
           System.out.println("Connected!");
           ctx.fireChannelActive();
       }
   }
  
   public class MyOutboundHandler extends ChannelOutboundHandlerAdapter {
        @Override
       public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
           System.out.println("Closing ..");
           ctx.close(promise);
       }
   }

Building a pipeline
A user is supposed to have one or more ChannelHandlers in a pipeline to receive I/O events (e.g. read) and to request I/O operations (e.g. write and close). For example, a typical server will have the following handlers in each channel’s pipeline, but your mileage may vary depending on the complexity and characteristics of the protocol and business logic:
Protocol Decoder - translates binary data (e.g. ByteBuf) into a Java object.
Protocol Encoder - translates a Java object into binary data.
Business Logic Handler - performs the actual business logic (e.g. database access).
and it could be represented as shown in the following example:

static final EventExecutorGroup group = new DefaultEventExecutorGroup(16);
...

ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast("decoder", new MyProtocolDecoder());
pipeline.addLast("encoder", new MyProtocolEncoder());

// Tell the pipeline to run MyBusinessLogicHandler's event handler methods
// in a different thread than an I/O thread so that the I/O thread is not blocked by
// a time-consuming task.
// If your business logic is fully asynchronous or finished very quickly, you don't
// need to specify a group.
pipeline.addLast(group, "handler", new MyBusinessLogicHandler());

Thread safety。A ChannelHandler can be added or removed at any time because a ChannelPipeline is thread safe. For example, you can insert an encryption handler when sensitive information is about to be exchanged, and remove it after the exchange.

在构造DefaultChannelPipeline的时候会传入Netty的通道对象,并设置内部链表的TailContext和HeadContext 。

protected DefaultChannelPipeline(Channel channel) {
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    
    succeededFuture = new SucceededChannelFuture(channel, null);
    voidPromise =  new VoidChannelPromise(channel, true);

    tail = new TailContext(this);
    head = new HeadContext(this);

    head.next = tail;
    tail.prev = head;
}

从以下的关系图中可以看出TailContext是一个ChannelInboundHandler(用于处理写入的信息),而HeadContext则既是ChannelInboundHandler又是ChannelOutboundHandler,既处理入站,又处理出站信息。
在这里插入图片描述

ChannelHandlerContext通道处理器上下文对象

同时这二者也都是ChannelHandlerContext的实现类。通过ChannelHandlerContext可以获取绑定的通道(netty的Channel)、执行器(EventExecutor)、绑定的通道处理器(ChannelHandler)。如下所示:
在这里插入图片描述
同时也包含一系列的fire方法用于将方法传递下去。在这里插入图片描述

以下是关于ChannelHandlerContext的官方说明:

Enables a ChannelHandler to interact with its ChannelPipeline and other handlers. Among other things a handler can notify the next ChannelHandler in the ChannelPipeline as well as modify the ChannelPipeline it belongs to dynamically.

  1. 允许一个通道处理器与管道和管道上面的其他通道管理器关联
  2. 通知管道的下一个通道管理器

You can notify the closest handler in the same ChannelPipeline by calling one of the various methods provided here. Please refer to ChannelPipeline to understand how an event flows.

  1. 动态修改管道,比如添加或者删除通道管理器

You can get the ChannelPipeline your handler belongs to by calling pipeline(). A non-trivial application could insert, remove, or replace handlers in the pipeline dynamically at runtime.

  1. 获取作为后用,比如在处理方法之外触发事件,甚至是其他的线程。

You can keep the ChannelHandlerContext for later use, such as triggering an event outside the handler methods, even from a different thread.

public class MyHandler extends ChannelDuplexHandler {

    private ChannelHandlerContext ctx;

    public void beforeAdd(ChannelHandlerContext ctx) {
        this.ctx = ctx;
    }

    public void login(String username, password) {
        ctx.write(new LoginMessage(username, password));
    }
    ...
}
  1. 保存状态信息

attr(AttributeKey) allow you to store and access stateful information that is related with a handler and its context. Please refer to ChannelHandler to learn various recommended ways to manage stateful information.

需要注意的是,一个处理器可以包含多个context对象。也就是说一个处理器可以添加到多个管道当中。这就需要保证线程的安全性了。比如以下的实例当中,对应的参数就会被计算多次。

Please note that a ChannelHandler instance can be added to more than one ChannelPipeline. It means a single ChannelHandler instance can have more than one ChannelHandlerContext and therefore the single instance can be invoked with different ChannelHandlerContexts if it is added to one or more ChannelPipelines more than once.

For example, the following handler will have as many independent AttributeKeys as how many times it is added to pipelines, regardless if it is added to the same pipeline multiple times or added to different pipelines multiple times:

 public class FactorialHandler extends ChannelInboundHandlerAdapter {

   private final AttributeKey<Integer> counter = AttributeKey.valueOf("counter");

   // This handler will receive a sequence of increasing integers starting
   // from 1.
    @Override
   public void channelRead(ChannelHandlerContext ctx, Object msg) {
     Integer a = ctx.attr(counter).get();

     if (a == null) {
       a = 1;
     }

     attr.set(a * (Integer) msg);
   }
 }

 // Different context objects are given to "f1", "f2", "f3", and "f4" even if
 // they refer to the same handler instance.  Because the FactorialHandler
 // stores its state in a context object (using an AttributeKey), the factorial is
 // calculated correctly 4 times once the two pipelines (p1 and p2) are active.
 FactorialHandler fh = new FactorialHandler();

 ChannelPipeline p1 = Channels.pipeline();
 p1.addLast("f1", fh);
 p1.addLast("f2", fh);

 ChannelPipeline p2 = Channels.pipeline();
 p2.addLast("f3", fh);
 p2.addLast("f4", fh);

构建TailContext对象
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
HeadContext构造的流程基本一致。只是会额外记录通道的unsafe属性。
在这里插入图片描述
创建完TailContext和HeadContext之后就是设置链表的关系。head与tail互相连接。并完成pipeline的创建。

2. 设置非阻塞模式

对于NIO,需要设置非阻塞模式,这个是在第二层的抽象类中完成的。

/**
 * Create a new instance
 *
 * @param parent            the parent {@link Channel} by which this instance was created. May be {@code null}
 * @param ch                the underlying {@link SelectableChannel} on which it operates
 * @param readInterestOp    the ops to set to receive data from the {@link SelectableChannel}
 */
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    this.ch = ch;
    this.readInterestOp = readInterestOp;
    try {
        ch.configureBlocking(false);
    } catch (IOException e) {
        try {
            ch.close();
        } catch (IOException e2) {
            if (logger.isWarnEnabled()) {
                logger.warn(
                        "Failed to close a partially initialized socket.", e2);
            }
        }

        throw new ChannelException("Failed to enter non-blocking mode.", e);
    }
}

同时在这里设置了当前的通道所感兴趣的事件类型为SelectionKey.OP_ACCEPT,对应的值是在NioServerSocketChannel构造中传入的。
在这里插入图片描述
而NioSocketChannel则是对读事件感兴趣。
在这里插入图片描述

3. 通道配置

在创建完NIO的通道、pipeline以及设置了阻塞模式之后,此时会创建一个ChannelConfig实例,对应的实现类型为NioServerSocketChannelConfig。它的继承关系如下图所示:
在这里插入图片描述

config = new NioServerSocketChannelConfig(this, javaChannel().socket());

这里首先会获取到前面获取的NIO的通道实例

io.netty.channel.socket.nio.NioServerSocketChannel#javaChannel
@Override
protected ServerSocketChannel javaChannel() {
    return (ServerSocketChannel) super.javaChannel();
}
io.netty.channel.nio.AbstractNioChannel#javaChannel
protected SelectableChannel javaChannel() {
    return ch;
}

然后获取对应的ServerSocket实例,如下所示,当然这些都属于NIO的范畴了。

/**
 * Retrieves a server socket associated with this channel.
 *
 * <p> The returned object will not declare any public methods that are not
 * declared in the {@link java.net.ServerSocket} class.  </p>
 *
 * @return  A server socket associated with this channel
 */
public abstract ServerSocket socket();

创建ChannelConfig主要涉及到netty通道对象、java的socket对象,以及另一个新的知识点-RecvByteBufAllocator接收缓冲区分配器。

a. rcvBufAllocator接收缓冲区分配器

接收缓冲区分配器用于分配一个新的接收缓冲区,其容量可能足够大以读取所有入站数据,而又足够小而不会浪费其空间。
首先创建一个实例对象

public DefaultChannelConfig(Channel channel) {
    this(channel, new AdaptiveRecvByteBufAllocator());
}

然后从通道内读取参数并设置

protected DefaultChannelConfig(Channel channel, RecvByteBufAllocator allocator) {
    setRecvByteBufAllocator(allocator, channel.metadata());
    this.channel = channel;
}

在这里插入图片描述
设置完参数之后,会记录netty的通道对象,而且会设置socket属性。
在这里插入图片描述

b. 普通缓冲区分配器ByteBufAllocator

在DefaultChannelConfig中还存在另一个缓冲区分配器属性allocator,如下所示

private volatile ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
/**
 * Implementations are responsible to allocate buffers. Implementations of this interface are expected to be
 * thread-safe.
 */
public interface ByteBufAllocator {

    ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;

这个默认实例的初始化实在ByteBufUtil的静态块中执行的。

    private static final byte WRITE_UTF_UNKNOWN = (byte) '?';
    private static final int MAX_CHAR_BUFFER_SIZE;
    private static final int THREAD_LOCAL_BUFFER_SIZE;
    private static final int MAX_BYTES_PER_CHAR_UTF8 =
            (int) CharsetUtil.encoder(CharsetUtil.UTF_8).maxBytesPerChar();

    static final int WRITE_CHUNK_SIZE = 8192;
    static final ByteBufAllocator DEFAULT_ALLOCATOR;

    static {
        String allocType = SystemPropertyUtil.get(
                "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
        allocType = allocType.toLowerCase(Locale.US).trim();

        ByteBufAllocator alloc;
        if ("unpooled".equals(allocType)) {
            alloc = UnpooledByteBufAllocator.DEFAULT;
            logger.debug("-Dio.netty.allocator.type: {}", allocType);
        } else if ("pooled".equals(allocType)) {
            alloc = PooledByteBufAllocator.DEFAULT;
            logger.debug("-Dio.netty.allocator.type: {}", allocType);
        } else {
            alloc = PooledByteBufAllocator.DEFAULT;
            logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType);
        }

        DEFAULT_ALLOCATOR = alloc;

        THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalDirectBufferSize", 0);
        logger.debug("-Dio.netty.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE);

        MAX_CHAR_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.maxThreadLocalCharBufferSize", 16 * 1024);
        logger.debug("-Dio.netty.maxThreadLocalCharBufferSize: {}", MAX_CHAR_BUFFER_SIZE);
    }

如果没有设置系统属性io.netty.allocator.type的值,而且非安卓平台,使用的就是pooled类型的。也就是

public static final PooledByteBufAllocator DEFAULT =
            new PooledByteBufAllocator(PlatformDependent.directBufferPreferred());

在这里插入图片描述

通过以上的一系列步骤终于完成了netty通道对象的实例化。
在这里插入图片描述
包含的信息如下
在这里插入图片描述
包含的管道信息如下
在这里插入图片描述
配置信息中的两个分配器
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Web服务完整实例构建Web通道(Web服务端) 简单、规范、易学、易用 曾经,对于上世纪八十年代就从事软件开发的笔者而言,写出自己的Web服务器(Web服务端、Web数据通道)及BS软件,是那么的遥不可及!是个难以实现的梦! 而今,看到在电脑及手机浏览器上运行的自己写出的网页程序,感慨万千。高兴之余,写成此文,与网友共享、共勉,抛砖引玉。 开发工具,HTML+JS+FlashSocket插件构成前台网页开发,中间数据通道采用笔者开发的Web服务端(利用Socket tcp/ip协议数据通讯,数据流符合规范即可),后台数据解析程序(示例)采用vfp9开发。 Socket直译为“插座、插口”,计算机书籍中多译为“套接字”,笔者认为应该理解为“电话总机及电话线路”可能更接近实际情况,socket长连接 相当于电话接通后一直保持连通状态(不论是否有语音通话),socket短连接 相当于电话接通后即语言通话(连接),否则随即断线,以便让出线路供他人使用。更多不做解释,网友可网上百度。 思路比较简单,前台通过HTML录入界面获取数据,然后组合成符合Web服务端要求的字符串(操作指令串),存入JS本地变量(各网页间的公用变量)中,FlashSocket网页采用JS定时器获取操作指令串,通过上述web服务端传输至后台,再将指令串传送给后台解析程序执行操作指令,完成操作后结果回送到前台,前台其他程序即可使用该变量。即: 1.前台页面录入数据==> 2.确认(或提交)后形成操作指令串存入本地变量供FlashSocket调用==> 3.FlashSocket将指令串传送给指定IP地址的指定端口(Web服务端的IP地址及端口)==> 4.Web服务端获取指令串==> 5.Web服务端调用后台解析程序==> 6.解析程序依据指令串进行操作、结果(字符串)回送给Web服务端==> 7.Web服务端获取结果串==> 8.Web服务端将结果字符串回送给前台FlashSocket所在的IP地址及端口==> 9.FlashSocket将结果串存入本地变量==> 10.前台页面程序调用本地变量(结果串)…… 读者可能注意到,上述各步骤除了4、7二步骤外,其余各步骤都在软件开发者的开发范围内,也就是说,只要传输的指令串及结果串符合Web服务端的收发规则,开发者再也不必操心前后台的数据传输问题了(前后台环境及开发工具完全由开发者自己说了算,可用自己熟悉的环境及语言,学习压力小),从而扫平了很多人进入BS世界的一大障碍。 实际上,上述过程类似于网上购物,消费者不需要关心商品货物的配送,只需要网上下单及等待收货即可,而Web服务端则相当于物流公司。 由于笔者接触HTML+JS时间不长,水平、经验有限,所写的示例浅白易懂,只要是搞过软件设计的人,肯定都能看懂。示例只是一个示范,还存有很多缺陷,如:口令没有加密、输入页面没有美化、网页之间调用没有安全机制……等等,正如前文所言,笔者抛砖,网友引玉,有待大家完善! 笔者目前取得的小小成绩,得益于很多前辈的书籍、资料或授课,其中有: 张洪举老师,他的多本书籍使笔者受益匪浅、获感良多; 加菲猫老师(抱歉!只知道网名,但如雷贯耳!),他在网上的BS开发授课中,为笔者打开了HTML+JS开发之门,看到了另一个世界; FlashSocket的提供者enjoy_lhl,为示例中FlashSocket网页的形成提供了基础资料; getUTF8StrLeng函数的提供者junjie,为该函数的形成提供了基础资料; formatData2函数的提供者jontyy,为该函数的形成提供了基础资料; 还有网上随时查找的一些资料,不能一一说出出处,还望作者(或提供者)海涵…… 为此,笔者对上述老师、网友们致以衷心的感谢! 本文所提供的资料中,有详细的安装、使用方法(其中也有Web服务端的收发规则),可参考、试用、交流。 由于笔者水平有限,疏漏之处难免,望网友指正,以便大家共同提高! QQ:527800911、微信:13947680916 2019.6.27

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值