『壹』 以太坊源码分析(一 简介)
以太坊作为目前区块链技术2.0的代表作品,无论是它独创的智能合约以及它本身交易的速度都优于bitcoin,通过看它的白皮书以及一些文章也略微了解了它的一些原理,但是总体还是对它的实现半知半解。
因此就想分析下它的实现源码,再结合白皮书也许可以深入的理解它的实现。
每个包的作用大致为:
以上为个人初步理解,如有不当之处望指正
注:资料查询主要位置 wiki eip
『贰』 有没有详解Java代码的软件
有一些工具可以帮助您详解 Java 代码,包括以下几种类型:
集成开发环境(IDE):如睁液 Eclipse、IntelliJ IDEA 和 NetBeans 等。IDE 可以对 Java 代码进行分析,提供代码提示、自动补全、重构等功能,帮助开发者快速编写、修改和理解 Java 代码。
静态分析工具:如 Checkstyle、PMD 和 FindBugs 等。这些工具可以对 Java 代码进行静态分析,检测潜在的代码问题,例如代码风格不符合规范、空指针异常等。
反编译工具:如 JD-GUI、FernFlower 和 Procyon 等。这些工具可以将 Java 字节码反编译为 Java 源代码,帮助开发者理解和分析 Java 代码。
UML 工具:如 Visual Paradigm、StarUML 和 PlantUML 等。这些工具可以通过 UML 图形化戚桥表示 Java 代码,帮助开发者更好地理解 Java 代码的结构和关系。
代码注释工具:如 Javadoc、Doxygen 和 NaturalDocs 等。这些工具可以自动生成 Java 代码的文档注释,帮助开发者更好地理解和使用 Java 代码。
以上是一些常见的 Java 代码高早猛详解工具,您可以根据自己的需求选择合适的工具使用。
『叁』 【深度知识】以太坊数据序列化RLP编码/解码原理
RLP(Recursive Length Prefix),中文翻译过来叫递归长度前缀编码,它是以太坊序列化所采用的编码方式。RLP主要用于以太坊中数据的网络传输和持久化存储。
对象序列化方法有很多种,常见的像JSON编码,但是JSON有个明显的缺点:编码结果比较大。例如有如下的结构:
变量s序列化的结果是{"name":"icattlecoder","sex":"male"},字符串长度35,实际有效数据是icattlecoder 和male,共计16个字节,我们可以看到JSON的序列化时引入了太多的冗余信息。假设以太坊采用JSON来序列化,那么本来50GB的区块链可能现在就要100GB,当然实际没这么简单。
所以,以太坊需要设计一种结果更小的编码方法。
RLP编码的定义只处理两类数据:一类是字符串(例如字节数组),一类是列表。字符串指的是一串二进制数据,列表是一个嵌套递归的结构,里面可以包含字符串和列表,例如["cat",["puppy","cow"],"horse",[[]],"pig",[""],"sheep"]就是一个复杂的列表。其他类型的数据需要转成以上的两类,转换的规则不是RLP编码定义的,可以根据自己的规则转换,例如struct可以转成列表,int可以转成二进制(属于字符串一类),以太坊中整数都以大端形式存储。
从RLP编码的名字可以看出它的特点:一个是递归,被编码的数据是递归的结构,编码算法也是递归进行处理的;二是长度前缀,也就是RLP编码都带有一个前缀,这个前缀是跟被编码数据的长度相关的,从下面的编码规则中可以看出这一点。
对于值在[0, 127]之间的单个字节,其编码是其本身。
例1:a的编码是97。
如果byte数组长度l <= 55,编码的结果是数组本身,再加上128+l作为前缀。
例2:空字符串编码是128,即128 = 128 + 0。
例3:abc编码结果是131 97 98 99,其中131=128+len("abc"),97 98 99依次是a b c。
如果数组长度大于55, 编码结果第一个是183加数组长度的编码的长度,然后是数组长度的本身的编码,最后是byte数组的编码。
请把上面的规则多读几篇,特别是数组长度的编码的长度。
例4:编码下面这段字符串:
The length of this sentence is more than 55 bytes, I know it because I pre-designed it
这段字符串共86个字节,而86的编码只需要一个字节,那就是它自己,因此,编码的结果如下:
184 86 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116
其中前三个字节的计算方式如下:
184 = 183 + 1,因为数组长度86编码后仅占用一个字节。
86即数组长度86
84是T的编码
例5:编码一个重复1024次"a"的字符串,其结果为:185 4 0 97 97 97 97 97 97 ...。
1024按 big endian编码为004 0,省略掉前面的零,长度为2,因此185 = 183 + 2。
规则1~3定义了byte数组的编码方案,下面介绍列表的编码规则。在此之前,我们先定义列表长度是指子列表编码后的长度之和。
如果列表长度小于55,编码结果第一位是192加列表长度的编码的长度,然后依次连接各子列表的编码。
注意规则4本身是递归定义的。
例6:["abc", "def"]的编码结果是200 131 97 98 99 131 100 101 102。
其中abc的编码为131 97 98 99,def的编码为131 100 101 102。两个子字符串的编码后总长度是8,因此编码结果第一位计算得出:192 + 8 = 200。
如果列表长度超过55,编码结果第一位是247加列表长度的编码长度,然后是列表长度本身的编码,最后依次连接各子列表的编码。
规则5本身也是递归定义的,和规则3相似。
例7:
["The length of this sentence is more than 55 bytes, ", "I know it because I pre-designed it"]
的编码结果是:
248 88 179 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 163 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116
其中前两个字节的计算方式如下:
248 = 247 +1
88 = 86 + 2,在规则3的示例中,长度为86,而在此例中,由于有两个子字符串,每个子字符串本身的长度的编码各占1字节,因此总共占2字节。
第3个字节179依据规则2得出179 = 128 + 51
第55个字节163同样依据规则2得出163 = 128 + 35
例8:最后我们再来看个稍复杂点的例子以加深理解递归长度前缀,
["abc",["The length of this sentence is more than 55 bytes, ", "I know it because I pre-designed it"]]
编码结果是:
248 94 131 97 98 99 248 88 179 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 163 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116
列表第一项字符串abc根据规则2,编码结果为131 97 98 99,长度为4。
列表第二项也是一个列表项:
["The length of this sentence is more than 55 bytes, ", "I know it because I pre-designed it"]
根据规则5,结果为
248 88 179 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 163 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116
长度为90,因此,整个列表的编码结果第二位是90 + 4 = 94, 占用1个字节,第一位247 + 1 = 248
以上5条就是RPL的全部编码规则。
各语言在具体实现RLP编码时,首先需要将对像映射成byte数组或列表两种形式。以go语言编码struct为例,会将其映射为列表,例如Student这个对象处理成列表["icattlecoder","male"]
如果编码map类型,可以采用以下列表形式:
[["",""],["",""],["",""]]
解码时,首先根据编码结果第一个字节f的大小,执行以下的规则判断:
1.如果f∈ [0,128),那么它是一个字节本身。
2.如果f∈[128,184),那么它是一个长度不超过55的byte数组,数组的长度为 l=f-128
3.如果f∈[184,192),那么它是一个长度超过55的数组,长度本身的编码长度ll=f-183,然后从第二个字节开始读取长度为ll的bytes,按照BigEndian编码成整数l,l即为数组的长度。
4.如果f∈(192,247],那么它是一个编码后总长度不超过55的列表,列表长度为l=f-192。递归使用规则1~4进行解码。
5.如果f∈(247,256],那么它是编码后长度大于55的列表,其长度本身的编码长度ll=f-247,然后从第二个字节读取长度为ll的bytes,按BigEndian编码成整数l,l即为子列表长度。然后递归根据解码规则进行解码。
以上解释了什么叫递归长度前缀编码,这个名字本身很好的解释了编码规则。
(1) 以太坊源码学习—RLP编码( https://segmentfault.com/a/1190000011763339 )
(2)简单分析RLP编码原理
( https://blog.csdn.net/itchosen/article/details/78183991 )
『肆』 怎么在windows下启动以太坊java客户端ethereumj
以太坊源码go-ethereum怎么运行
安装基于缓卖腔MIPS的linux头文件
$ cd $PRJROOT/kernel
$ tar -xjvf linux-2.6.38.tar.bz2
$ cd linux-2.6.38
在指定路径下创建include文件夹,用来存放相关头文件配高。
$ mkdir -p $TARGET_PREFIX/include
保证linux源码是干扰衫净的。
$ make mrproper
生成需要的头文件。
$ make ARCH=mips headers_check
$ make ARCH=mips INSTALL_HDR_PATH=dest headers_install
将dest文件夹下的所有文件复制到指定的include文件夹内。
$ cp -rv dest/include/* $TARGET_PREFIX/include
最后删除dest文件夹
$ rm -rf dest
$ ls -l $TARGET_PREFIX/include
『伍』 揭秘Java虚拟机:JVM设计原理与实现详细资料大全
《揭秘Java虚拟机:JVM设计原理与实现》是2017年电子工业出版社出版的图书,作者是封亚飞。
『陆』 java并发包源码怎么读
1. 各种同步控制工具的使用
1.1 ReentrantLock
ReentrantLock感觉上是synchronized的增强版,synchronized的特点是使用简单,一切交给JVM去处理,但是功能上是比较薄弱的。在JDK1.5之前,ReentrantLock的性能要好于synchronized,由于对JVM进行了优化,现在的JDK版本中,两者性能是不相上下的。如果是简单的实现,不要刻意去使用ReentrantLock。
相比于synchronized,ReentrantLock在功能上更加丰富,它具有可重入、可中断、可限时、公平锁等特点。
首先我们通过一个例子来说明ReentrantLock最初步的用法:
package test;
import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static int i = 0;
@Override public void run() { for (int j = 0; j < 10000000; j++)
{ lock.lock(); try
{
i++;
} finally
{ lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
有两个线程都对i进行++操作,为了保证线程安全,使用了ReentrantLock,从用法上可以看出,与synchronized相比,ReentrantLock就稍微复杂一点。因为必须在finally中进行解锁操作,如果不在finally解锁,有可能代码出现异常锁没被释放,而synchronized是由JVM来释放锁。
那么ReentrantLock到底有哪些优秀的特点呢?
1.1.1 可重入
单线程可以重复进入,但要重复退出
lock.lock();
lock.lock();try{
i++;
}
finally{
lock.unlock();
lock.unlock();
}
由于ReentrantLock是重入锁,所以可以反复得到相同的一把锁,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放(重入锁)。这模仿了synchronized肆裂的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出裂丛闭第二个(或者后续)郑基synchronized块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个synchronized块时,才释放锁。
public class Child extends Father implements Runnable{ final static Child child = new Child();//为了保证锁唯一
public static void main(String[] args) { for (int i = 0; i < 50; i++) { new Thread(child).start();
}
}
public synchronized void doSomething() {
System.out.println("1child.doSomething()");
doAnotherThing(); // 调用自己类中其他的synchronized方法
}
private synchronized void doAnotherThing() { super.doSomething(); // 调用父类的synchronized方法
System.out.println("3child.doAnotherThing()");
}
@Override
public void run() {
child.doSomething();
}
}class Father { public synchronized void doSomething() {
System.out.println("2father.doSomething()");
}
}
我们可以看到一个线程进入不同的synchronized方法,是不会释放之前得到的锁的。所以输出还是顺序输出。所以synchronized也是重入锁
输出:
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
...
1.1.2.可中断
与synchronized不同的是,ReentrantLock对中断是有响应的。中断相关知识查看[高并发Java 二] 多线程基础
普通的lock.lock()是不能响应中断的,lock.lockInterruptibly()能够响应中断。
我们模拟出一个死锁现场,然后用中断来处理死锁
package test;import java.lang.management.ManagementFactory;import java.lang.management.ThreadInfo;import java.lang.management.ThreadMXBean;import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); int lock; public Test(int lock)
{ this.lock = lock;
} @Override
public void run()
{ try
{ if (lock == 1)
{
lock1.lockInterruptibly(); try
{
Thread.sleep(500);
} catch (Exception e)
{ // TODO: handle exception
}
lock2.lockInterruptibly();
} else
{
lock2.lockInterruptibly(); try
{
Thread.sleep(500);
} catch (Exception e)
{ // TODO: handle exception
}
lock1.lockInterruptibly();
}
} catch (Exception e)
{ // TODO: handle exception
} finally
{ if (lock1.isHeldByCurrentThread())
{
lock1.unlock();
} if (lock2.isHeldByCurrentThread())
{
lock2.unlock();
}
System.out.println(Thread.currentThread().getId() + ":线程退出");
}
} public static void main(String[] args) throws InterruptedException {
Test t1 = new Test(1);
Test t2 = new Test(2);
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
Thread.sleep(1000); //DeadlockChecker.check();
} static class DeadlockChecker
{ private final static ThreadMXBean mbean = ManagementFactory
.getThreadMXBean(); final static Runnable deadlockChecker = new Runnable()
{ @Override
public void run()
{ // TODO Auto-generated method stub
while (true)
{ long[] deadlockedThreadIds = mbean.findDeadlockedThreads(); if (deadlockedThreadIds != null)
{
ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds); for (Thread t : Thread.getAllStackTraces().keySet())
{ for (int i = 0; i < threadInfos.length; i++)
{ if(t.getId() == threadInfos[i].getThreadId())
{
t.interrupt();
}
}
}
} try
{
Thread.sleep(5000);
} catch (Exception e)
{ // TODO: handle exception
}
}
}
};
public static void check()
{
Thread t = new Thread(deadlockChecker);
t.setDaemon(true);
t.start();
}
}
}
上述代码有可能会发生死锁,线程1得到lock1,线程2得到lock2,然后彼此又想获得对方的锁。
我们用jstack查看运行上述代码后的情况
下面举个例子:
package test;import java.util.concurrent.CyclicBarrier;public class Test implements Runnable{ private String soldier; private final CyclicBarrier cyclic; public Test(String soldier, CyclicBarrier cyclic)
{ this.soldier = soldier; this.cyclic = cyclic;
} @Override
public void run()
{ try
{ //等待所有士兵到齐
cyclic.await();
dowork(); //等待所有士兵完成工作
cyclic.await();
} catch (Exception e)
{ // TODO Auto-generated catch block
e.printStackTrace();
}
} private void dowork()
{ // TODO Auto-generated method stub
try
{
Thread.sleep(3000);
} catch (Exception e)
{ // TODO: handle exception
}
System.out.println(soldier + ": done");
} public static class BarrierRun implements Runnable
{ boolean flag; int n; public BarrierRun(boolean flag, int n)
{ super(); this.flag = flag; this.n = n;
} @Override
public void run()
{ if (flag)
{
System.out.println(n + "个任务完成");
} else
{
System.out.println(n + "个集合完成");
flag = true;
}
}
} public static void main(String[] args)
{ final int n = 10;
Thread[] threads = new Thread[n]; boolean flag = false;
CyclicBarrier barrier = new CyclicBarrier(n, new BarrierRun(flag, n));
System.out.println("集合"); for (int i = 0; i < n; i++)
{
System.out.println(i + "报道");
threads[i] = new Thread(new Test("士兵" + i, barrier));
threads[i].start();
}
}
}
打印结果:
集合
士兵5: done士兵7: done士兵8: done士兵3: done士兵4: done士兵1: done士兵6: done士兵2: done士兵0: done士兵9: done10个任务完成
1.7 LockSupport
提供线程阻塞原语
和suspend类似
LockSupport.park();
LockSupport.unpark(t1);
与suspend相比不容易引起线程冻结
LockSupport的思想呢,和Semaphore有点相似,内部有一个许可,park的时候拿掉这个许可,unpark的时候申请这个许可。所以如果unpark在park之前,是不会发生线程冻结的。
下面的代码是[高并发Java 二] 多线程基础中suspend示例代码,在使用suspend时会发生死锁。
而使用LockSupport则不会发生死锁。
另外
park()能够响应中断,但不抛出异常。中断响应的结果是,park()函数的返回,可以从Thread.interrupted()得到中断标志。
在JDK当中有大量地方使用到了park,当然LockSupport的实现也是使用unsafe.park()来实现的。
public static void park() { unsafe.park(false, 0L);
}
1.8 ReentrantLock 的实现
下面来介绍下ReentrantLock的实现,ReentrantLock的实现主要由3部分组成:
CAS状态
等待队列
park()
ReentrantLock的父类中会有一个state变量来表示同步的状态
通过CAS操作来设置state来获取锁,如果设置成了1,则将锁的持有者给当前线程
如果拿锁不成功,则会做一个申请
首先,再去申请下试试看tryAcquire,因为此时可能另一个线程已经释放了锁。
如果还是没有申请到锁,就addWaiter,意思是把自己加到等待队列中去
其间还会有多次尝试去申请锁,如果还是申请不到,就会被挂起
同理,如果在unlock操作中,就是释放了锁,然后unpark,这里就不具体讲了。
2. 并发容器及典型源码分析
2.1ConcurrentHashMap
我们知道HashMap不是一个线程安全的容器,最简单的方式使HashMap变成线程安全就是使用Collections.synchronizedMap,它是对HashMap的一个包装
同理对于List,Set也提供了相似方法。
但是这种方式只适合于并发量比较小的情况。
我们来看下synchronizedMap的实现
它会将HashMap包装在里面,然后将HashMap的每个操作都加上synchronized。
由于每个方法都是获取同一把锁(mutex),这就意味着,put和remove等操作是互斥的,大大减少了并发量。
下面来看下ConcurrentHashMap是如何实现的
在ConcurrentHashMap内部有一个Segment段,它将大的HashMap切分成若干个段(小的HashMap),然后让数据在每一段上Hash,这样多个线程在不同段上的Hash操作一定是线程安全的,所以只需要同步同一个段上的线程就可以了,这样实现了锁的分离,大大增加了并发量。
在使用ConcurrentHashMap.size时会比较麻烦,因为它要统计每个段的数据和,在这个时候,要把每一个段都加上锁,然后再做数据统计。这个就是把锁分离后的小小弊端,但是size方法应该是不会被高频率调用的方法。
在实现上,不使用synchronized和lock.lock而是尽量使用trylock,同时在HashMap的实现上,也做了一点优化。这里就不提了。
2.2BlockingQueue
BlockingQueue不是一个高性能的容器。但是它是一个非常好的共享数据的容器。是典型的生产者和消费者的实现。
『柒』 怎么看JAVA开源项目的源码
有个开源代码托管平台叫github来了解下。
GitHub是一个面向开源及私有软件项目的托管平台,因为只支持git作为唯一的版本库格式进行托管,故名GitHub。
github也可察雀以是一个远程代码仓库,你可以将你的代码或者项目上传到github仓库,这个完全没有问题,网上有github客户端管理软件,操作非常简单,就类似于:SVN、CVS。
github也是一个开源代码协作社区,通过github你可以参与别人的开源项目,也可以让别人参与你的开源项目。有些公司的产品,自己不想投入人力,但又不想放弃,就采用github代码托管的方式,将代码开源出去,让开发爱好者参与进来,其中docker就是一个很好的例子,也是开源最成功的一个项目。
下面介绍如何从github上拿到开源项目:zxin。
1、打开github官网“”。
2、根据“拍没知zxing”查找。
3、下载开源项目:zxing
操作:Cloneordowanload->DownloadZIP.
这样zxin源代码就拿到了,doc目录是项目文档袭消,打开
index.html,全是英文,
要能读懂源代码,需要有很好的英文阅读能力,祝你好运。『捌』 以太坊源码分析--p2p节点发现
节点发现功能主要涉及 Server Table udp 这几个数据结构,它们有独自的事件响应循环,节点发现功能便是它们互相协作完成的。其中,每个以太坊客户端启动后都会在本地运行一个 Server ,并将网络拓扑中相邻的节点视为 Node ,而 Table 是 Node 的容器, udp 则是负责维持底层的连接。下面重点描述它们中重要的字段和事件循环处理的关键部分。
PrivateKey - 本节点的私钥,用于与其他节点建立时的握手协商
Protocols - 支持的所有上层协议
StaticNodes - 预设的静态 Peer ,节点启动时会首先去向它们发起连接,建立邻居关系
newTransport - 下层传输层实现,定义握手过程中的数据加密解密方式,默认的传输层实现是用 newRLPX() 创建的 rlpx ,这不是本文的重点
ntab - 典型实现是 Table ,所有 peer 以 Node 的形式存放在 Table
ourHandshake - 与其他节点建立连接时的握手信息,包含本地节点的版本号以及支持的上层协议
addpeer - 连接握手完成后,连接过程通过这个通道通知 Server
Server 的监听循环,启动底层监听socket,当收到连接请求时,Accept后调用 setupConn() 开始连接建立过程
Server的主要事件处理和功能实现循环
Node 唯一表示网络上的一个节点
IP - IP地址
UDP/TCP - 连接使用的UDP/TCP端口号
ID - 以太坊网络中唯一标识一个节点,本质上是一个椭圆曲线公钥(PublicKey),与 Server 的 PrivateKey 对应。一个节点的IP地址不一定是固定的,但ID是唯一的。
sha - 用于节点间的距离计算
Table 主要用来管理与本节点与其他节点的连接的建立更新删除
bucket - 所有 peer 按与本节点的距离远近放在不同的桶(bucket)中,详见之后的 节点维护
refreshReq - 更新 Table 请求通道
Table 的主要事件循环,主要负责控制 refresh 和 revalidate 过程。
refresh.C - 定时(30s)启动Peer刷新过程的定时器
refreshReq - 接收其他线程投递到 Table 的 刷新Peer连接 的通知,当收到该通知时启动更新,详见之后的 更新邻居关系
revalidate.C - 定时重新检查以连接节点的有效性的定时器,详见之后的 探活检测
udp 负责节点间通信的底层消息控制,是 Table 运行的 Kademlia 协议的底层组件
conn - 底层监听端口的连接
addpending - udp 用来接收 pending 的channel。使用场景为:当我们向其他节点发送数据包后(packet)后可能会期待收到它的回复,pending用来记录一次这种还没有到来的回复。举个例子,当我们发送ping包时,总是期待对方回复pong包。这时就可以将构造一个pending结构,其中包含期待接收的pong包的信息以及对应的callback函数,将这个pengding投递到udp的这个channel。 udp 在收到匹配的pong后,执行预设的callback。
gotreply - udp 用来接收其他节点回复的通道,配合上面的addpending,收到回复后,遍历已有的pending链表,看是否有匹配的pending。
Table - 和 Server 中的ntab是同一个 Table
udp 的处理循环,负责控制消息的向上递交和收发控制
udp 的底层接受数据包循环,负责接收其他节点的 packet
以太坊使用 Kademlia 分布式路由存储协议来进行网络拓扑维护,了解该协议建议先阅读 易懂分布式 。更权威的资料可以查看 wiki 。总的来说该协议:
源码中由 Table 结构保存所有 bucket , bucket 结构如下
节点可以在 entries 和 replacements 互相转化,一个 entries 节点如果 Validate 失败,那么它会被原本将一个原本在 replacements 数组的节点替换。
有效性检测就是利用 ping 消息进行探活操作。 Table.loop() 启动了一个定时器(0~10s),定期随机选择一个bucket,向其 entries 中末尾的节点发送 ping 消息,如果对方回应了 pong ,则探活成功。
Table.loop() 会定期(定时器超时)或不定期(收到refreshReq)地进行更新邻居关系(发现新邻居),两者都调用 doRefresh() 方法,该方法对在网络上查找离自身和三个随机节点最近的若干个节点。
Table 的 lookup() 方法用来实现节点查找目标节点,它的实现就是 Kademlia 协议,通过节点间的接力,一步一步接近目标。
当一个节点启动后,它会首先向配置的静态节点发起连接,发起连接的过程称为 Dial ,源码中通过创建 dialTask 跟踪这个过程
dialTask表示一次向其他节点主动发起连接的任务
在 Server 启动时,会调用 newDialState() 根据预配置的 StaticNodes 初始化一批 dialTask , 并在 Server.run() 方法中,启动这些这些任务。
Dial 过程需要知道目标节点( dest )的IP地址,如果不知道的话,就要先使用 recolve() 解析出目标的IP地址,怎么解析?就是先要用借助 Kademlia 协议在网络中查找目标节点。
当得到目标节点的IP后,下一步便是建立连接,这是通过 dialTask.dial() 建立连接
连接建立的握手过程分为两个阶段,在在 SetupConn() 中实现
第一阶段为 ECDH密钥建立 :
第二阶段为协议握手,互相交换支持的上层协议
如果两次握手都通过,dialTask将向 Server 的 addpeer 通道发送 peer 的信息
『玖』 ValueAnimator源码解析-基于Android API30
先上个时序图,整个调用链都在扒薯图里了。
ValueAnimator.java
初始化动画,并将监听添加到AnimationHandler
AnimationHandler.java
将Frame监听添加到Choreographer
Choreographer.java
请求下一个信号,不明白信号的可看 《Android 底层渲染 - 屏幕刷新机制源码分析》
FrameDisplayEventReceiver.java
当来了信号量后,执行onvsync,发送Handler同步消息,Message的Callback就是FrameDisplayEventReceiver,最终通过Handler执行了FrameDisplayEventReceiver.run()方法
DisplayEventReceiver.java
请求信号量,并分发处理
FrameDisplayEventReceiver.java
处理回调
Choreographer.CallbackRecord.java
执行Frame回调
AnimationHandler.java
帧回调到动画回调
ValueAnimator.java
整个流程就分析完了。动画锋此尺的核心驱动是,利用屏幕的刷新机制,请求信号,然后在通过Handler的同步消息,执行Frame回调。Frame回调中在执行动画回调。动画回调中根据时间和动画插值。计算出最新的动画值,回调给用户。ValueAnimation中animateBasedOnTime方法会返回当前动画是否银高结束,如果已经结束就移除动画回调,如果未结束FrameCallback的doFrame中处理完这一帧后,会继续请求下一个信号量。
『拾』 为什么大多数区块链项目不使用java开发
区块链项目对效率的要求比较高,所以大多数核心源码的开发都是使用c/c++。但是如果是做都区块链项目,除非要对源代码进行大量的调整,否则也不见得就不选择使用java。一般的dapp应用,使用java开发应该也是不错的选择。比如以太坊区块链的话,针对java的有web3j的类库,十分方便;比特币的话有bitcoinj类库,也很好用。还是要看还是什么级别的应用,要做什么,以及团队的情况吧。
分享两个java区块链教程:
java比特币详解
java以太坊开发