BFT-SMaRt:用Netty做客户端的可靠信道

关键字:Netty BFT-SMaRt Channel findCache KeyLoader Bootstrap NioEventLoopGroup ChannelFuture 视图

Netty是目前最高效便捷的NIO框架。Netty可提供更加高可用、更好健壮性的稳定大规模连接的IO通道。任何一款区块链早期的技术产品,都是从联盟链开始演进,因为联盟链降低了很多原教旨的难度。回到BFT-SMaRt,它的网络连接分为节点之间的连接,节点与客户端之间的连接。节点之间的连接,我们在BFT-SMaRt:用Java做节点间的可靠信道一文中详细分析了在共识逻辑之前节点之间能够做到的连接准备。那么,本文将继续探索在BFT-SMaRt项目中,节点与客户端之间的连接是如何实现的。

作为源码研究的起点,有两个现成的入口:

  • 服务端:ServerCommunicationSystem构造函数的最后一个步骤,即clientsConn的创建。
  • 客户端:CounterClient类的入口命令,将本地作为客户端对节点发起访问请求。

一、Netty服务端的构建

首先构建服务端,转到ServerCommunicationSystem构造函数的最后一行。

clientsConn = CommunicationSystemServerSideFactory.getCommunicationSystemServerSide(controller);

这里采用了工厂模式的设计:构建一个controller基类,业务方可有多个实现类,在工厂get方法中传入实现类对象,通过不同的实现类,返回不同的处理对象。BFT-SMaRt并未有多个实现类,这里可以在上层业务方进行丰富。

public class CommunicationSystemServerSideFactory {
    public static CommunicationSystemServerSide getCommunicationSystemServerSide(ServerViewController controller) {
        return new NettyClientServerCommunicationSystemServerSide(controller);
    } // 直接返回NettyClientServerCommunicationSystemServerSide对象
}

直接返回NettyClientServerCommunicationSystemServerSide对象,以下称NettyClientServerCommunicationSystemServerSide类为Netty服务端类。

1. 父类构造函数

直接进入NettyClientServerCommunicationSystemServerSide类的构造函数,函数体内无super指定父类构造函数,因此隐式调用父类SimpleChannelInboundHandler的无参构造函数。

对于不熟悉继承关系下构造函数的执行顺序的朋友,请自行补充上。

protected SimpleChannelInboundHandler() {
    this(true);
}

父类的无参构造函数指定了本地的有参构造。设定了本地属性autoRelease为true。

protected SimpleChannelInboundHandler(boolean autoRelease) {
  this.matcher = TypeParameterMatcher.find(this, SimpleChannelInboundHandler.class, "I");
  this.autoRelease = autoRelease;
}

接下来执行TypeParameterMatcher的find方法。find方法主要维护一个查找缓存,包括构建和使用。

① 查找缓存

该方法首先获得并配置查找缓存findCache:

Map<Class<?>, Map<String, TypeParameterMatcher>> findCache = InternalThreadLocalMap.get().typeParameterMatcherFindCache(); // InternalThreadLocalMap容器
Class<?> thisClass = object.getClass();
Map<String, TypeParameterMatcher> map = (Map)findCache.get(thisClass); // 类型参数匹配器
if (map == null) {
map = new HashMap();
findCache.put(thisClass, map);
}

查找缓存会将热度较高的内容优先缓存,以增进查询速度。

查找缓存的容器结构是通过InternalThreadLocalMap来构建,注意从SimpleChannelInboundHandler开始,始终带着泛型<I>进入,而本例中的泛型类为TOMMessage,该类是共识排序消息类,将会在BFT-SMaRt共识部分展开介绍。那么,find方法会将泛型类放置到查找缓存findCache中。

a) 匹配器

接下来,获得并配置类型参数匹配器,也是用于增强查找。

TypeParameterMatcher matcher = (TypeParameterMatcher)((Map)map).get(typeParamName);
if (matcher == null) {
    matcher = get(find0(object, parametrizedSuperclass, typeParamName));
    ((Map)map).put(typeParamName, matcher);
}
return matcher;

匹配器使用到Java的反射机制来查找类。

首先通过本地map查找类型参数匹配器,如果没有查到,则初始构建。使用调用find时传入的类型参数名,调用find0方法通过反射机制得到泛型类,然后调用get方法通过反射机制获得对应匹配器,最后填充进匹配器map,共同构成查找缓存findCache的内容。最后回顾一下findCache容器的结构。

Map<Class<?>, Map<String, TypeParameterMatcher>>

因此,一个类可以有多个对应不同类型参数名的匹配器。

② 相关日志

该缓存的容器结构是InternalThreadLocalMap,类加载进入内存,首先执行static静态方法。

static {
    logger.debug("-Dio.netty.threadLocalMap.stringBuilder.initialSize: {}", STRING_BUILDER_INITIAL_SIZE);
    STRING_BUILDER_MAX_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalMap.stringBuilder.maxSize", 4096);
    logger.debug("-Dio.netty.threadLocalMap.stringBuilder.maxSize: {}", STRING_BUILDER_MAX_SIZE);
}

打印出日志,StringBuilder的初始化长度以及最大长度。日志输出如下:

11:18:32.645 [main] DEBUG io.netty.util.internal.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.initialSize: 1024
11:18:32.645 [main] DEBUG io.netty.util.internal.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.maxSize: 4096

2. 服务端构造

回到NettyClientServerCommunicationSystemServerSide的构造函数,首先是配置读取及分析。通过配置文件获得私钥、IP、端口号、节点总数、节点id等信息。

① 配置读取

配置读取可分为三方面:

  • 私钥读取
  • IP加端口号读取处理
  • 配置域信息读取
a) 私钥读取

Netty服务端类有一个私有属性字段privKey,用于存储私钥,以备后续签名使用。

private PrivateKey privKey;

该字段通过服务端类的构造函数赋值。

privKey = controller.getStaticConf().getPrivateKey();

跳转到Configuration类,调用getPrivateKey方法。私钥内容是从配置域controller中获取。

return keyLoader.loadPrivateKey();

keyLoader对象是在Configuration类构造时传入。而Configuration类的构造要追踪到其子类TOMConfiguration的构造函数,继续TOMConfiguration是在ViewController构造时调用。这部分内容将在CounterClient入口时展开。回到keyLoader,它是KeyLoader的实例,而KeyLoader有三个子类。

  • RSAKeyLoader,适用于RSA类非对称加密算法簇的秘钥加载。
  • ECDSAKeyLoader,适用于ECDSA类非对称加密算法簇的秘钥加载,全称椭圆曲线数字签名算法。是ECC与DSA的结合。Java原生类库中在jdk1.7以后已经加入支持。
  • SunECKeyLoader,适用于jdk自带的sunEC加密秘钥的加载,位于sun.security.ec.SunEC。

下面是他们的类图关系。

b) IP端口号

接下来是从配置域中读取节点服务器端的IP端口号。

// 获取IP、端口号
String myAddress;
String confAddress = controller.getStaticConf().getRemoteAddress(controller.getStaticConf().getProcessId())
      .getAddress().getHostAddress();
if (InetAddress.getLoopbackAddress().getHostAddress().equals(confAddress)) {
   myAddress = InetAddress.ge
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值