昆仑山

首页 » 问答 » 简介 » BFTSMaRt用Netty做客户端的
TUhjnbcbe - 2025/7/22 18:11:00

目录

一、Netty服务端的构建1.父类构造函数①查找缓存②相关日志2.服务端构造①配置读取②服务端配置3.服务端功能①通用接口功能②Channel处理器4.节点通信层已完成二、CounterClient入口1.构建服务代理①视图控制器②启动Netty客户端2.Netty通信原理3.客户端功能①通用接口功能②channel处理器③已完成内容4.调用排序消息三、后记

关键字:NettyBFT-SMaRtChannelfindCacheKeyLoaderBootstrapNioEventLoopGroupChannelFuture视图

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

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

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

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

clientsConn=CommunicationSystemServerSideFactory.getCommunicationSystemServerSide(controller);

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

publicclassCommunicationSystemServerSideFactory{publicstaticCommunicationSystemServerSidegetCommunicationSystemServerSide(ServerViewControllercontroller){returnnewNettyClientServerCommunicationSystemServerSide(controller);}//直接返回NettyClientServerCommunicationSystemServerSide对象}直接返回NettyClientServerCommunicationSystemServerSide对象,以下称NettyClientServerCommunicationSystemServerSide类为Netty服务端类。

1.父类构造函数

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

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

protectedSimpleChannelInboundHandler(){this(true);}父类的无参构造函数指定了本地的有参构造。设定了本地属性autoRelease为true。

protectedSimpleChannelInboundHandler(booleanautoRelease){this.matcher=TypeParameterMatcher.find(this,SimpleChannelInboundHandler.class,I);this.autoRelease=autoRelease;}接下来执行TypeParameterMatcher的find方法。find方法主要维护一个查找缓存,包括构建和使用。

①查找缓存

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

Map,MapfindCache=InternalThreadLocalMap.get().typeParameterMatcherFindCache();//InternalThreadLocalMap容器ClassthisClass=object.getClass();Mapmap=(Map)findCache.get(thisClass);//类型参数匹配器if(map==null){map=newHashMap();findCache.put(thisClass,map);}查找缓存会将热度较高的内容优先缓存,以增进查询速度。

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

a)匹配器

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

TypeParameterMatchermatcher=(TypeParameterMatcher)((Map)map).get(typeParamName);if(matcher==null){matcher=get(find0(object,parametrizedSuperclass,typeParamName));((Map)map).put(typeParamName,matcher);}returnmatcher;匹配器使用到Java的反射机制来查找类。

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

Map,Map

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

②相关日志

该缓存的容器结构是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,);logger.debug(-Dio.netty.threadLocalMap.stringBuilder.maxSize:{},STRING_BUILDER_MAX_SIZE);}打印出日志,StringBuilder的初始化长度以及最大长度。日志输出如下:

11:18:32.[main]DEBUGio.netty.util.internal.InternalThreadLocalMap--Dio.netty.threadLocalMap.stringBuilder.initialSize::18:32.[main]DEBUGio.netty.util.internal.InternalThreadLocalMap--Dio.netty.threadLocalMap.stringBuilder.maxSize:2.服务端构造

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

①配置读取

配置读取可分为三方面:

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

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

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

privKey=controller.getStaticConf().getPrivateKey();跳转到Configuration类,调用getPrivateKey方法。私钥内容是从配置域controller中获取。

returnkeyLoader.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、端口号StringmyAddress;StringconfAddress=controller.getStaticConf().getRemoteAddress(controller.getStaticConf().getProcessId()).getAddress().getHostAddress();if(InetAddress.getLoopbackAddress().getHostAddress().equals(confAddress)){myAddress=InetAddress.getLoopbackAddress().getHostAddress();}elseif(controller.getStaticConf().getBindAddress().equals()){myAddress=InetAddress.getLocalHost().getHostAddress();//如果Netty绑定到环回地址,客户端将无法连接节点。为了解决这个问题,我们绑定到config/hosts.config中提供的地址。if(InetAddress.getLoopbackAddress().getHostAddress().equals(myAddress)!myAddress.equals(confAddress)){myAddress=confAddress;}}else{myAddress=controller.getStaticConf().getBindAddress();}intmyPort=controller.getStaticConf().getPort(controller.getStaticConf().getProcessId());这段读取代码与节点间通信如出一辙。但值得注意的是,配置域端口号是由两项组成,我们再次查看配置域内容。

#serverid,addressandport(theidsfrom0ton-1aretheservicereplicas).0.0..0.0..0.0..0.0.IP后面有两个端口号,第一列为客户端通信端口,第二列为节点间通信端口。就拿节点id为0的第一行举例,本地作为节点服务,其他节点要通过(serverserver)端口进行访问,而其他客户端需要通过(clientserver)端口进行访问。这一段在下面日志输出代码中也有体现。

logger.info(Port(clientserver)=+controller.getStaticConf().getPort(controller.getStaticConf().getProcessId()));logger.info(Port(serverserver)=日志打印:

14:36:19.[main]INFObftsmart.

1
查看完整版本: BFTSMaRt用Netty做客户端的